Compare commits

...
378 Commits
Author SHA1 Message Date
camierjs 5457706a29 Fix merge with global API 2026-05-01 10:28:29 -07:00
camierjs fd195d53a5 Merge branch 'dfem-multiple-outputs' into dfem-kernels 2026-05-01 10:03:48 -07:00
camierjs ced299b0b3 Remove vscode files 2026-05-01 09:49:26 -07:00
camierjs 667022093d Merge branch 'master' into origin-dfem-kernels 2026-05-01 09:47:16 -07:00
camierjs 5a3e9a93c2 Merge branch 'master' into dfem-multiple-outputs 2026-05-01 09:30:41 -07:00
Julian Andrej b2bf589c87 jit playground updates 2026-04-29 13:09:32 -07:00
Julian Andrej ac48dfcfa5 update 2026-04-27 16:02:33 -07:00
Julian Andrej 5e98b82b26 skeleton 2026-04-20 14:52:41 -07:00
Julian Andrej dfc582149d move files 2026-04-20 10:27:29 -07:00
Julian Andrej 79680d9bc9 reorganizing dfem backends 2026-04-20 10:21:39 -07:00
camierjs f261928799 Merge branch 'master' into camierjs-dfem-kernels 2026-04-14 08:10:38 -07:00
camierjs f4baeb41ad Added MMA(magic 4|5) 2026-04-12 11:08:24 -07:00
camierjs fd73f3895f Ini PADiffMmaIntegrator 2026-04-12 09:24:21 -07:00
camierjs 5820d70a50 remove unused variable 2026-04-11 14:33:58 -07:00
camierjs d923432284 Use T_Q1D for offset computation 2026-04-11 14:29:20 -07:00
camierjs cffd9c618f Reorder kernels & add fallbacks 2026-04-11 14:20:43 -07:00
camierjs 6ccc490e18 Specialization only on Q1D for dFEM 2026-04-11 13:51:34 -07:00
camierjs 80f973d708 Swap DIM as fastest indice in shared mem 2026-04-11 13:35:14 -07:00
camierjs ef13f6e9d8 Specialization only on Q1D 2026-04-11 13:24:16 -07:00
camierjs 48cb7bcc2e Cleanup 2026-04-11 13:18:21 -07:00
camierjs 621a9ed1d6 Low @ 4.43378k/s 2026-04-11 13:01:34 -07:00
camierjs 5ca23fd56f Fix PADiffLowMult 2026-04-11 11:31:35 -07:00
camierjs 1b9d97ec28 Fix CPU regs3d_t layout 2026-04-11 11:14:17 -07:00
camierjs 4add547b04 cleanup 2026-04-11 08:32:50 -07:00
camierjs 9c9b5cee2f dFEM new kernels with tensors from raw pointers @ 4.2k/s 2026-04-11 08:21:15 -07:00
camierjs 80b5ecfb7b Add back PA ∂fem std kernels 2026-04-10 17:57:43 -07:00
camierjs 074a716634 Re-enable specialized kernels and add #7 w/o specilization 2026-04-10 17:14:12 -07:00
camierjs ad9682118d wip QF apply LOW kernels 2026-04-10 17:06:19 -07:00
camierjs 25cba3afa5 Cleanup, inc kernels3d header file 2026-04-10 14:58:55 -07:00
camierjs 76f061dd9c Low running on GPU 2026-04-10 14:16:04 -07:00
camierjs 22f25269a5 wip low kernels 2026-04-10 13:19:47 -07:00
camierjs 684d8fc64b WIP LOW reg Grad 3D 2026-04-10 11:34:59 -07:00
camierjs ded0173a92 wip MFEM_SHARED full d0_regs3d_t 2026-04-09 13:40:57 -07:00
camierjs 7aca674961 Added #6 PADiffLowIntegrator 2026-04-09 11:58:10 -07:00
camierjs be6f6299aa Bring smem version 2026-04-08 16:57:34 -07:00
camierjs c81fabcc54 BP3/1/6/160 @ 4k/s 2026-04-08 15:50:23 -07:00
camierjs f963bd897b wip forall_2D_batch, constant memory 2026-04-08 15:13:04 -07:00
camierjs dcd5bee0f6 wip forall_2D_batch tries 2026-04-08 13:02:14 -07:00
camierjs 1dd20334e2 Add forall_kernel_static_smem_launch_bounds 2026-04-07 16:35:07 -07:00
camierjs 8889988956 Tuos runs w/ new kernels 2026-04-07 10:21:12 -07:00
camierjs 06b4a68c7d dFEM new kernels hc vdim, same B/G outputs 2026-04-07 09:52:07 -07:00
camierjs 17e15d08cd Merge branch 'main' of github.com:camierjs/mfem-dfem-kernels into main 2026-04-07 08:13:53 -07:00
camierjs c05ce7dacd R Identity 2026-04-07 08:13:51 -07:00
camierjs c80f7fb1f1 json update 2026-04-07 08:12:32 -07:00
camierjs 0eac62aa3b wip restriction D2D 2026-04-07 07:07:58 -07:00
camierjs fbe07d97ea All but MF new kernels 2026-04-07 05:58:47 -07:00
camierjs e4ce8375f3 Reuse qdata for PA ∂fem new kernels 2026-04-06 18:06:09 -07:00
camierjs da959ef7e9 Add back mi300a constant B & G 2026-04-03 20:19:32 -07:00
camierjs aba87d9e14 Add user cmake tuo 2026-04-03 19:27:40 -07:00
camierjs f0efcf4253 nvcc compilations 2026-04-03 19:18:04 -07:00
camierjs 7aee5f56ba Rename user cmake file 2026-04-03 18:22:16 -07:00
camierjs 9bd3409458 Add darwin/matrix user cmake files 2026-04-03 18:21:46 -07:00
camierjs cca1678ccb Add vscode files 2026-04-03 09:32:34 -07:00
camierjs 97ca2f9ecc Fix dFEM new action for exact CG iterations 2026-04-03 09:16:37 -07:00
camierjs a00f222761 dFEM kernels CPU runs 2026-04-02 17:48:30 -07:00
camierjs 99ebc58be4 wip merge fixes 2026-04-02 11:47:40 -07:00
camierjs cf213ea6b6 Merge branch 'master' into camierjs-dfem-kernels 2026-04-02 10:21:43 -07:00
camierjs 031be712a1 Warnings fix 2026-04-02 10:13:53 -07:00
camierjs 134bc32d93 Merge branch 'origin-dfem-kernels' into camierjs-dfem-kernels 2026-04-02 10:05:30 -07:00
camierjs e640a3e3fb Cleanup and run with new traces 2026-04-02 09:49:26 -07:00
Julian Andrej faba224c26 jit playground 2026-04-02 08:34:33 -07:00
Giorgis Georgakoudis 485121d3ad Use proteus::jit_arg instrumentation 2026-03-30 16:55:34 -07:00
Giorgis Georgakoudis af527e27d1 Update top-level CMakeLists.txt for proteus
- Add target-based path for libProteusPass
- Link with libproteus
2026-03-30 16:51:52 -07:00
Julian Andrej 3e680af733 disable derivatives temporarily 2026-03-30 12:41:16 -07:00
Julian Andrej 4212310405 add proteus 2026-03-30 12:20:44 -07:00
Julian Andrej 41aed0e916 cosmetic changes 2026-03-12 08:42:35 -07:00
Julian Andrej 9bf156adf2 bugfix 2026-03-10 09:18:45 -07:00
Julian Andrej 9f0fcd6b10 custom layouts 2026-03-10 08:10:38 -07:00
Julian Andrej 2350a5e9eb typo 2026-03-05 10:52:39 -08:00
Julian Andrej 001c686a19 make rank 0 tensor compatible with real_t 2026-03-05 10:50:33 -08:00
Julian Andrej da9fc85862 support MultiVector 2026-03-04 13:32:19 -08:00
Julian Andrej 996553be3d simplify assert 2026-03-04 12:53:26 -08:00
Julian Andrej ff6715b8b1 Merge branch 'multi-vector-dev' into dfem-multiple-outputs 2026-03-04 12:46:47 -08:00
Julian Andrej 54acbdd395 consistency checks 2026-03-04 10:35:30 -08:00
Julian Andrej 76d4f1942b refactor how bases are created 2026-03-04 07:36:19 -08:00
Julian Andrej 939310203d updates 2026-03-03 15:02:16 -08:00
Julian Andrej 979f08b3eb allow Q-function arguments to be non-const references 2026-03-02 09:14:15 -08:00
Veselin Dobrev 1ed3b48c2e In class MultiVector, remove the need for Memory flag synchronizations
in some cases. This required changes in the internals of the class.

Added some new methods in class MultiVector.
2026-02-26 09:57:21 -08:00
Veselin Dobrev fbd9189e7b Restrist with 'enable_if' the variadic template MultiVector ctor and
MakeRef method to be considered only when the arg types are convertible
to (Vector &).
2026-02-25 19:17:07 -08:00
Veselin Dobrev 1dd889cb16 Add support for constructing and re-constructing MultiVectors to reference
multiple Vectors given as arguments.
2026-02-25 17:44:31 -08:00
Veselin Dobrev 2e8fbd661a Fix a warning in a miniapp. 2026-02-25 14:56:28 -08:00
Veselin Dobrev 6e424dba6e Draft implementation of an array-of-Vectors class where each Vector generally
has a different size and is allocated independently.

The tentative name for the new class is MultiVector.

In class Operator, added new virtual methods Mult() and GetGradient() that
use MultiVectors.
2026-02-25 13:51:43 -08:00
Julian Andrej 60a04e4e4f qdata L to Q 2026-02-23 17:22:52 -08:00
Julian Andrej 8d95a6e5ca qdata 2026-02-23 16:51:58 -08:00
Julian Andrej 10cb466fb2 more stuff 2026-02-23 14:14:41 -08:00
Julian Andrej 5be9de7e95 bugfixes 2026-02-23 09:15:17 -08:00
Julian Andrej a13a4f4d8b more 2026-02-20 14:14:10 -08:00
Julian Andrej bf9b6f4d83 multiple outputs with derivatives 2026-02-19 12:51:30 -08:00
Julian Andrej 52b8703b78 bugs 2026-02-06 16:26:43 -08:00
Julian Andrej 69e7820d01 phew 2026-02-06 15:05:03 -08:00
Julian Andrej dbedeecece more refactor 2026-02-05 09:58:24 -08:00
Julian Andrej e8847b80a2 refactor 2026-02-04 13:25:00 -08:00
Julian Andrej 0fe2aece0b enable multiple outputs 2026-01-26 14:58:58 -08:00
camierjs b639394d56 Cleanup 2025-07-10 08:26:00 -07:00
camierjs 12c2d71bd2 Merge branch 'master' into dfem-kernels 2025-07-10 08:25:18 -07:00
camierjs c6f7e9f635 Switched VDIM/DIM dimensions runs 2025-07-07 13:56:11 -07:00
camierjs 7151713d9c Try StiffnessMult with VDIM/DIM layout 2025-07-07 10:10:06 -07:00
camierjs a5379de077 Use constants, simplify & cleanup 2025-07-06 16:29:53 -07:00
camierjs 85d24f1354 Merge branch 'master' into dfem-kernels 2025-07-06 14:45:43 -07:00
camierjs b60a76f0db Cleanup 2025-07-06 14:45:06 -07:00
camierjs e0dc7659fb With specializations 2025-07-06 11:56:22 -07:00
camierjs b7b4268138 use_kernels_specialization 2025-07-05 21:11:17 -07:00
camierjs e89a61399c GPU runs 2025-07-05 15:57:31 -07:00
camierjs 6c90686880 Forced inlines w/o changes 2025-07-05 15:12:09 -07:00
camierjs 331a66c027 Cleanup 2025-07-05 14:51:59 -07:00
camierjs 6d509fa5c3 Cleanup 2025-07-05 14:40:23 -07:00
camierjs b62cf39359 Simplify 2025-07-05 14:34:58 -07:00
camierjs 49f44e65c0 Cleanup & Simplify 2025-07-05 12:04:54 -07:00
camierjs 32697fea42 cleanup 2025-07-05 11:37:11 -07:00
camierjs c80a091f56 w/o unpack_shmem 2025-07-05 10:56:43 -07:00
camierjs bbc6708976 with r2 2025-07-05 10:46:52 -07:00
camierjs 6a01f6551a Action 2025-07-05 10:17:05 -07:00
camierjs 0c663a8aa2 with process_qf_result 2025-07-05 08:28:27 -07:00
camierjs be224ed94a wip apply_kernel 2025-07-05 08:17:17 -07:00
camierjs c09da71078 wip back action_callback_new 2025-07-04 18:07:15 -07:00
camierjs cf5f0126ce Avoid double mdofs in first iteration 2025-07-04 17:15:46 -07:00
camierjs a23e8907d7 removed fqp and use directly r0 2025-07-04 17:03:04 -07:00
camierjs 4ca2805303 map_quadrature_data_to_fields 2025-07-04 14:14:08 -07:00
camierjs 1b775faa43 is_gradient_fop 2025-07-04 13:38:00 -07:00
camierjs 0edefaeae5 action_callback_new cleanup 2025-07-04 12:50:59 -07:00
camierjs a12132ccb6 MFEM_NEW_KERNELS & action_callback_new 2025-07-04 11:30:30 -07:00
camierjs 9f044d89b5 bench_dfem run with assert Grad diff 2025-07-04 09:51:07 -07:00
camierjs 191e3df84b LoadDofs3d, Grad3d 2025-07-04 09:45:34 -07:00
camierjs 6b6f8afdac wip sync 2025-07-04 09:11:58 -07:00
camierjs 25e333dbf4 Merge branch 'dfem-bench' 2025-07-04 08:12:03 -07:00
camierjs 856d13e9ff Roctx init 2025-07-04 08:00:50 -07:00
camierjs eb8f7f433c Run tests/unit/dfem/test_diffusion_q1d 2025-07-02 17:00:41 -07:00
camierjs 6a693a818f wip merge fix 2025-07-02 15:05:47 -07:00
camierjs fa7d81095a Merge branch 'master' into dfem-kernels 2025-07-02 15:05:30 -07:00
camierjs 16260082f6 wip interpolate 2025-07-02 14:52:18 -07:00
camierjs 7763785ed7 Use MFEM_FOREACH_THREAD_DIRECT 2025-07-02 10:29:40 -07:00
camierjs 2baa889917 Merge branch 'master' into dfem-bench 2025-07-02 08:38:17 -07:00
camierjs 2ed1a9eaad BP3/1/6/25 @ 40 MDof/s 2025-06-30 18:02:56 -07:00
camierjs 1545f03a94 Merge branch 'master' into dfem-bench 2025-06-30 16:19:53 -07:00
camierjs 59a5c9fc79 Sync with fem/kernels.hpp, still performance wip 2025-06-24 11:43:05 -07:00
camierjs c389a3c434 Use latest dFEM for benchmark 2025-06-24 11:27:34 -07:00
camierjs ec96a85f86 Merge branch 'master' into dfem-bench 2025-06-24 11:27:15 -07:00
camierjs dd99371cda dFEM diffusion test Identity vs. None fix 2025-05-19 16:48:56 -07:00
camierjs 90aa6fc544 Merge branch 'dfem-phase1-dev' into dfem-kernels 2025-05-19 16:44:02 -07:00
camierjs 7b4fcc3e52 Add qp wip header/test 2025-05-19 16:42:15 -07:00
camierjs 81b6b7eeb2 Merge branch 'master'/'dfem-phase-1' into dfem-bench 2025-05-19 16:02:46 -07:00
camierjs 4b5974f600 Fix CMake and dFEM bench 2025-05-19 16:01:16 -07:00
camierjs a6926f4ce6 Merge branch 'dfem-phase1-dev' 2025-05-19 15:54:58 -07:00
camierjs b6e972af79 Merge branch 'master' 2025-05-19 15:49:54 -07:00
Julian Andrej e76ec19775 restructure 2025-05-19 14:46:20 -07:00
Julian Andrej d797322fea path 2025-05-19 08:22:36 -07:00
Julian AndrejandJohn Camier 5a5e34a744 Update fem/dfem/doperator.hpp
Co-authored-by: John Camier <camierjs@gmail.com>
2025-05-19 08:11:13 -07:00
Julian AndrejandJohn Camier 93db7052ff Update fem/dfem/util.hpp
Co-authored-by: John Camier <camierjs@gmail.com>
2025-05-19 08:10:44 -07:00
Julian AndrejandJohn Camier f7170af7bd Update fem/dfem/tuple.hpp
Co-authored-by: John Camier <camierjs@gmail.com>
2025-05-19 08:10:12 -07:00
Julian AndrejandJohn Camier b78eef3eaa Update fem/dfem/tuple.hpp
Co-authored-by: John Camier <camierjs@gmail.com>
2025-05-19 08:10:00 -07:00
Julian Andrej d5decea85c Revert "change default location for enzyme and add instructions"
This reverts commit dea3ae3317.
2025-05-16 12:59:14 -07:00
Julian Andrej dea3ae3317 change default location for enzyme and add instructions 2025-05-16 12:55:04 -07:00
Julian Andrej 33c1e50235 astyle 2025-05-16 12:39:47 -07:00
Julian Andrej 5718ad1b53 cuda compat 2025-05-16 12:38:08 -07:00
Julian AndrejandAndrew Ho 4f3671e253 Update fem/dfem/util.hpp
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:58:50 -07:00
Julian AndrejandAndrew Ho 4e08bb1b66 Update fem/dfem/util.hpp
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:58:42 -07:00
Julian AndrejandAndrew Ho 69c5016b63 Update fem/dfem/util.hpp
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:58:20 -07:00
Julian AndrejandAndrew Ho ce1bf58dc0 Update fem/dfem/doperator.hpp
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:58:13 -07:00
Julian AndrejandAndrew Ho 5eb00c9ee6 Update fem/dfem/doperator.hpp
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:58:05 -07:00
Julian AndrejandAndrew Ho 1f3b6b95aa Update fem/dfem/util.hpp
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:57:58 -07:00
Julian AndrejandAndrew Ho 118db41049 Update fem/dfem/util.hpp
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:57:49 -07:00
Julian AndrejandAndrew Ho 8390c3e50b Update fem/dfem/doperator.hpp
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:57:40 -07:00
Julian Andrej 168b5179e6 remove findenzyme module 2025-05-16 11:57:11 -07:00
Julian AndrejandAndrew Ho 7697f6d400 Update CMakeLists.txt
Co-authored-by: Andrew Ho <ho37@llnl.gov>
2025-05-16 11:56:11 -07:00
Julian AndrejandJan Nikl 235ebce5d5 Update examples/dfem/minimal_surface.cpp
Co-authored-by: Jan Nikl <nikl1@llnl.gov>
2025-05-16 07:58:47 -07:00
Julian AndrejandJan Nikl e8a09d6499 Update examples/dfem/minimal_surface.cpp
Co-authored-by: Jan Nikl <nikl1@llnl.gov>
2025-05-16 07:57:47 -07:00
Julian AndrejandJan Nikl edc67827d8 Update examples/dfem/minimal_surface.cpp
Co-authored-by: Jan Nikl <nikl1@llnl.gov>
2025-05-16 07:57:34 -07:00
Julian Andrej 9e5cdef2ef add host device 2025-05-14 17:39:40 -07:00
Andrew Ho c2f4a5e248 Updated makefile to work with clang as the cuda compiler 2025-05-14 11:25:59 -07:00
Julian Andrej 72d811b289 device support for fdjacobian 2025-05-14 09:57:18 -07:00
Julian Andrej 43731aa990 memory type for temporary 2025-05-14 09:46:39 -07:00
Julian Andrej 45f59fff3a device memory locations 2025-05-14 09:23:58 -07:00
Julian Andrej 58a4cfa132 cuda compat 2025-05-14 07:43:13 -07:00
Julian Andrej 333dd3f2fd rename ParametricSpace -> ParameterSpace 2025-05-13 13:29:38 -07:00
Julian Andrej c4f7dd77b1 bugs 2025-05-13 13:24:18 -07:00
Julian Andrej f442b83573 whitespace 2025-05-13 11:33:54 -07:00
Julian Andrej 768aaae25d docs 2025-05-13 11:27:36 -07:00
Julian Andrej eab997c557 typo 2025-05-13 11:26:09 -07:00
Julian Andrej 9d73dc487d docs 2025-05-13 11:24:36 -07:00
Julian Andrej 2575ac61ba more comments 2025-05-13 09:08:54 -07:00
Julian Andrej 6130144da1 comments 2025-05-13 08:46:15 -07:00
camierjs 68db31da44 SetMaxOf comments 2025-05-12 18:15:09 -07:00
Julian Andrej 1acbce733c cmake 2025-05-09 11:46:10 -07:00
Julian Andrej b44316049b cmake 2025-05-09 10:41:11 -07:00
Julian Andrej 2e133e8ecb remove serial tests from cmake 2025-05-09 10:36:37 -07:00
Julian Andrej dfb2f4d7f2 typos 2025-05-09 08:41:17 -07:00
Julian Andrej 10e9e4215f cmake 2025-05-09 08:38:46 -07:00
Julian Andrej 8125a211d3 Merge branch 'master' into dfem-phase1-dev 2025-05-08 09:40:48 -07:00
Julian Andrej 818b8db433 switch example to CG 2025-05-07 17:19:36 -07:00
Julian Andrej ad4626edfc leftover comment 2025-05-07 15:51:29 -07:00
Julian Andrej 8d7e8933cf mesh 2025-05-07 15:50:43 -07:00
Julian Andrej 3ad21a409f precision 2025-05-07 15:31:39 -07:00
Julian Andrej b16b550150 corrections 2025-05-07 15:08:11 -07:00
Julian Andrej 102dc8bd02 ifdef 2025-05-07 14:20:41 -07:00
Julian Andrej e306ba0c85 ifdef 2025-05-07 13:44:30 -07:00
Julian Andrej 4b88ad2b0a more minsurface 2025-05-07 13:19:28 -07:00
Julian Andrej d0fb4e342e example draft 2025-05-06 21:06:56 -07:00
Julian Andrej b53d0529db bug 2025-05-06 17:41:48 -07:00
Julian Andrej 8c7988b525 changes 2025-05-06 17:41:22 -07:00
Julian Andrej dfffe4b5e8 rename fops 2025-05-06 09:12:33 -07:00
Julian Andrej 538aa11904 rename fops 2025-05-06 08:56:20 -07:00
Julian Andrej 6fa978af9a rename fops 2025-05-06 08:53:31 -07:00
Julian Andrej 3f81af72f6 rename fops 2025-05-06 08:51:06 -07:00
Julian Andrej 97f1cf08fb docs 2025-05-05 13:45:03 -07:00
camierjs e047cec18a All 3 MQ1Settings working 2025-05-03 14:30:34 -07:00
camierjs bdcf59d109 make_qf_map 2025-05-03 13:48:03 -07:00
Tzanio Kolev d3f1379dc8 Merge branch 'master' into dfem-phase1-dev 2025-05-03 13:46:43 -07:00
camierjs b876d32452 Pre cleanup MQ1 on qfunction 2025-05-03 13:06:34 -07:00
camierjs 50a6be3d58 wip runtime_get 2025-05-03 10:49:25 -07:00
camierjs 5251db2278 All interpolate gradient tests 2025-05-02 17:31:40 -07:00
camierjs 92fca7cf01 Interpolate all Gradient but toroid mesh 2025-05-02 17:27:54 -07:00
camierjs e22f5bc048 Interpolate Gradient AlmostEq 2025-05-02 17:17:22 -07:00
camierjs f971d1e0bb Merge branch 'dfem-phase1-dev' 2025-05-02 15:29:05 -07:00
camierjs 752917acaa Rename Diffusion PA kernels
dFEM DOperator debug traces
2025-05-02 15:27:59 -07:00
Julian Andrej ea6fb52698 bug 2025-05-02 13:09:47 -07:00
Julian Andrej 07a87e369c style 2025-05-02 12:01:08 -07:00
camierjs 56c46e6da5 Remove MFEM_FOREACH_THREAD1 2025-05-02 11:23:27 -07:00
camierjs 2db4ca1300 Avoid MFEM recompilation with dFEM changes 2025-05-02 11:19:48 -07:00
camierjs 53bc415268 Squashed commit of the following:
commit be537728df
Merge: 8cc9eec53 4e5b98b10
Author: camierjs <camierjs@gmail.com>
Date:   Fri May 2 10:37:34 2025 -0700

    Merge branch 'dfem-phase1-dev' into dfem-bench

commit 4e5b98b10f
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 10:05:20 2025 -0700

    doc

commit d4acd906bf
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 09:01:51 2025 -0700

    update brew before enzyme install

commit d751ce66a3
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 08:43:46 2025 -0700

    ci

commit 3f0abd4dfd
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 08:40:45 2025 -0700

    ci

commit 44a423d804
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 08:39:58 2025 -0700

    ci

commit 3e61e0490e
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 08:38:47 2025 -0700

    ci

commit def4919592
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 08:33:33 2025 -0700

    ci config

commit 2d147d70e0
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 08:33:29 2025 -0700

    reintroduce tests

commit e29e64dffe
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Fri May 2 08:04:44 2025 -0700

    reintroduce macos fp64 ci target

commit a7ec259bd5
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Thu May 1 16:46:22 2025 -0700

    reintroduce macos fp64 ci target

commit 3e93e19767
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Thu May 1 14:41:25 2025 -0700

    enzyme bug notes

commit 532b065596
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Thu May 1 14:41:17 2025 -0700

    consistency

commit 82c1e2315b
Author: Julian Andrej <andrej1@llnl.gov>
Date:   Thu May 1 13:19:25 2025 -0700

    modernize

commit 8cc9eec535
Author: camierjs <camierjs@gmail.com>
Date:   Thu May 1 11:06:03 2025 -0700

    Remove unused code

commit dece65be31
Author: camierjs <camierjs@gmail.com>
Date:   Thu May 1 10:59:50 2025 -0700

    Header and style

commit 3e6d29b3dd
Author: camierjs <camierjs@gmail.com>
Date:   Thu May 1 10:53:12 2025 -0700

    Meld toward dfem

commit 487135b497
Author: camierjs <camierjs@gmail.com>
Date:   Thu May 1 10:48:02 2025 -0700

    Meld back toward dfem dev

commit 91f648aa95
Author: camierjs <camierjs@gmail.com>
Date:   Thu May 1 10:36:43 2025 -0700

    Remove examples leftovers

commit 999931ded2
Author: camierjs <camierjs@gmail.com>
Date:   Thu May 1 10:36:18 2025 -0700

    Sync dfem bench
2025-05-02 10:40:23 -07:00
camierjs be537728df Merge branch 'dfem-phase1-dev' into dfem-bench 2025-05-02 10:37:34 -07:00
Julian Andrej 4e5b98b10f doc 2025-05-02 10:05:20 -07:00
Julian Andrej d4acd906bf update brew before enzyme install 2025-05-02 09:01:51 -07:00
Julian Andrej d751ce66a3 ci 2025-05-02 08:43:46 -07:00
Julian Andrej 3f0abd4dfd ci 2025-05-02 08:40:45 -07:00
Julian Andrej 44a423d804 ci 2025-05-02 08:39:58 -07:00
Julian Andrej 3e61e0490e ci 2025-05-02 08:38:47 -07:00
Julian Andrej def4919592 ci config 2025-05-02 08:33:33 -07:00
Julian Andrej 2d147d70e0 reintroduce tests 2025-05-02 08:33:29 -07:00
Julian Andrej e29e64dffe reintroduce macos fp64 ci target 2025-05-02 08:04:44 -07:00
Julian Andrej a7ec259bd5 reintroduce macos fp64 ci target 2025-05-01 16:46:22 -07:00
Julian Andrej 3e93e19767 enzyme bug notes 2025-05-01 14:41:25 -07:00
Julian Andrej 532b065596 consistency 2025-05-01 14:41:17 -07:00
Julian Andrej 82c1e2315b modernize 2025-05-01 13:19:25 -07:00
camierjs 8cc9eec535 Remove unused code 2025-05-01 11:06:03 -07:00
camierjs dece65be31 Header and style 2025-05-01 10:59:50 -07:00
camierjs 3e6d29b3dd Meld toward dfem 2025-05-01 10:53:12 -07:00
camierjs 487135b497 Meld back toward dfem dev 2025-05-01 10:48:02 -07:00
camierjs 91f648aa95 Remove examples leftovers 2025-05-01 10:36:43 -07:00
camierjs 999931ded2 Sync dfem bench 2025-05-01 10:36:18 -07:00
camierjs 15dbcae725 Add version info 2025-05-01 10:26:49 -07:00
camierjs 01efb623da Update kernels pa to regs use 2025-05-01 10:08:01 -07:00
camierjs f854c5262d Move kernels pa to dfem regs 2025-05-01 10:07:46 -07:00
camierjs a91b754aaa Update dfem examples 2025-05-01 10:06:06 -07:00
camierjs b1623ff3d4 Sync dfem examples with latest changes 2025-05-01 10:05:54 -07:00
camierjs c2426ca45a Merge branch 'dfem-phase1-dev' 2025-05-01 09:35:22 -07:00
camierjs 276f419a3d Merge branch 'dfem-phase1-dev' of github.com:mfem/mfem into dfem-phase1-dev 2025-05-01 09:31:53 -07:00
Julian Andrej c91b8bea01 prevent possible indexing error 2025-05-01 08:42:56 -07:00
Veselin Dobrev 7bdceca6ce Windows CI debug 2025-05-01 04:48:28 -07:00
Veselin Dobrev 6f9a263435 Disable Ninja on windows -- it does not detect MSVC.
Add a debug action step to print the environment under windows.
2025-05-01 03:49:52 -07:00
Veselin Dobrev 28a7865ed1 Fix MSVC build issue.
Use the Ninja CMake generator on Windows to try to speedup the build.
2025-05-01 00:44:37 -07:00
Julian Andrej 6e7335ac52 Revert "test more captures"
This reverts commit 8115383dec.
2025-04-30 16:49:35 -07:00
Julian Andrej 8115383dec test more captures 2025-04-30 16:34:17 -07:00
Julian Andrej 3d1b017a60 Revert "test capture"
This reverts commit bf14e5b018.
2025-04-30 16:29:16 -07:00
Julian Andrej bf14e5b018 test capture 2025-04-30 16:12:23 -07:00
Julian Andrej fb3517453f correctness 2025-04-30 15:48:35 -07:00
Julian Andrej bfca6beb28 Revert "hints for mscv"
This reverts commit 78a60cc1d9.
2025-04-30 14:30:02 -07:00
Julian Andrej f51e46d3d8 changelog 2025-04-30 14:02:28 -07:00
Julian Andrej 78a60cc1d9 hints for mscv 2025-04-30 14:02:24 -07:00
Julian Andrej 935d3a9e42 namespaces 2025-04-30 10:30:44 -07:00
Julian Andrej 35866f8485 namespaces 2025-04-30 09:40:45 -07:00
Julian Andrej 6b4b644355 namespaces 2025-04-30 09:38:52 -07:00
Julian Andrej b9ec58e7a1 guards 2025-04-30 09:19:17 -07:00
Julian Andrej 4644aed322 native ad test 2025-04-30 09:18:22 -07:00
Julian Andrej 80da896859 namespaces 2025-04-30 09:18:16 -07:00
Julian Andrej b96dcb4401 namespaces 2025-04-30 08:54:57 -07:00
Julian Andrej 5054f1784d again 2025-04-29 14:13:51 -07:00
Julian Andrej 788c0efda0 sync input values 2025-04-29 14:10:42 -07:00
Julian Andrej 4d49d42702 typo 2025-04-29 13:33:16 -07:00
Julian Andrej f5192230e0 more msvc handholding 2025-04-29 11:22:24 -07:00
Tzanio Kolev 400e3eca7d Merge branch 'master' into dfem-phase1-dev 2025-04-29 09:55:18 -07:00
Julian Andrej b90c8d80fe remove problematic constexpr 2025-04-29 08:50:20 -07:00
Veselin Dobrev a0491f6bfc Fix some msvc warnings which also fixed some compilation errors 2025-04-29 01:43:41 -07:00
Julian Andrej a8df54cf5d please msvc 2025-04-28 20:39:29 -07:00
Julian Andrej 9c4e43ee12 revert 2025-04-28 19:29:23 -07:00
Julian Andrej 7a1887c525 oops 2025-04-28 17:58:40 -07:00
Julian Andrej 907783f9ca testing 2025-04-28 17:56:23 -07:00
Julian Andrej f4f68fa021 size 2025-04-28 17:08:04 -07:00
Julian Andrej b76e9e80a7 real annoying real_t 2025-04-28 17:03:59 -07:00
Julian Andrej b8f677b2fe shadows 2025-04-28 16:59:44 -07:00
Julian Andrej 6e42fbae4d guards 2025-04-28 16:51:34 -07:00
Julian Andrej b8c0008061 include orders etc 2025-04-28 16:36:44 -07:00
Julian Andrej cdce090c2a cmake 2025-04-28 15:48:25 -07:00
Julian Andrej e246c0852b c++17 2025-04-28 15:40:50 -07:00
Julian Andrej 4db86286ee unguard test 2025-04-28 14:05:55 -07:00
Julian Andrej 9308946715 guards 2025-04-28 14:05:44 -07:00
Julian Andrej d28eca6b7f renaming 2025-04-28 14:05:34 -07:00
Julian Andrej 8e26105232 temporary disable offended unit tests 2025-04-28 11:53:12 -07:00
Julian Andrej c674f9f7ad defuse test 2025-04-24 15:29:09 -07:00
Julian Andrej 537d30120a Merge branch 'master' into dfem-phase1-dev 2025-04-24 14:35:23 -07:00
Julian Andrej 519267e1cb paths 2025-04-24 14:02:09 -07:00
Julian Andrej 2c495fb70d shadow warnings 2025-04-24 13:36:05 -07:00
Julian Andrej 401d1aec7b ci 2025-04-24 13:02:10 -07:00
Julian Andrej 8299b1c036 ci 2025-04-24 12:52:18 -07:00
Julian Andrej 9dd1e4dbdb ci 2025-04-24 11:48:17 -07:00
Julian Andrej 47a3534eff ci 2025-04-24 11:44:08 -07:00
Julian Andrej 2f39ff66f3 ci 2025-04-24 11:34:36 -07:00
Julian Andrej ddca183704 ci 2025-04-24 11:20:31 -07:00
Julian Andrej 078ce6130c ci 2025-04-24 11:17:54 -07:00
Julian Andrej 6d15c2a156 ci 2025-04-24 11:11:54 -07:00
Julian Andrej 7d705c0677 ci 2025-04-24 11:09:42 -07:00
Julian Andrej c027328b91 ci 2025-04-24 11:07:27 -07:00
Julian Andrej 65cb67e1c1 ci 2025-04-24 11:04:54 -07:00
Julian Andrej 494f27c14c ci 2025-04-24 11:00:37 -07:00
Julian Andrej 3c02b72084 ci 2025-04-24 10:51:20 -07:00
Julian Andrej 75e2be35ba ci 2025-04-24 10:46:12 -07:00
Julian Andrej b0f9cbfd26 ci 2025-04-24 10:42:24 -07:00
Julian Andrej 6afea18cde ci 2025-04-24 10:39:14 -07:00
Julian Andrej 2e69ff4b97 ci 2025-04-24 10:33:08 -07:00
Julian Andrej 6c70fe9334 ci 2025-04-24 10:27:46 -07:00
Julian Andrej 4ccbd4581e ci 2025-04-24 10:19:07 -07:00
Julian Andrej 6e262f6c3f ci 2025-04-24 10:13:37 -07:00
Julian Andrej 52e10475a5 ci 2025-04-24 10:10:20 -07:00
Julian Andrej c0299a5a4b ci 2025-04-24 10:05:24 -07:00
Julian Andrej 06eecb0dce yaml lint and first enzyme ci entries 2025-04-24 10:01:07 -07:00
Julian Andrej 96261a7742 c++17 and experimental namespace 2025-04-23 18:09:12 -07:00
Julian Andrej d7c479fa1e documentation 2025-04-21 09:21:07 -07:00
Julian Andrej 8b01d8f13b std::cout -> mfem::out 2025-04-16 10:53:23 -07:00
Julian Andrej 710da275c8 add dfem folder to makefile 2025-04-16 09:02:57 -07:00
Julian Andrej fd481eb725 correct include orders 2025-04-16 09:02:43 -07:00
Julian Andrej b5bbdbbed5 vectorfe leftover 2025-04-16 09:02:31 -07:00
Julian Andrej 6a26200314 remove vectorfe crumbs 2025-04-15 11:31:14 -07:00
Julian Andrej a485121526 msvc ambiguity enable_if 2025-04-14 14:40:52 -07:00
Julian Andrej 1f9e1cf175 brackets 2025-04-14 14:03:28 -07:00
Julian Andrej ec402882da move guard 2025-04-14 13:58:10 -07:00
Julian Andrej e7633e0e2c guard tests 2025-04-14 13:49:46 -07:00
Julian Andrej 30aeb465b7 includes 2025-04-14 13:27:15 -07:00
Julian Andrej ff4993fc51 array include 2025-04-14 13:13:44 -07:00
Julian Andrej 0a42ea8021 copyright dates 2025-04-14 13:13:33 -07:00
Julian Andrej b8d024b59b remove example subdirectory 2025-04-14 10:51:57 -07:00
Julian Andrejandcamierjs 9e1ccf4543 phase 1 skeleton
Co-authored-by: camierjs <camierjs@gmail.com>
2025-04-14 09:43:43 -07:00
camierjs 075ebb255d Do one first benchmark 2025-04-09 11:35:40 -07:00
camierjs 3eb6a5b3b2 Merge branch 'dfem-phase1-dev' 2025-04-03 14:01:28 -07:00
Julian Andrej 8ba1f17f72 add nonlinear solver options to command line arguments 2025-04-03 11:01:31 -07:00
Julian Andrej e5f5a79e66 attempt to fix parametric function transfers 2025-04-03 08:19:53 -07:00
Julian Andrej 43f1b19767 switch to 2d by default 2025-04-03 08:19:32 -07:00
Julian Andrej 7bebe4528f stop printing dependency maps 2025-04-03 08:19:16 -07:00
camierjs da63657cdd GCC warning fixes 2025-04-02 18:40:57 -07:00
camierjs 2b1d271888 Merge branch 'dfem-phase1-dev' 2025-04-02 18:34:46 -07:00
camierjs 47fb8a4fda No auto for gcc 2025-04-02 18:34:30 -07:00
camierjs ee7d9726df Warnings & fixes 2025-04-02 18:34:08 -07:00
camierjs 44b560a916 Merge branch 'dfem-phase1-dev' 2025-04-02 17:56:37 -07:00
Julian Andrej 5657f6ebe8 Merge branch 'dfem-phase1-dev' of github.com:mfem/mfem into dfem-phase1-dev 2025-04-02 17:44:22 -07:00
Julian Andrej 19543b6b16 more device stuff 2025-04-02 17:41:57 -07:00
camierjs 94a832a0c6 Merge branch 'dfem-phase1-dev' 2025-04-02 17:21:09 -07:00
camierjs b56e994ecd Copyright header, includes trim & warning fixes 2025-04-02 17:20:29 -07:00
camierjs 8be11cdfdb Remove duplicate inline 2025-04-02 16:49:20 -07:00
camierjs d71a9602b5 Merge branch 'dfem-phase1-dev' 2025-04-02 16:41:06 -07:00
camierjs 1108bb7e85 Use SetMaxOf inside kernel 2025-04-02 16:40:38 -07:00
Julian Andrej ae8e5aa88d some device stuff 2025-04-02 16:21:18 -07:00
camierjs 17f4acf6b1 Merge branch 'main' of github.com:camierjs/mfem-dfem-bench into main 2025-04-02 16:03:32 -07:00
camierjs 2ce3f3037c Cleanup 2025-04-02 16:03:30 -07:00
camierjs 29189a6d4a Merge branch 'dfem-phase1-dev' 2025-04-02 16:02:10 -07:00
Julian Andrej 08f3c86b8a make attributes device compatible 2025-04-02 15:46:19 -07:00
camierjs c6eb171b5b Back to foreach treads 2025-04-02 14:33:42 -07:00
camierjs d26695cd2a Use latest AddDomainIntegrator API 2025-04-02 12:18:21 -07:00
camierjs 01ab390b06 Merge branch 'dfem-phase1-dev' 2025-04-02 12:09:57 -07:00
camierjs 43c42295d3 Few changes with clang 20.1 2025-04-02 12:09:36 -07:00
camierjs 52bc915120 Few fixes to run on device and removed warnings 2025-04-02 12:07:57 -07:00
camierjs cd9cabb955 Cleanup all hipGetLastError 2025-04-02 09:32:36 -07:00
Julian Andrej e66a61c198 add build instructions 2025-03-31 17:23:33 -07:00
Julian Andrej f8b3c78b19 tensor additions 2025-03-31 14:28:29 -07:00
Julian Andrej 4749746171 add laghos 2025-03-31 14:28:10 -07:00
camierjs 1ddd01c2a0 All dfem BP3 versions 2025-03-31 13:47:06 -07:00
camierjs 87ec3850b5 Update Diffusion class 2025-03-30 13:08:31 -07:00
camierjs 1b25a61c9e Re-order kpc benchmarks 2025-03-30 11:52:41 -07:00
camierjs 7bee8e8161 tests/benchmarks/bench_dfem 2025-03-30 11:40:41 -07:00
camierjs a545ff8264 Bring StiffnessIntegrator in bench dfem 2025-03-30 10:29:17 -07:00
camierjs 5352234aef Use SetMaxOf 2025-03-30 10:06:54 -07:00
camierjs c3732f9d86 dfem diffusion3d D1D Q1D tests 2025-03-30 09:46:08 -07:00
camierjs b95f3809fe ParametricSpace d1d/q1d 2025-03-28 17:24:29 -07:00
camierjs e6a28b7753 Merge branch 'dfem-phase1-dev' 2025-03-28 15:09:46 -07:00
camierjs 62adea8b46 WIP dfem diffusion 2025-03-28 15:09:22 -07:00
camierjs 7d11db33c0 Add dfem diffusion multi-version example and bench dfem setup 2025-03-28 12:09:52 -07:00
Julian Andrej 11fce4235b revert width determination 2025-03-28 08:16:26 -07:00
camierjs f500b4875f dfem bench check 2025-03-27 10:48:46 -07:00
camierjs ba212c583e bench dfem init with nvtx 2025-03-27 10:34:12 -07:00
Julian Andrej fd341e07da example 2025-03-21 15:54:58 -07:00
Julian Andrej d59e2a229c phase 1 skeleton 2025-03-21 15:54:18 -07:00
67 changed files with 8563 additions and 1057 deletions
+2 -2
View File
@@ -451,8 +451,8 @@ miniapps/plasma/pic/*.csv
tests/unit/output_meshes
tests/unit/unit_tests
tests/unit/punit_tests
tests/unit/gpu_unit_tests
tests/unit/pgpu_unit_tests
tests/unit/cunit_tests
tests/unit/pcunit_tests
tests/unit/sedov_tests_*
tests/unit/psedov_tests_*
tests/unit/tmop_pa_tests_*
+17
View File
@@ -592,6 +592,13 @@ if (MFEM_USE_ENZYME)
set(ENZYME_INCLUDE_DIRS ${ENZYME_DIR}/include)
endif()
if (MFEM_USE_PROTEUS)
enable_language(C)
find_package(proteus REQUIRED PATHS "${PROTEUS_DIR}")
message(STATUS "${PROTEUS_DIR}/include")
include_directories("${PROTEUS_DIR}/include")
endif()
# MFEM_TIMER_TYPE
if (NOT DEFINED MFEM_TIMER_TYPE)
if (APPLE)
@@ -728,6 +735,16 @@ mfem_add_library(mfem ${SOURCES} ${HEADERS} ${MASTER_HEADERS})
target_compile_features(mfem PUBLIC cxx_std_${CMAKE_CXX_STANDARD})
# message(STATUS "TPL_LIBRARIES = ${TPL_LIBRARIES}")
target_link_libraries(mfem PUBLIC ${TPL_LIBRARIES} ${TPL_TARGETS})
if (MFEM_USE_PROTEUS)
add_library(ClangProteusFlags INTERFACE IMPORTED)
set_target_properties(ClangProteusFlags PROPERTIES
INTERFACE_COMPILE_OPTIONS "-fpass-plugin=$<TARGET_FILE:ProteusPass>"
)
target_link_libraries(mfem PUBLIC ClangProteusFlags)
target_link_libraries(mfem PUBLIC proteus)
endif()
if (TPL_TARGETS)
add_dependencies(mfem ${TPL_TARGETS})
endif()
+18
View File
@@ -157,4 +157,22 @@ constexpr real_t operator""_r(unsigned long long v)
#endif
#endif // MFEM_USE_MPI not defined
#ifdef NVTX_DBG_HPP
#include NVTX_DBG_HPP
#else
#define db1(...)
#define dbg(...)
#define dbl(...)
#define dba(...)
#define dbc(...)
#define NVTX_MARK_FUNCTION
#define NVTX_MARK_BEGIN(...)
#define NVTX_INI(...)
#define NVTX_END(...)
#define NVTX_MARK_INI(...)
#define NVTX_MARK_END(...)
#define NVTX_MARK(...)
#define NVTX(...)
#endif
#endif // MFEM_CONFIG_HPP
+1
View File
@@ -47,6 +47,7 @@ list(APPEND ALL_EXE_SRCS
ex39.cpp
ex40.cpp
ex41.cpp
jitplayground.cpp
)
if (MFEM_USE_MPI)
+536
View File
@@ -0,0 +1,536 @@
#include <mfem.hpp>
#include "../fem/dfem/util.hpp"
#include <proteus/CppJitModule.h>
#include "jitplayground.hpp"
#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <fstream>
#include <initializer_list>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace util
{
constexpr std::string_view Dirname(std::string_view path)
{
const size_t last_sep = path.find_last_of("/\\");
if (last_sep == std::string_view::npos) { return {}; }
return path.substr(0, last_sep);
}
constexpr std::string_view thisFileDir = Dirname(__FILE__);
}
template <typename T>
static std::string TypeNameString()
{
return std::string(mfem::future::get_type_name<T>());
}
template <typename Tuple, size_t... Is>
static auto ParamTypeStringsImpl(std::index_sequence<Is...>)
{
return std::array<std::string, sizeof...(Is)>
{
TypeNameString<std::remove_reference_t<decltype(mfem::future::get<Is>(std::declval<Tuple&>()))>>()...
};
}
template <typename Tuple>
static auto ParamTypeStrings()
{
return ParamTypeStringsImpl<Tuple>(
std::make_index_sequence<mfem::future::tuple_size<Tuple>::value> {});
}
static std::string_view Trim(std::string_view s)
{
size_t begin = 0;
while (begin < s.size() && std::isspace(static_cast<unsigned char>(s[begin])))
{
++begin;
}
size_t end = s.size();
while (end > begin &&
std::isspace(static_cast<unsigned char>(s[end - 1])))
{
--end;
}
return s.substr(begin, end - begin);
}
static bool IsValidIdentifier(std::string_view s)
{
if (s.empty()) { return false; }
const unsigned char c0 = static_cast<unsigned char>(s[0]);
if (!(std::isalpha(c0) || c0 == '_')) { return false; }
for (size_t i = 1; i < s.size(); ++i)
{
const unsigned char c = static_cast<unsigned char>(s[i]);
if (!(std::isalnum(c) || c == '_')) { return false; }
}
return true;
}
static bool ParseJitDirective(std::string_view line,
std::string &type,
std::string &var,
std::string &kind)
{
const size_t jit_pos = line.find("$JIT");
if (jit_pos == std::string_view::npos) { return false; }
const size_t open = line.find('[', jit_pos);
const size_t close = line.find(']', jit_pos);
MFEM_VERIFY(open != std::string_view::npos &&
close != std::string_view::npos &&
close > open,
"malformed $JIT directive (expected brackets): " << line);
const std::string_view payload = line.substr(open + 1, close - open - 1);
const size_t comma1 = payload.find(',');
const size_t comma2 = (comma1 == std::string_view::npos)
? std::string_view::npos
: payload.find(',', comma1 + 1);
MFEM_VERIFY(comma1 != std::string_view::npos &&
comma2 != std::string_view::npos,
"malformed $JIT directive (expected 3 comma-separated fields): "
<< line);
const std::string_view f0 = Trim(payload.substr(0, comma1));
const std::string_view f1 = Trim(payload.substr(comma1 + 1,
comma2 - comma1 - 1));
const std::string_view f2 = Trim(payload.substr(comma2 + 1));
MFEM_VERIFY(!f0.empty() && !f1.empty() && !f2.empty(),
"malformed $JIT directive (empty field): " << line);
type.assign(f0);
var.assign(f1);
kind.assign(f2);
return true;
}
static std::string ReadFileOrEmpty(const std::string &fn)
{
std::ifstream file(fn);
if (!file.is_open())
{
std::cerr << "could not open file " << fn << "\n";
return {};
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
static std::vector<std::string> ExtractJitVarNames(const std::string
&kernel_code)
{
std::stringstream ss(kernel_code);
std::string line;
std::vector<std::string> var_names;
std::unordered_set<std::string> seen_vars;
while (std::getline(ss, line))
{
std::string type, var, kind;
if (ParseJitDirective(line, type, var, kind))
{
MFEM_VERIFY(IsValidIdentifier(var),
"$JIT variable must be a valid identifier: " << var);
MFEM_VERIFY(seen_vars.insert(var).second,
"duplicate $JIT variable name: " << var);
var_names.push_back(var);
}
}
return var_names;
}
static std::string RewriteKernelForJit(std::string kernel_code,
const std::vector<std::string> &jit_values)
{
std::stringstream ss(kernel_code);
std::string line;
std::string out;
out.reserve(kernel_code.size() + 128);
bool have_pending = false;
size_t pending_index = 0;
std::string pending_type;
std::string pending_var;
std::unordered_set<std::string> seen_vars;
while (std::getline(ss, line))
{
line.push_back('\n');
if (have_pending)
{
MFEM_VERIFY(pending_index < jit_values.size(),
"not enough JIT values provided");
const size_t indent_end = line.find_first_not_of(" \t");
const std::string indent =
(indent_end == std::string::npos) ? std::string() :
line.substr(0, indent_end);
out += indent + "const " + pending_type + " " + pending_var + " = " +
jit_values[pending_index] + ";\n";
have_pending = false;
++pending_index;
continue;
}
std::string type, var, kind;
if (ParseJitDirective(line, type, var, kind))
{
MFEM_VERIFY(IsValidIdentifier(var),
"$JIT variable must be a valid identifier: " << var);
MFEM_VERIFY(kind == "generic",
"unsupported $JIT kind: " << kind);
MFEM_VERIFY(seen_vars.insert(var).second,
"duplicate $JIT variable name: " << var);
pending_type = std::move(type);
pending_var = std::move(var);
have_pending = true;
continue; // drop directive line
}
out += line;
}
MFEM_VERIFY(!have_pending,
"$JIT directive must annotate a following line");
MFEM_VERIFY(jit_values.size() == pending_index,
"JIT value count must match number of $JIT directives");
return out;
}
static std::string GeneratedOutputPath(std::string_view original_path)
{
const size_t last_sep = original_path.find_last_of("/\\");
const size_t dot = original_path.find_last_of('.');
const bool dot_in_filename =
(dot != std::string_view::npos) &&
(last_sep == std::string_view::npos || dot > last_sep);
const std::string_view base =
dot_in_filename ? original_path.substr(0, dot) : original_path;
return std::string(base) + "_generated.hpp";
}
static void WriteFileOrWarn(const std::string &path,
const std::string &contents)
{
std::ofstream out(path);
if (!out.is_open())
{
std::cerr << "could not write generated file " << path << "\n";
return;
}
out << contents;
}
class JitQFunction
{
public:
template <typename ImplT, size_t N>
JitQFunction(ImplT, const std::string &fn,
const std::array<bool, N> &activity_map)
{
using qf_signature = typename
mfem::future::get_function_signature<
decltype(&ImplT::operator())>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
constexpr size_t nparams = mfem::future::tuple_size<qf_param_ts>::value;
static_assert(N == nparams, "activity_map size must match qfunc arity");
this->fn = fn;
this->nparams = nparams;
this->activity_map.reserve(N);
for (size_t i = 0; i < N; ++i)
{
this->activity_map.push_back(activity_map[i]);
}
{
const auto param_types_arr = ParamTypeStrings<qf_param_ts>();
this->param_types.assign(param_types_arr.begin(), param_types_arr.end());
}
this->return_type = TypeNameString<typename qf_signature::return_t>();
this->return_is_void = std::is_same_v<typename qf_signature::return_t, void>;
this->impl_type_name = TypeNameString<ImplT>();
this->jit_var_names = ExtractJitVarNames(ReadFileOrEmpty(fn));
}
template <typename ReturnT, typename... Args>
ReturnT run(std::string_view name,
std::initializer_list<std::pair<std::string_view, std::string_view>> jit_values,
Args&&... args)
{
auto ordered_values = MatchJitValues(jit_values);
auto &mod = GetOrCreateModule(ordered_values);
auto &instance = mod.instantiate(std::string(name), std::string());
return instance.template run<ReturnT>(std::forward<Args>(args)...);
}
template <typename ReturnT, typename... Args>
ReturnT run_primal(
std::initializer_list<std::pair<std::string_view, std::string_view>> jit_values,
Args&&... args)
{
return run<ReturnT>(qfunc_name, jit_values,
std::forward<Args>(args)...);
}
template <typename ReturnT, typename... Args>
ReturnT run_derivative(
std::initializer_list<std::pair<std::string_view, std::string_view>> jit_values,
Args&&... args)
{
return run<ReturnT>(qfunc_name + "_fwddiff", jit_values,
std::forward<Args>(args)...);
}
private:
std::vector<std::string_view> MatchJitValues(
std::initializer_list<std::pair<std::string_view, std::string_view>>
named_values) const
{
std::unordered_map<std::string_view, std::string_view> value_map;
for (const auto &[name, value] : named_values)
{
value_map[name] = value;
}
std::vector<std::string_view> ordered_values;
ordered_values.reserve(jit_var_names.size());
for (const auto &var_name : jit_var_names)
{
auto it = value_map.find(var_name);
MFEM_VERIFY(it != value_map.end(),
"missing JIT value for variable: " << var_name);
ordered_values.push_back(it->second);
}
MFEM_VERIFY(ordered_values.size() == named_values.size(),
"provided " << named_values.size() << " JIT values but expected "
<< jit_var_names.size());
return ordered_values;
}
std::string BuildModuleCode(const std::vector<std::string> &jit_values) const
{
std::string module_code =
RewriteKernelForJit(ReadFileOrEmpty(fn), jit_values);
module_code += "\n\n";
module_code += "// --- generated ---\n";
module_code +=
"template <typename return_type, typename... Args>\n"
"return_type __enzyme_fwddiff(Args...);\n"
"\n"
"extern int enzyme_const;\n"
"extern int enzyme_dup;\n"
"\n";
// Generate a primal wrapper with the requested symbol name, so the kernel
// header can just define the qfunc as a functor.
//
// Note: Proteus instantiates entrypoints via `qfunc_wrapper<>(...)` even
// when there are no user template args, so keep the wrapper itself a
// template (with a default parameter) while still doing literal `$JIT`
// replacements in the kernel code.
module_code += "template <typename = void>\n";
module_code += return_type + " " +
std::string(qfunc_name) + "(";
bool first = true;
for (size_t i = 0; i < nparams; ++i)
{
if (!first) { module_code += ", "; }
first = false;
module_code += param_types[i] + " Arg" + std::to_string(i);
}
module_code += ")\n";
module_code += "{\n";
module_code += " " + impl_type_name + " qf;\n";
if (return_is_void)
{
module_code += " ";
}
else
{
module_code += " return ";
}
module_code += "qf(";
for (size_t i = 0; i < nparams; ++i)
{
if (i) { module_code += ", "; }
module_code += "Arg" + std::to_string(i);
}
module_code += ");\n";
module_code += "}\n\n";
module_code += "template <typename = void>\n";
module_code += return_type + " " +
std::string(qfunc_name) + "_fwddiff(";
first = true;
for (size_t i = 0; i < nparams; ++i)
{
if (!first) { module_code += ", "; }
first = false;
module_code += param_types[i] + " Arg" + std::to_string(i);
if (activity_map[i])
{
module_code += ", " + param_types[i] + " dArg" + std::to_string(i);
}
}
module_code += ")\n";
module_code += "{\n";
if (return_is_void)
{
module_code += " __enzyme_fwddiff<void>(\n";
}
else
{
module_code += " return __enzyme_fwddiff<" +
return_type + ">(\n";
}
module_code += " (void*)" + std::string(qfunc_name) + "<>";
module_code += ",\n";
for (size_t i = 0; i < nparams; ++i)
{
if (activity_map[i])
{
module_code += " enzyme_dup, Arg" + std::to_string(i) +
", dArg" + std::to_string(i);
}
else
{
module_code += " enzyme_const, Arg" + std::to_string(i);
}
module_code += (i + 1 == nparams) ? ");\n" : ",\n";
}
module_code += "}\n";
WriteFileOrWarn(GeneratedOutputPath(fn), module_code);
return module_code;
}
proteus::CppJitModule &GetOrCreateModule(
const std::vector<std::string_view> &jit_values)
{
std::string key;
for (const auto &val : jit_values)
{
if (!key.empty()) { key += ","; }
key += val;
}
auto it = modules.find(key);
if (it != modules.end())
{
return *it->second;
}
std::vector<std::string> values(jit_values.begin(), jit_values.end());
std::string code = BuildModuleCode(values);
auto mod = std::make_unique<proteus::CppJitModule>("host", code,
DefaultExtraArgs());
auto [inserted, ok] = modules.emplace(key, std::move(mod));
MFEM_VERIFY(ok, "failed to cache JIT module");
return *inserted->second;
}
static std::vector<std::string> DefaultExtraArgs()
{
return {"-fplugin=/Users/andrej1/local/enzyme/lib/ClangEnzyme-20.dylib"};
}
std::string qfunc_name = "qfunc_wrapper";
std::string fn;
size_t nparams = 0;
std::vector<bool> activity_map;
std::vector<std::string> param_types;
std::string return_type;
bool return_is_void = false;
std::string impl_type_name;
std::vector<std::string> jit_var_names;
std::unordered_map<std::string, std::unique_ptr<proteus::CppJitModule>> modules;
};
int main()
{
const size_t N = 4;
const size_t M = 5;
const double A = 123.4;
std::vector<double> X(N);
std::vector<double> Y(N);
for (size_t i = 0; i < N; ++i)
{
X[i] = static_cast<double>(i + 1);
Y[i] = static_cast<double>(N - i);
}
// // >>> user interface calls
// const std::string kernel_path = std::string(util::thisFileDir) +
// "/jitplayground.hpp";
// JitQFunction qf(daxpy_op{}, kernel_path, std::array{false, true, false});
// // <<< user interface calls
// // this will happen internally in dFEM
daxpy_op op;
printf("\n\nfunction call\n");
op(&A, X.data(), Y.data(), &N);
// reset X for the derivative test
for (size_t i = 0; i < N; ++i)
{
X[i] = static_cast<double>(i + 1);
Y[i] = static_cast<double>(N - i);
}
std::vector<double> dX(N, 1.0);
printf("\n\nforward diff call\n");
daxpy_op_fwddiff(&A, X.data(), dX.data(), Y.data(), &N);
std::vector<double> dX_manual(N, A);
printf("\n\nderivative checks\n");
std::cout << "dX: ";
for (size_t i = 0; i < N; ++i)
{
std::cout << dX[i] << (i + 1 == N ? '\n' : ' ');
}
std::cout << "dX_manual: ";
for (size_t i = 0; i < N; ++i)
{
std::cout << dX_manual[i] << (i + 1 == N ? '\n' : ' ');
}
double max_abs_err = 0.0;
for (size_t i = 0; i < N; ++i)
{
max_abs_err = std::max(max_abs_err, std::abs(dX[i] - dX_manual[i]));
}
std::cout << "max |dX - dX_manual| = " << max_abs_err << "\n";
return 0;
}
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include <cstddef>
#include <vector>
#include <type_traits>
#include "proteus/JitInterface.h"
struct daxpy_op
{
void operator()(
const double *a,
double *x,
const double *y,
const size_t *N) const
{
const size_t n = *N;
auto lam = [=, n = proteus::jit_variable(n)]
() __attribute__((annotate("jit")))
{
printf("N = %zu\n", n);
for (size_t i = 0; i < n; ++i)
{
printf("x[%zu] = %f, y[%zu] = %f\n", i, x[i], i, y[i]);
x[i] = *a * x[i] + y[i];
printf("updated x[%zu] = %f\n", i, x[i]);
}
};
proteus::register_lambda(lam);
lam();
}
};
template <typename return_type, typename... Args>
return_type __enzyme_fwddiff(Args...);
extern int enzyme_const;
extern int enzyme_dup;
void daxpy_op_wrapper(const double * Arg0, double * Arg1,
const double * Arg2, const size_t *Arg3)
{
daxpy_op qf;
qf(Arg0, Arg1, Arg2, Arg3);
}
void daxpy_op_fwddiff(const double * Arg0, double * Arg1,
double * dArg1, const double * Arg2, const size_t *Arg3)
{
__enzyme_fwddiff<void>(
(void*)daxpy_op_wrapper,
enzyme_const, Arg0,
enzyme_dup, Arg1, dArg1,
enzyme_const, Arg2,
enzyme_const, Arg3);
}
+20 -13
View File
@@ -121,6 +121,11 @@ set(SRCS
qinterp/eval_hdiv.cpp
qinterp/grad_by_nodes.cpp
qinterp/grad_by_vdim.cpp
qinterp/grad_transpose.cpp
qinterp/grad_transpose_by_nodes.cpp
qinterp/grad_transpose_by_vdim.cpp
qinterp/eval_transpose.cpp
qinterp/eval_transpose_by_vdim.cpp
qspace.cpp
quadinterpolator.cpp
quadinterpolator_face.cpp
@@ -133,7 +138,7 @@ set(SRCS
tmop/assemble/diag2.cpp
tmop/assemble/grad2_limit.cpp
tmop/assemble/grad2.cpp
tmop/assemble/diag3_limit.cpp
tmop/assemble/diag3_limit.cpp
tmop/assemble/diag3.cpp
tmop/assemble/grad3_limit.cpp
tmop/assemble/grad3.cpp
@@ -278,8 +283,10 @@ set(HDRS
qfunction.hpp
qinterp/det.hpp
qinterp/eval.hpp
qinterp/eval_transpose.hpp
qinterp/eval_hdiv.hpp
qinterp/grad.hpp
qinterp/grad_transpose.hpp
qspace.hpp
quadinterpolator.hpp
quadinterpolator_face.hpp
@@ -313,36 +320,36 @@ set(HDRS
)
if (MFEM_USE_SIDRE)
list(APPEND SRCS sidredatacollection.cpp)
list(APPEND HDRS sidredatacollection.hpp)
list(APPEND SRCS sidredatacollection.cpp)
list(APPEND HDRS sidredatacollection.hpp)
endif()
if (MFEM_USE_CONDUIT)
list(APPEND SRCS conduitdatacollection.cpp)
list(APPEND HDRS conduitdatacollection.hpp)
list(APPEND SRCS conduitdatacollection.cpp)
list(APPEND HDRS conduitdatacollection.hpp)
endif()
if (MFEM_USE_ADIOS2)
list(APPEND SRCS adios2datacollection.cpp)
list(APPEND HDRS adios2datacollection.hpp)
list(APPEND SRCS adios2datacollection.cpp)
list(APPEND HDRS adios2datacollection.hpp)
endif()
if (MFEM_USE_FMS)
list(APPEND SRCS fmsdatacollection.cpp fmsconvert.cpp)
list(APPEND HDRS fmsdatacollection.hpp fmsconvert.hpp)
list(APPEND SRCS fmsdatacollection.cpp fmsconvert.cpp)
list(APPEND HDRS fmsdatacollection.hpp fmsconvert.hpp)
endif()
if (MFEM_USE_MPI)
list(APPEND SRCS
list(APPEND SRCS
pbilinearform.cpp
pfespace.cpp
pgridfunc.cpp
plinearform.cpp
pnonlinearform.cpp
prestriction.cpp)
# If this list (HDRS -> HEADERS) is used for install, we probably want the
# headers added all the time.
list(APPEND HDRS
# If this list (HDRS -> HEADERS) is used for install, we probably want the
# headers added all the time.
list(APPEND HDRS
pbilinearform.hpp
pfespace.hpp
pgridfunc.hpp
+17 -12
View File
@@ -2178,18 +2178,22 @@ class DiffusionIntegrator: public BilinearFormIntegrator
{
public:
using ApplyKernelType = void(*)(const int, const bool, const Array<real_t>&,
const Array<real_t>&, const Array<real_t>&,
const Array<real_t>&,
const Vector&, const Vector&,
Vector&, const int, const int);
using DiffusionApplyKernelType = void(*)(const int, const bool,
const Array<real_t>&,
const Array<real_t>&, const Array<real_t>&,
const Array<real_t>&,
const Vector&, const Vector&,
Vector&, const int, const int);
using DiagonalKernelType = void(*)(const int, const bool, const Array<real_t>&,
const Array<real_t>&, const Vector&, Vector&,
const int, const int);
using DiffusionDiagonalKernelType = void(*)(const int, const bool,
const Array<real_t>&,
const Array<real_t>&, const Vector&, Vector&,
const int, const int);
MFEM_REGISTER_KERNELS(ApplyPAKernels, ApplyKernelType, (int, int, int));
MFEM_REGISTER_KERNELS(DiagonalPAKernels, DiagonalKernelType, (int, int, int));
MFEM_REGISTER_KERNELS(DiffusionApplyPAKernel, DiffusionApplyKernelType,
(int, int, int));
MFEM_REGISTER_KERNELS(DiffusionDiagonalPAKernel, DiffusionDiagonalKernelType,
(int, int, int));
struct Kernels { Kernels(); };
protected:
@@ -2209,6 +2213,7 @@ private:
const FiniteElementSpace *fespace;
const DofToQuad *maps; ///< Not owned
const GeometricFactors *geom; ///< Not owned
public:
int dim, ne, dofs1D, quad1D;
Vector pa_data;
bool symmetric = true; ///< False if using a nonsymmetric matrix coefficient
@@ -2350,8 +2355,8 @@ public:
template <int DIM, int D1D, int Q1D>
static void AddSpecialization()
{
ApplyPAKernels::Specialization<DIM,D1D,Q1D>::Add();
DiagonalPAKernels::Specialization<DIM,D1D,Q1D>::Add();
DiffusionApplyPAKernel::Specialization<DIM,D1D,Q1D>::Add();
DiffusionDiagonalPAKernel::Specialization<DIM,D1D,Q1D>::Add();
}
protected:
const IntegrationRule* GetDefaultIntegrationRule(
+587
View File
@@ -0,0 +1,587 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include <cassert>
#include <cstddef>
// #include "fem/kernels.hpp"
#include "fem/kernels3d.hpp"
namespace ker = mfem::kernels::internal;
namespace low = mfem::kernels::internal::low;
#include "fem/kernel_dispatch.hpp"
// #include "linalg/kernels.hpp"
#include "util.hpp"
#undef NVTX_COLOR
#define NVTX_COLOR ::nvtx::kOrchid
namespace mfem::future
{
/** @brief Zero-copy view of a contiguous block as a `tensor<T, n1>` */
template<typename T, int n1>
MFEM_HOST_DEVICE
const tensor<T, n1>& as_tensor(const T* ptr)
{
// std::launder makes this defined behavior under strict aliasing rules
return *std::launder(reinterpret_cast<const tensor<T, n1>*>(ptr));
}
// convenience overload if you prefer a mutable view
template<typename T, int n1>
MFEM_HOST_DEVICE
tensor<T, n1>& as_tensor(T* ptr)
{
return *std::launder(reinterpret_cast<tensor<T, n1>*>(ptr));
}
/** @brief Zero-copy view of a contiguous block as a `tensor<T, n1, n2>` */
template<typename T, int n1, int n2>
MFEM_HOST_DEVICE
const tensor<T, n1, n2>& as_tensor(const T* ptr)
{
// std::launder makes this defined behavior under strict aliasing rules
return *std::launder(reinterpret_cast<const tensor<T, n1, n2>*>(ptr));
}
// convenience overload if you prefer a mutable view
template<typename T, int n1, int n2>
MFEM_HOST_DEVICE
tensor<T, n1, n2>& as_tensor(T* ptr)
{
return *std::launder(reinterpret_cast<tensor<T, n1, n2>*>(ptr));
}
/** @brief Zero-copy view of a contiguous block as a `tensor<T, n1, n2, n3>` */
template<typename T, int n1, int n2, int n3>
MFEM_HOST_DEVICE
const tensor<T, n1, n2, n3>& as_tensor(const T* ptr)
{
// std::launder makes this defined behavior under strict aliasing rules
return *std::launder(reinterpret_cast<const tensor<T, n1, n2, n3>*>(ptr));
}
// convenience overload if you prefer a mutable view
template<typename T, int n1, int n2, int n3>
MFEM_HOST_DEVICE
tensor<T, n1, n2, n3>& as_tensor(T* ptr)
{
return *std::launder(reinterpret_cast<tensor<T, n1, n2, n3>*>(ptr));
}
/** @brief Zero-copy view of a contiguous block as a `tensor<T, n1, n2, n3, n4>` */
template<typename T, int n1, int n2, int n3, int n4>
MFEM_HOST_DEVICE
const tensor<T, n1, n2, n3, n4>& as_tensor(const T* ptr)
{
// std::launder makes this defined behavior under strict aliasing rules
return *std::launder(reinterpret_cast<const tensor<T, n1, n2, n3, n4>*>(ptr));
}
// convenience overload if you prefer a mutable view
template<typename T, int n1, int n2, int n3, int n4>
MFEM_HOST_DEVICE
tensor<T, n1, n2, n3, n4>& as_tensor(T* ptr)
{
return *std::launder(reinterpret_cast<tensor<T, n1, n2, n3, n4>*>(ptr));
}
template <std::size_t N>
MFEM_HOST_DEVICE inline
std::array<real_t*, N>
load_field_e_ptr(const std::array<DeviceTensor<2>, N> &fields_e,
const int e)
{
std::array<real_t*, N> f;
for_constexpr<N>([&](auto i) { f[i] = &fields_e[i](0, e); });
return f;
}
namespace qf
{
template <int T_Q1D,
size_t num_args,
typename reg_t,
typename qfunc_t,
typename args_ts>
MFEM_HOST_DEVICE inline
void apply_kernel(reg_t &res /*output*/,
reg_t &reg,
const real_t *rd,
const int qx, const int qy, const int qz,
const qfunc_t &qfunc, args_ts &args)
{
if constexpr (num_args == 2) // PAApply
{
// ∇u
tensor<real_t, 3> &arg_0 = get<0>(args);
arg_0[0] = reg[qz][qy][qx][0];
arg_0[1] = reg[qz][qy][qx][1];
arg_0[2] = reg[qz][qy][qx][2];
// D (PA data)
tensor<real_t, 3, 3> &arg_1 = get<1>(args);
if constexpr (T_Q1D > 0)
{
const auto *D = (const real_t (*)[T_Q1D][T_Q1D][3][3]) rd;
for (int k = 0; k < 3; k++)
{
for (int j = 0; j < 3; j++)
{
arg_1[k][j] = D[qx][qy][qz][k][j];
}
}
}
else
{
static_assert(false);
// const auto D = Reshape(r2, 3, 3, Q1D, Q1D, Q1D);
// for (int j = 0; j < 3; j++)
// {
// for (int k = 0; k < 3; k++)
// {
// arg_1[k][j] = D(j, k, qz, qy, qx);
// }
// }
}
}
else
{
// MFApply comes here
assert(false);
// MFEM_ABORT("Only two arguments (∇u and D) are supported in apply_kernel for now");
}
const auto r = get<0>(apply(qfunc, args));
if constexpr (decltype(r)::ndim == 1)
{
// process_qf_result_from_reg(r0, qx, qy, qz, r);
as_tensor<real_t, 3>(&res[qz][qy][qx][0]) = r;
}
else
{
static_assert(false);
}
}
} // namespace qf
#define MFEM_D2Q_MAX_SIZE 4
static MFEM_CONSTANT real_t Bi[MFEM_D2Q_MAX_SIZE][8*8], Bo[8*8];
static MFEM_CONSTANT real_t Gi[MFEM_D2Q_MAX_SIZE][8*8], Go[8*8];
template<size_t num_fields,
size_t num_inputs,
size_t num_outputs,
typename restriction_cb_t,
typename qfunc_t,
typename input_t,
typename output_fop_t>
class NewActionCallback
{
restriction_cb_t &restriction_cb;
qfunc_t &qfunc;
input_t &inputs;
const std::array<size_t, num_inputs> &input_to_field;
const std::array<DofToQuadMap, num_inputs> &input_dtq_maps;
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps;
const int num_entities;
const int test_vdim;
const int num_test_dof;
const int dimension;
const ThreadBlocks &thread_blocks;
SharedMemoryInfo<num_fields, num_inputs, num_outputs> &shmem_info;
const Array<int> &attributes;
const output_fop_t &output_fop;
const Array<int> *elem_attributes;
// refs
std::vector<Vector> &fields_e;
Vector &residual_e;
std::function<void(Vector &, Vector &)> &output_restriction_transpose;
// args
std::vector<Vector> &solutions_l;
const std::vector<Vector> &parameters_l;
Vector &residual_l;
public:
NewActionCallback() = delete;
NewActionCallback(const bool use_kernels_specialization,
restriction_cb_t &restriction_cb,
qfunc_t &qfunc,
input_t &inputs,
const std::array<size_t, num_inputs> &input_to_field,
const std::array<DofToQuadMap, num_inputs> &input_dtq_maps,
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps,
const int num_entities,
const int test_vdim,
const int num_test_dof,
const int dimension,
const ThreadBlocks &thread_blocks,
SharedMemoryInfo<num_fields, num_inputs, num_outputs> &shmem_info,
const Array<int> &attributes,
const output_fop_t &output_fop,
const Array<int> *elem_attributes,
// refs
std::vector<Vector> &fields_e,
Vector &residual_e,
std::function<void(Vector &, Vector &)> &output_restriction_transpose,
// args
std::vector<Vector> &solutions_l,
const std::vector<Vector> &parameters_l,
Vector &residual_l):
restriction_cb(restriction_cb),
qfunc(qfunc),
inputs(inputs),
input_to_field(input_to_field),
input_dtq_maps(input_dtq_maps),
output_dtq_maps(output_dtq_maps),
num_entities(num_entities),
test_vdim(test_vdim),
num_test_dof(num_test_dof),
dimension(dimension),
thread_blocks(thread_blocks),
shmem_info(shmem_info),
attributes(attributes),
output_fop(output_fop),
elem_attributes(elem_attributes),
fields_e(fields_e),
residual_e(residual_e),
output_restriction_transpose(output_restriction_transpose),
solutions_l(solutions_l),
parameters_l(parameters_l),
residual_l(residual_l)
{
if (!use_kernels_specialization) { return; }
NewActionCallbackKernels::template Specialization<3>::Add(); // 1
NewActionCallbackKernels::template Specialization<4>::Add(); // 2
NewActionCallbackKernels::template Specialization<5>::Add(); // 3
NewActionCallbackKernels::template Specialization<6>::Add(); // 4
NewActionCallbackKernels::template Specialization<7>::Add(); // 5
NewActionCallbackKernels::template Specialization<8>::Add(); // 6
}
template<int T_Q1D = 0>
static void action_callback_new(const int d1d,
restriction_cb_t &restriction_cb,
qfunc_t &qfunc,
[[maybe_unused]] input_t &inputs,
[[maybe_unused]] const std::array<size_t, num_inputs> &input_to_field,
const std::array<DofToQuadMap, num_inputs> &input_dtq_maps,
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps,
[[maybe_unused]] const int dimension,
const int num_entities,
[[maybe_unused]] const int test_vdim,
[[maybe_unused]] const int num_test_dof,
const ThreadBlocks &thread_blocks,
[[maybe_unused]] SharedMemoryInfo<num_fields, num_inputs, num_outputs>
&shmem_info,
[[maybe_unused]] const Array<int> &attributes,
[[maybe_unused]] const output_fop_t &output_fop,
[[maybe_unused]] const Array<int> *elem_attributes,
// refs
std::vector<Vector> &fields_e,
Vector &residual_e,
std::function<void(Vector &, Vector &)> &output_restriction_transpose,
// args
std::vector<Vector> &solutions_l,
const std::vector<Vector> &parameters_l,
Vector &residual_l,
// fallback arguments
const int q1d)
{
NVTX_MARK_FUNCTION;
assert(dimension == 3);
static_assert(MFEM_D2Q_MAX_SIZE >= num_inputs, "MFEM_D2Q_MAX_SIZE error");
constexpr int DIM = 3;
[[maybe_unused]] static bool ini = (for_constexpr<num_inputs>([&](auto i)
{
const auto dtq = input_dtq_maps[i];
{
const auto [q, _, p] = dtq.B.GetShape();
const auto B = (const real_t*)input_dtq_maps[i].B;
dbg("Loading Bi[{}]: q={} p={}", i.value, q, p);
if (B) { Gpu(MemcpyToSymbol)(Bi[i], B, (p*q)*sizeof(real_t)); }
}
{
const auto [q, _, p] = dtq.G.GetShape();
const auto G = (const real_t*)input_dtq_maps[i].G;
if (G) { Gpu(MemcpyToSymbol)(Gi[i], G, (p*q)*sizeof(real_t)); }
}
if constexpr (i == 0) // output B
{
const auto dtq_o = output_dtq_maps[0];
const auto [q, _, p] = dtq_o.B.GetShape();
const auto B = (const real_t*)dtq_o.B;
if (B) { Gpu(MemcpyToSymbol)(Bo, B, (p*q)*sizeof(real_t)); }
}
if constexpr (i == 0) // output G
{
const auto dtq_o = output_dtq_maps[0];
const auto [q, _, p] = dtq_o.G.GetShape();
const auto G = (const real_t*)dtq_o.G;
if (G) { Gpu(MemcpyToSymbol)(Go, G, (p*q)*sizeof(real_t)); }
dbg("Loaded B and G to constant memory");
}
}), true);
// types
using qf_signature =
typename create_function_signature<decltype(&qfunc_t::operator())>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
restriction_cb(solutions_l, parameters_l, fields_e);
NVTX_INI("res=0");
residual_e = 0.0;
NVTX_END("res=0");
// auto wrapped_fields_e =
// wrap_fields(fields_e, shmem_info.field_sizes, num_entities);
const bool has_attr = attributes.Size() > 0;
const auto d_attr = attributes.Read();
const auto d_elem_attr = elem_attributes->Read();
// const int vdim = input.vdim;
// const auto fields_e_ptr = load_field_e_ptr(wrapped_fields_e, e);
// const real_t *field_e_r = fields_e_ptr[input_to_field[i]];
// const auto fields_e_ptr = load_field_e_ptr(wrapped_fields_e, e);
const int NE = num_entities;
constexpr int VDIM = 1;
const auto XE = Reshape(fields_e[0].Read(), d1d, d1d, d1d, VDIM, NE);
const real_t *dx_ptr = fields_e[1].Read();
auto YE = Reshape(residual_e.ReadWrite(), d1d, d1d, d1d, VDIM, NE);
const auto B = (const real_t*)input_dtq_maps[0/*i*/].B;
const auto G = (const real_t*)input_dtq_maps[0/*i*/].G;
NVTX_INI("forall");
dfem::forall<T_Q1D*T_Q1D*T_Q1D>([=] MFEM_HOST_DEVICE (int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
constexpr int MQ1 = T_Q1D > 0 ? T_Q1D : 8;
MFEM_SHARED real_t sm0[MQ1][MQ1][MQ1][3];
MFEM_SHARED real_t sm1[MQ1][MQ1][MQ1][3];
// real_t (&sm0_ptr)[MQ1][MQ1][MQ1][3] = sm0;
// real_t (&sm1_ptr)[MQ1][MQ1][MQ1][3] = sm1;
low::regs3d_t<DIM, MQ1> reg;
const real_t *rd = dx_ptr;
// const auto fields_e_ptr = load_field_e_ptr(wrapped_fields_e, e);
MFEM_SHARED real_t sB[MQ1][MQ1], sG[MQ1][MQ1];
// real_t (&sB_ptr)[MD1][MQ1] = sB;
// real_t (&sG_ptr)[MD1][MQ1] = sG;
// Interpolate
// for_constexpr<num_inputs>(
// [ D1D, Q1D, MQ1, e,
// &input_dtq_maps,
// &sm0_ptr, &sm1_ptr,
// &sB = sB_ptr, &sG = sG_ptr,
// &inputs,
// // &fields_e_ptr,
// &reg, &rd,
// &input_to_field ] (auto i)
{
// const auto input = get<0/*i*/>(inputs);
// using field_operator_t = std::decay_t<decltype(input)>;
// if constexpr (is_gradient_fop<field_operator_t>::value) // Grad
{
// const int vdim = input.vdim;
// const real_t *field_e_r = fields_e_ptr[input_to_field[i]];
// const auto XE = Reshape(field_e_r, D1D, D1D, D1D, vdim);
// const auto sB = reinterpret_cast<const real_t (*)[MQ1]>(Bi[i]);
// const auto sG = reinterpret_cast<const real_t (*)[MQ1]>(Gi[i]);
low::LoadMatrix(d1d, q1d, B, sB);
low::LoadMatrix(d1d, q1d, G, sG);
// for (int c = 0; c < vdim; c++)
// constexpr int c = 0;
{
low::LoadDofs3d(e, d1d, XE, sm0);
low::Grad3d(d1d, q1d, sB, sG, sm0, sm1, reg);
}
}
// else if constexpr (is_identity_fop<field_operator_t>::value) // Identity
{
// db1("Identity");
// rd = fields_e_ptr[input_to_field[i]];
// rd = dx_ptr;
}
// else if constexpr (is_weight_fop<field_operator_t>::value) // Weight
// {
// dbg("Weight");
// rw = fields_e_ptr[input_to_field[i]]; // 🔥
// }
// else
{
// MFApply comes here
// assert(false);
// MFEM_ABORT("Only Grad and Identity field operators are supported");
}
}//); // for_constexpr<num_inputs>
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
#if 0
auto qf_args = decay_tuple<qf_param_ts> {};
qf::apply_kernel<T_Q1D, num_inputs>
(reg, reg, rd, qx, qy, qz, qfunc, qf_args);
#elif 0
real_t v[3], u[3] = { reg[qz][qy][qx][0],
reg[qz][qy][qx][1],
reg[qz][qy][qx][2]
};
const auto *D = (real_t (*)[T_Q1D][T_Q1D][3][3]) rd;
kernels::Mult(3, 3, &D[qx][qy][qz][0][0], u, v);
reg[qz][qy][qx][0] = v[0];
reg[qz][qy][qx][1] = v[1];
reg[qz][qy][qx][2] = v[2];
#elif 0
const auto *D = (real_t (*)[T_Q1D][T_Q1D][3][3]) rd;
const auto args = decay_tuple<qf_param_ts>
{
{{ reg[qz][qy][qx][0], reg[qz][qy][qx][1], reg[qz][qy][qx][2] }},
{{
{{ D[qx][qy][qz][0][0], D[qx][qy][qz][0][1], D[qx][qy][qz][0][2] }},
{{ D[qx][qy][qz][1][0], D[qx][qy][qz][1][1], D[qx][qy][qz][1][2] }},
{{ D[qx][qy][qz][2][0], D[qx][qy][qz][2][1], D[qx][qy][qz][2][2] }}
}
}
};
const auto r = get<0>(apply(qfunc, args));
reg[qz][qy][qx][0] = r[0];
reg[qz][qy][qx][1] = r[1];
reg[qz][qy][qx][2] = r[2];
#elif 0
auto u = as_tensor<real_t, 3>(&reg[qz][qy][qx][0]);
const auto *d = (real_t (*)[T_Q1D][T_Q1D][3][3]) rd;
auto D = as_tensor<real_t, 3, 3>(&d[qx][qy][qz][0][0]);
auto r = D * u;
reg[qz][qy][qx][0] = r[0];
reg[qz][qy][qx][1] = r[1];
reg[qz][qy][qx][2] = r[2];
#else
auto args = decay_tuple<qf_param_ts> {};
get<0>(args) = as_tensor<real_t, 3>(&reg[qz][qy][qx][0]);
if constexpr (T_Q1D > 0)
{
get<1>(args) = as_tensor<real_t, 3, 3>(rd + 9*(qx*T_Q1D*T_Q1D + qy*T_Q1D + qz));
}
else
{
get<1>(args) = as_tensor<real_t, 3, 3>(rd + 9*(qx*q1d*q1d + qy*q1d + qz));
}
auto r = get<0>(apply(qfunc, args));
if constexpr (decltype(r)::ndim == 1)
{
as_tensor<real_t, 3>(&reg[qz][qy][qx][0]) = r;
}
else { static_assert(false); }
#endif
}
}
}
MFEM_SYNC_THREAD;
// Integrate
// if constexpr (is_gradient_fop<std::decay_t<output_fop_t>>::value) // Gradient
{
// const auto sB = reinterpret_cast<const real_t (*)[MQ1]>(Bo);
// const auto sG = reinterpret_cast<const real_t (*)[MQ1]>(Go);
low::GradTranspose3d(d1d, q1d, sB, sG, reg, sm1, sm0);
low::WriteDofs3d(d1d, 0, e, reg, YE);
}
},
num_entities, thread_blocks, 0, nullptr);
NVTX_END("forall");
NVTX_INI("out^T");
output_restriction_transpose(residual_e, residual_l);
NVTX_END("out^T");
}
using NewActionKernelType = decltype(&NewActionCallback::action_callback_new<>);
MFEM_REGISTER_KERNELS(NewActionCallbackKernels, NewActionKernelType, (int));
void Apply(const int d1d, const int q1d)
{
db1();
NewActionCallbackKernels::Run(q1d,
// args
d1d,
restriction_cb,
qfunc,
inputs,
input_to_field,
input_dtq_maps,
output_dtq_maps,
dimension,
num_entities,
test_vdim,
num_test_dof,
thread_blocks,
shmem_info,
attributes,
output_fop,
elem_attributes,
fields_e,
residual_e,
output_restriction_transpose,
solutions_l,
parameters_l,
residual_l,
// fallback arguments
q1d);
}
};
template<size_t num_fields, size_t num_inputs, size_t num_outputs,
typename restriction_cb_t, typename qfunc_t, typename input_t, typename output_fop_t>
template<int T_Q1D>
typename NewActionCallback<num_fields, num_inputs, num_outputs, restriction_cb_t, qfunc_t, input_t, output_fop_t>::NewActionKernelType
NewActionCallback<num_fields, num_inputs, num_outputs, restriction_cb_t, qfunc_t, input_t, output_fop_t>::NewActionCallbackKernels::Kernel()
{
return action_callback_new<T_Q1D>;
}
template<size_t num_fields, size_t num_inputs, size_t num_outputs,
typename restriction_cb_t, typename qfunc_t, typename input_t, typename output_fop_t>
typename NewActionCallback<num_fields, num_inputs, num_outputs, restriction_cb_t, qfunc_t, input_t, output_fop_t>::NewActionKernelType
NewActionCallback<num_fields, num_inputs, num_outputs, restriction_cb_t, qfunc_t, input_t, output_fop_t>::NewActionCallbackKernels::Fallback
(int q1d)
{
dbg("\x1b[33mFallback q1d:{}", q1d);
// MFEM_ABORT("No kernel for q1d=" << q1d);
// return nullptr;
return action_callback_new<>;
}
} // namespace mfem::future
+111
View File
@@ -0,0 +1,111 @@
#pragma once
#include "../util.hpp"
#include "../../integrator_ctx.hpp"
#include <utility>
namespace mfem::future
{
namespace GlobalQFImpl
{
template<
typename qfunc_t,
typename inputs_t,
typename outputs_t,
size_t ninputs = tuple_size<inputs_t>::value,
size_t noutputs = tuple_size<outputs_t>::value>
struct Action
{
Action(
IntegratorContext ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs) :
ctx(ctx),
qfunc(std::move(qfunc)),
inputs(inputs),
outputs(outputs)
{
create_fop_to_fd(inputs, ctx.infds, input_to_infd);
create_fop_to_fd(outputs, ctx.outfds, output_to_outfd);
check_consistency(inputs, input_to_infd, ctx.infds);
check_consistency(outputs, output_to_outfd, ctx.outfds);
create_fieldbases(inputs, input_to_infd, ctx.infds, ctx.ir, input_bases);
create_fieldbases(outputs, output_to_outfd, ctx.outfds, ctx.ir, output_bases);
create_qlayouts(inputs, ctx.in_qlayouts, input_qlayouts);
create_qlayouts(outputs, ctx.out_qlayouts, output_qlayouts);
const int nqp = ctx.ir.GetNPoints();
gnqp = nqp * ctx.nentities;
xq_offsets.SetSize(ninputs + 1);
xq_offsets[0] = 0;
constexpr_for<0, ninputs>([&](auto i)
{
const auto input = get<i>(inputs);
xq_offsets[i + 1] = nqp * input.size_on_qp * ctx.nentities;
});
xq_offsets.PartialSum();
xq.Update(xq_offsets);
yq_offsets.SetSize(noutputs + 1);
yq_offsets[0] = 0;
constexpr_for<0, noutputs>([&](auto i)
{
const auto output = get<i>(outputs);
yq_offsets[i + 1] = nqp * output.size_on_qp * ctx.nentities;
});
yq_offsets.PartialSum();
yq.Update(yq_offsets);
}
void operator()(
const std::vector<Vector *> &xe,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
// E -> Q
interpolate(input_to_infd, input_bases, xe, xq);
// Q -> Q
static_assert(
detail::supports_tensor_array_qfunc<qfunc_t, inputs_t, outputs_t>::value,
"qfunc signature not supported by default backend Action");
detail::call_qfunc(
qfunc, xq, yq, gnqp, input_qlayouts, output_qlayouts,
std::make_index_sequence<ninputs> {},
std::make_index_sequence<noutputs> {});
// Q -> E
integrate(output_to_outfd, output_bases, yq, ye);
}
IntegratorContext ctx;
qfunc_t qfunc;
inputs_t inputs;
outputs_t outputs;
std::array<size_t, ninputs> input_to_infd;
std::array<size_t, noutputs> output_to_outfd;
std::array<FieldBasis, ninputs> input_bases;
std::array<FieldBasis, noutputs> output_bases;
std::array<std::vector<int>, ninputs> input_qlayouts;
std::array<std::vector<int>, noutputs> output_qlayouts;
int gnqp = 0;
Array<int> xq_offsets, yq_offsets;
mutable BlockVector xq, yq;
};
}
}
@@ -0,0 +1,131 @@
#pragma once
#include "../fem/quadinterpolator.hpp"
#include "../../integrator_ctx.hpp"
#include "../util.hpp"
#include <utility>
namespace mfem::future
{
namespace GlobalQFImpl
{
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t,
size_t ninputs = tuple_size<inputs_t>::value,
size_t noutputs = tuple_size<outputs_t>::value>
struct DerivativeActionEnzyme
{
DerivativeActionEnzyme(
IntegratorContext ctx,
qfunc_t &qfunc,
inputs_t inputs,
outputs_t outputs) :
ctx(ctx),
qfunc(qfunc),
inputs(inputs),
outputs(outputs)
{
create_fop_to_fd(inputs, ctx.infds, input_to_infd);
create_fop_to_fd(outputs, ctx.outfds, output_to_outfd);
check_consistency(inputs, input_to_infd, ctx.infds);
check_consistency(outputs, output_to_outfd, ctx.outfds);
create_fieldbases(inputs, input_to_infd, ctx.infds, ctx.ir, input_bases);
create_fieldbases(outputs, output_to_outfd, ctx.outfds, ctx.ir, output_bases);
create_qlayouts(inputs, ctx.in_qlayouts, input_qlayouts);
create_qlayouts(outputs, ctx.out_qlayouts, output_qlayouts);
const int nqp = ctx.ir.GetNPoints();
gnqp = nqp * ctx.nentities;
xq_offsets.SetSize(ninputs + 1);
xq_offsets[0] = 0;
constexpr_for<0, ninputs>([&](auto i)
{
const auto input = get<i>(inputs);
xq_offsets[i + 1] = nqp * input.size_on_qp * ctx.nentities;
});
xq_offsets.PartialSum();
xq.Update(xq_offsets);
yq_offsets.SetSize(noutputs + 1);
yq_offsets[0] = 0;
constexpr_for<0, noutputs>([&](auto i)
{
const auto output = get<i>(outputs);
yq_offsets[i + 1] = nqp * output.size_on_qp * ctx.nentities;
});
yq_offsets.PartialSum();
yq.Update(yq_offsets);
// For each dependent input in the dependency map we create a shadow
// memory variable at the quadrature point level.
const auto activity_map = detail::make_activity_map<derivative_id>(inputs);
shadow_xq_offsets.SetSize(ninputs + 1);
shadow_xq_offsets = 0;
constexpr_for<0, ninputs>([&](auto i)
{
if (activity_map[i])
{
shadow_xq_offsets[i + 1] =
xq_offsets[i + 1] - xq_offsets[i];;
}
});
shadow_xq_offsets.PartialSum();
shadow_xq.Update(shadow_xq_offsets);
}
void operator()(
const std::vector<Vector *> &xe,
const Vector *de,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
// E -> Q
interpolate(input_to_infd, input_bases, xe, xq);
const auto activity_map = detail::make_activity_map<derivative_id>(inputs);
interpolate(input_to_infd, input_bases, xe, shadow_xq, activity_map);
// Q -> Q
static_assert(
detail::supports_tensor_array_qfunc<qfunc_t, inputs_t, outputs_t>::value,
"qfunc signature not supported by default backend Action");
detail::enzyme_fwddiff<derivative_id, qfunc_t, inputs_t, outputs_t>(
qfunc, xq, shadow_xq, yq, gnqp, input_qlayouts, output_qlayouts,
std::make_index_sequence<ninputs> {},
std::make_index_sequence<noutputs> {});
// Q -> E
integrate(output_to_outfd, output_bases, yq, ye);
}
IntegratorContext ctx;
qfunc_t &qfunc;
inputs_t inputs;
outputs_t outputs;
std::array<size_t, ninputs> input_to_infd;
std::array<size_t, noutputs> output_to_outfd;
std::array<FieldBasis, ninputs> input_bases;
std::array<FieldBasis, noutputs> output_bases;
std::array<std::vector<int>, ninputs> input_qlayouts;
std::array<std::vector<int>, noutputs> output_qlayouts;
int gnqp = 0;
Array<int> xq_offsets, shadow_xq_offsets, yq_offsets;
mutable BlockVector xq, shadow_xq, yq;
};
}
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include "action.hpp"
#include "derivative_action_enzyme.hpp"
namespace mfem::future
{
struct GlobalQFBackend
{
template<
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeAction(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs)
{
return GlobalQFImpl::Action(ctx, qfunc, inputs, outputs);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeAction(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs)
{
return GlobalQFImpl::DerivativeActionEnzyme<
derivative_id, qfunc_t, inputs_t, outputs_t>(
ctx, qfunc, inputs, outputs);
}
};
}
+166
View File
@@ -0,0 +1,166 @@
#pragma once
#include "../util.hpp"
#include "../../integrator_ctx.hpp"
#include <utility>
namespace mfem::future
{
namespace LocalQFImpl
{
template<
typename qfunc_t,
typename inputs_t,
typename outputs_t,
size_t ninputs = tuple_size<inputs_t>::value,
size_t noutputs = tuple_size<outputs_t>::value>
struct Action
{
Action(
IntegratorContext ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs) :
ctx(ctx),
qfunc(std::move(qfunc)),
inputs(inputs),
outputs(outputs)
{
create_fop_to_fd(inputs, ctx.infds, input_to_infd);
create_fop_to_fd(outputs, ctx.outfds, output_to_outfd);
check_consistency(inputs, input_to_infd, ctx.infds);
check_consistency(outputs, output_to_outfd, ctx.outfds);
const int nqp = ctx.ir.GetNPoints();
// Initialize DofToQuad maps for inputs
for_constexpr<ninputs>([&](auto i)
{
const auto &fd = ctx.infds[input_to_infd[i]];
std::visit([&](auto* space_ptr)
{
using T = std::decay_t<decltype(*space_ptr)>;
if constexpr (std::is_same_v<T, FiniteElementSpace> ||
std::is_same_v<T, ParFiniteElementSpace>)
{
const auto *fe = space_ptr->GetTypicalFE();
input_dtq_maps[i] = &fe->GetDofToQuad(ctx.ir, DofToQuad::TENSOR);
}
}, fd.data);
});
// Initialize DofToQuad maps for outputs
for_constexpr<noutputs>([&](auto i)
{
const auto &fd = ctx.outfds[output_to_outfd[i]];
std::visit([&](auto* space_ptr)
{
using T = std::decay_t<decltype(*space_ptr)>;
if constexpr (std::is_same_v<T, FiniteElementSpace> ||
std::is_same_v<T, ParFiniteElementSpace>)
{
const auto *fe = space_ptr->GetTypicalFE();
output_dtq_maps[i] = &fe->GetDofToQuad(ctx.ir, DofToQuad::TENSOR);
}
}, fd.data);
});
}
void operator()(
const std::vector<Vector *> &xe,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
// input_dtq_maps
// const auto B = (const real_t*)input_dtq_maps[0/*i*/].B;
// const auto G = (const real_t*)input_dtq_maps[0/*i*/].G;
// dfem::forall<T_Q1D*T_Q1D*T_Q1D>([=] MFEM_HOST_DEVICE (int e, void *)
// {
// if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
// constexpr int MQ1 = T_Q1D > 0 ? T_Q1D : 8;
// MFEM_SHARED real_t sm0[MQ1][MQ1][MQ1][3];
// MFEM_SHARED real_t sm1[MQ1][MQ1][MQ1][3];
// low::regs3d_t<DIM, MQ1> reg;
// const real_t *rd = dx_ptr;
// MFEM_SHARED real_t sB[MQ1][MQ1], sG[MQ1][MQ1];
// {
// low::LoadMatrix(d1d, q1d, B, sB);
// low::LoadMatrix(d1d, q1d, G, sG);
// {
// low::LoadDofs3d(e, d1d, XE, sm0);
// low::Grad3d(d1d, q1d, sB, sG, sm0, sm1, reg);
// }
// }
// // else if constexpr (is_identity_fop<field_operator_t>::value) // Identity
// {
// // db1("Identity");
// // rd = fields_e_ptr[input_to_field[i]];
// // rd = dx_ptr;
// }
// }
// MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
// {
// MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
// {
// MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
// {
// auto args = decay_tuple<qf_param_ts> {};
// get<0>(args) = as_tensor<real_t, 3>(&reg[qz][qy][qx][0]);
// if constexpr (T_Q1D > 0)
// {
// get<1>(args) = as_tensor<real_t, 3, 3>(rd + 9*(qx*T_Q1D*T_Q1D + qy*T_Q1D + qz));
// }
// else
// {
// get<1>(args) = as_tensor<real_t, 3, 3>(rd + 9*(qx*q1d*q1d + qy*q1d + qz));
// }
// auto r = get<0>(apply(qfunc, args));
// if constexpr (decltype(r)::ndim == 1)
// {
// as_tensor<real_t, 3>(&reg[qz][qy][qx][0]) = r;
// }
// else { static_assert(false); }
// }
// }
// }
// MFEM_SYNC_THREAD;
// // Integrate
// // if constexpr (is_gradient_fop<std::decay_t<output_fop_t>>::value) // Gradient
// {
// // const auto sB = reinterpret_cast<const real_t (*)[MQ1]>(Bo);
// // const auto sG = reinterpret_cast<const real_t (*)[MQ1]>(Go);
// low::GradTranspose3d(d1d, q1d, sB, sG, reg, sm1, sm0);
// low::WriteDofs3d(d1d, 0, e, reg, YE);
// }
// },
// num_entities, thread_blocks, 0, nullptr);
}
IntegratorContext ctx;
qfunc_t qfunc;
inputs_t inputs;
outputs_t outputs;
std::array<size_t, ninputs> input_to_infd;
std::array<size_t, noutputs> output_to_outfd;
std::array<const DofToQuad*, ninputs> input_dtq_maps;
std::array<const DofToQuad*, noutputs> output_dtq_maps;
};
}
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include "../../integrator_ctx.hpp"
#include "action.hpp"
namespace mfem::future
{
struct LocalQFBackend
{
template<
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeAction(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs)
{
return LocalQFImpl::Action(ctx, qfunc, inputs, outputs);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeAction(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs)
{
MFEM_ABORT("LocalQFBackend does not support derivative actions.");
}
};
}
+659
View File
@@ -0,0 +1,659 @@
#pragma once
#include "../fem/quadinterpolator.hpp"
#include "../util.hpp"
#include "general/enzyme.hpp"
namespace mfem::future
{
template <size_t N, size_t... Is>
constexpr std::array<bool, N> all_true_impl(std::index_sequence<Is...>)
{
return {{((void)Is, true)...}};
}
template <size_t N>
constexpr std::array<bool, N> all_true()
{
return all_true_impl<N>(std::make_index_sequence<N> {});
}
struct FieldBasis
{
// E-vector -> Q-vector
std::function<void(const Vector &, Vector &)> forward;
// Q-vector -> E-vector
std::function<void(const Vector &, Vector &)> transpose;
};
inline FieldBasis FromQI(const QuadratureInterpolator *qi,
QuadratureInterpolator::EvalFlags mode)
{
return
{
[qi, mode](const Vector &xe, Vector &xq)
{
qi->SetOutputLayout(QVectorLayout::byVDIM);
if (mode == QuadratureInterpolator::VALUES)
{
qi->Values(xe, xq);
}
else
{
qi->Derivatives(xe, xq);
}
},
[qi, mode](const Vector &yq, Vector &ye)
{
Vector empty;
qi->SetOutputLayout(QVectorLayout::byVDIM);
if (mode == QuadratureInterpolator::VALUES)
{
qi->AddMultTranspose(QuadratureInterpolator::VALUES, yq, empty, ye);
}
else
{
qi->AddMultTranspose(QuadratureInterpolator::DERIVATIVES, empty, yq, ye);
}
}
};
}
// QuadratureFunction identity copy
inline FieldBasis FromQF()
{
return
{
[](const Vector &xe, Vector &xq) { xq = xe; },
[](const Vector &yq, Vector &ye) { ye = yq; }
};
}
// User-defined parameter space B
inline FieldBasis FromPS(const Operator *B, const Operator *Bt)
{
return
{
[B](const Vector &xe, Vector &xq) { B->Mult(xe, xq); },
[Bt](const Vector &yq, Vector &ye) { Bt->Mult(yq, ye); }
};
}
inline FieldBasis FieldBasisFromWeight(const IntegrationRule &ir)
{
return
{
[&ir](const Vector &, Vector &xq)
{
const int nqp = ir.GetNPoints();
MFEM_ASSERT(xq.Size() % nqp == 0, "weight block has unexpected size");
const int ne = xq.Size() / nqp;
const real_t *wref = ir.GetWeights().Read();
for (int e = 0; e < ne; e++)
{
std::memcpy(xq.GetData() + e*nqp, wref, nqp*sizeof(real_t));
}
},
[](const Vector &, Vector &) {}
};
}
inline const FieldBasis GetFieldBasis(const FieldDescriptor &f,
const IntegrationRule &ir,
QuadratureInterpolator::EvalFlags mode)
{
return std::visit([&ir, &mode](auto && arg) -> FieldBasis
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, const FiniteElementSpace *>)
{
return FromQI(arg->GetQuadratureInterpolator(ir), mode);
}
else if constexpr (std::is_same_v<T, const ParFiniteElementSpace *>)
{
return FromQI(arg->GetQuadratureInterpolator(ir), mode);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return FromQF();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return FromPS(arg->GetB(), arg->GetBt());
}
else if constexpr (std::is_same_v<T, const IntegrationRule *>)
{
return FieldBasis{};
}
else
{
static_assert(dfem::always_false<T>, "internal error");
}
}, f.data);
}
template <typename fops_t, size_t nfops>
void create_fieldbases(
fops_t &fops,
const std::array<size_t, nfops> &fop_to_fd,
const std::vector<FieldDescriptor> &fds,
const IntegrationRule &ir,
std::array<FieldBasis, nfops> &bases)
{
constexpr_for<0, nfops>([&](auto i)
{
const auto fop = get<i>(fops);
using fop_t = std::decay_t<decltype(fop)>;
const auto fd = fds[fop_to_fd[i]];
constexpr QuadratureInterpolator::EvalFlags dummy_mode =
QuadratureInterpolator::VALUES;
if constexpr (is_identity_fop<fop_t>::value)
{
bases[i] = GetFieldBasis(fd, ir, dummy_mode);
}
else if constexpr (is_weight_fop<fop_t>::value)
{
bases[i] = FieldBasisFromWeight(ir);
}
else if constexpr (is_value_fop<fop_t>::value)
{
bases[i] = GetFieldBasis(fd, ir, QuadratureInterpolator::VALUES);
}
else if constexpr (is_gradient_fop<fop_t>::value)
{
bases[i] = GetFieldBasis(fd, ir, QuadratureInterpolator::DERIVATIVES);
}
});
}
template <typename fops_t, size_t nfops>
void check_consistency(
fops_t &fops,
const std::array<size_t, nfops> &fop_to_fd,
const std::vector<FieldDescriptor> &fields)
{
constexpr_for<0, nfops>([&](auto i)
{
const auto input = get<i>(fops);
using input_t = std::decay_t<decltype(input)>;
const auto fd = fields[fop_to_fd[i]];
if constexpr (is_identity_fop<input_t>::value)
{
MFEM_ASSERT(std::holds_alternative<const QuadratureFunction *>(fd.data),
"Identity FieldOperator requested on non "
"QuadratureFunction");
}
else if constexpr (is_weight_fop<input_t>::value)
{
}
else if constexpr (is_value_fop<input_t>::value)
{
MFEM_ASSERT(std::holds_alternative<const FiniteElementSpace *>(fd.data) ||
std::holds_alternative<const ParFiniteElementSpace *>(fd.data) ||
std::holds_alternative<const ParameterSpace *>(fd.data),
"Value FieldOperator requested on non "
"QuadratureFunction");
}
else if constexpr (is_gradient_fop<input_t>::value)
{
MFEM_ASSERT(std::holds_alternative<const FiniteElementSpace *>(fd.data) ||
std::holds_alternative<const ParFiniteElementSpace *>(fd.data),
"Value FieldOperator requested on non "
"QuadratureFunction");
}
});
}
template <size_t ninputs>
void interpolate(
const std::array<size_t, ninputs> &input_to_infd,
const std::array<FieldBasis, ninputs> &input_bases,
const std::vector<Vector *> &xe,
BlockVector &xq,
const std::array<bool, ninputs> &conditional = all_true<ninputs>())
{
constexpr_for<0, ninputs>([&](auto i)
{
if (!conditional.empty() && !conditional[i]) { return; }
input_bases[i].forward(*xe[input_to_infd[i]], xq.GetBlock(i));
});
}
template <size_t noutputs>
void integrate(
const std::array<size_t, noutputs> &output_to_outfd,
const std::array<FieldBasis, noutputs> &output_bases,
const BlockVector &yq,
std::vector<Vector *> &ye)
{
for (auto v : ye) { *v = 0.0; }
constexpr_for<0, noutputs>([&](auto i)
{
output_bases[i].transpose(yq.GetBlock(i), *ye[output_to_outfd[i]]);
});
}
namespace detail
{
template <typename T>
struct is_tensor_array : std::false_type {};
template <typename scalar_t, int... Dims>
struct is_tensor_array<tensor_array<scalar_t, Dims...>> : std::true_type {};
template <typename T>
struct is_tensor_array_mut : std::false_type {};
template <typename scalar_t, int... Dims>
struct is_tensor_array_mut<tensor_array<scalar_t, Dims...>> :
std::bool_constant<!std::is_const_v<scalar_t>> {};
template <typename ndarray_t>
inline void set_layout_default(ndarray_t &a)
{
if constexpr (ndarray_t::tensor_rank() == 0) { return; }
constexpr std::size_t nd = ndarray_t::rank();
constexpr std::size_t td = ndarray_t::tensor_rank();
std::array<std::size_t, nd + td> perm{};
for (std::size_t i = 0; i < td; i++) { perm[i] = nd + i; }
for (std::size_t i = 0; i < nd; i++) { perm[td + i] = i; }
a.set_layout(perm);
}
template <typename ndarray_t>
inline void set_layout(ndarray_t& a, const std::vector<int>& layout)
{
if constexpr (ndarray_t::tensor_rank() == 0) { return; }
constexpr std::size_t nd = ndarray_t::rank();
constexpr std::size_t td = ndarray_t::tensor_rank();
constexpr std::size_t N = nd + td;
// missing means default
if (layout.empty()) { set_layout_default(a); return; }
MFEM_VERIFY(layout.size() == N,
"layout size mismatch: expected " << N << " got " << layout.size());
// TODO: make a version of set_layout that takes `std::vector<int>`
std::array<std::size_t, N> perm{};
for (std::size_t i = 0; i < N; i++)
{
MFEM_VERIFY(layout[i] >= 0, "layout index must be >=0");
perm[i] = static_cast<std::size_t>(layout[i]);
}
a.set_layout(perm);
}
/// Primary template: intentionally undefined — gives a clear error for unsupported types.
template <typename T>
struct tensor_array_traits;
/// Matches tensor<scalar_t, sizes...>
template <typename scalar_t, int... sizes>
struct tensor_array_traits<tensor<scalar_t, sizes...>>
{
using scalar_type = scalar_t;
template <std::size_t ndims>
using array_type = tensor_ndarray<scalar_t, ndims, sizes...>;
};
/// Matches tensor_ndarray<scalar_t, ndims, tensor_sizes...>
template <typename scalar_t, int ndims, int... tensor_sizes>
struct tensor_array_traits<tensor_ndarray<scalar_t, ndims, tensor_sizes...>>
{
using scalar_type = scalar_t;
template <std::size_t N>
using array_type = tensor_ndarray<scalar_t, N, tensor_sizes...>;
};
/// Entry point: explicit tensor type T as template argument.
template <typename T, typename ptr_scalar_t, typename... dyn_sizes_t>
decltype(auto) make_tensor_array(ptr_scalar_t *ptr,
const std::vector<int>* layout,
dyn_sizes_t... dynamic_sizes)
{
using traits = tensor_array_traits<T>;
using array_t = typename traits::template array_type<sizeof...(dynamic_sizes)>;
auto a = array_t(ptr, {std::size_t(dynamic_sizes)...});
if (layout) { set_layout(a, *layout); }
else { set_layout_default(a); }
return a;
}
template <typename qfunc_t, typename inputs_t, typename outputs_t>
struct supports_tensor_array_qfunc
{
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
static constexpr int ninputs = tuple_size<inputs_t>::value;
static constexpr int noutputs = tuple_size<outputs_t>::value;
static constexpr int nparams = tuple_size<qf_param_ts>::value;
template <std::size_t... Is>
static constexpr bool InputsOk(std::index_sequence<Is...>)
{
return (is_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<Is, qf_param_ts>::type>>>::value && ...);
}
template <std::size_t... Is>
static constexpr bool OutputsOk(std::index_sequence<Is...>)
{
return (is_tensor_array_mut<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<ninputs + Is, qf_param_ts>::type>>>::value && ...);
}
static constexpr bool value =
(nparams == ninputs + noutputs) &&
InputsOk(std::make_index_sequence<ninputs> {}) &&
OutputsOk(std::make_index_sequence<noutputs> {});
};
template <typename qfunc_t, std::size_t... Is, std::size_t... Os>
inline void call_qfunc(
const qfunc_t &qfunc,
const BlockVector &xq,
BlockVector &yq,
int gnqp,
const std::array<std::vector<int>, sizeof...(Is)>& in_layouts,
const std::array<std::vector<int>, sizeof...(Os)>& out_layouts,
std::index_sequence<Is...>,
std::index_sequence<Os...>)
{
constexpr std::size_t ninputs = sizeof...(Is);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
auto inputs = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<Is, qf_param_ts>::type>>>(
xq.GetBlock(Is).Read(), &in_layouts[Is], gnqp)...);
auto outputs = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<ninputs + Os, qf_param_ts>::type>>>(
yq.GetBlock(Os).ReadWrite(), &out_layouts[Os], gnqp)...);
std::apply([&](auto&&... args)
{
qfunc(args...);
}, std::tuple_cat(inputs, outputs));
}
template <typename func_t, typename... arg_ts>
MFEM_HOST_DEVICE inline
auto qfunction_wrapper(const func_t &f, arg_ts...args)
{
return f(args...);
}
template <std::size_t derivative_id, std::size_t I, typename Tuple, std::size_t... Is>
constexpr std::array<bool, sizeof...(Is)>
make_activity_array(std::index_sequence<Is...>)
{
return { (std::decay_t<typename tuple_element<Is, Tuple>::type>::GetFieldId() == derivative_id)... };
}
template <std::size_t derivative_id, typename inputs_t, std::size_t... Is>
constexpr auto make_activity_map_impl(std::index_sequence<Is...>)
{
constexpr std::size_t N = sizeof...(Is);
if constexpr (N == 0)
return std::array<bool, 0> {};
return make_activity_array<derivative_id, 0, inputs_t>
(std::make_index_sequence<N> {});
}
template <std::size_t derivative_id, typename inputs_t>
constexpr auto make_activity_map(inputs_t)
{
return make_activity_map_impl<derivative_id, inputs_t>(
std::make_index_sequence<tuple_size<inputs_t>::value> {});
}
namespace enzyme_detail
{
template <auto wrapper_fn, typename qf_return_t, typename... AccArgs>
__attribute__((always_inline)) inline void
do_enzyme_call(AccArgs... acc)
{
__enzyme_fwddiff<qf_return_t>(wrapper_fn, acc...);
}
template <auto wrapper_fn, typename qf_return_t,
size_t CurO, size_t NO,
typename primals_t, typename derivs_t,
typename... AccArgs>
__attribute__((always_inline)) inline void
process_outputs(primals_t &primals, derivs_t &derivs, AccArgs... acc)
{
if constexpr (CurO == NO)
{
do_enzyme_call<wrapper_fn, qf_return_t>(acc...);
}
else
{
process_outputs<wrapper_fn, qf_return_t, CurO + 1, NO>(
primals, derivs,
acc...,
enzyme_dupnoneed,
&std::get<CurO>(primals),
&std::get<CurO>(derivs));
}
}
template <auto wrapper_fn, typename qf_return_t,
size_t CurI, size_t NI, bool... ActivityMap,
typename inputs_t, typename shadows_t,
typename primals_t, typename derivs_t,
typename... AccArgs>
__attribute__((always_inline)) inline void
process_inputs(inputs_t &inputs, shadows_t &shadows,
primals_t &primals, derivs_t &derivs,
AccArgs... acc)
{
if constexpr (CurI == NI)
{
constexpr size_t NO = std::tuple_size_v<primals_t>;
process_outputs<wrapper_fn, qf_return_t, 0, NO>(
primals, derivs, acc...);
}
else
{
constexpr bool active =
std::array<bool, sizeof...(ActivityMap)> {ActivityMap...} [CurI];
if constexpr (active)
{
std::cout << "Input[" << CurI << "]: ACTIVE (enzyme_dup)\n"
<< " primal ptr type: "
<< get_type_name<decltype(&std::get<CurI>(inputs))>() << "\n"
<< " shadow ptr type: "
<< get_type_name<decltype(&std::get<CurI>(shadows))>() << "\n";
}
else
{
std::cout << "Input[" << CurI << "]: INACTIVE (enzyme_const)\n"
<< " primal ptr type: "
<< get_type_name<decltype(&std::get<CurI>(inputs))>() << "\n";
}
if constexpr (active)
{
process_inputs<wrapper_fn, qf_return_t, CurI + 1, NI, ActivityMap...>(
inputs, shadows, primals, derivs,
acc...,
enzyme_dup,
&std::get<CurI>(inputs),
&std::get<CurI>(shadows));
}
else
{
process_inputs<wrapper_fn, qf_return_t, CurI + 1, NI, ActivityMap...>(
inputs, shadows, primals, derivs,
acc...,
enzyme_const,
&std::get<CurI>(inputs));
}
}
}
} // namespace enzyme_detail
template <size_t derivative_id, typename qfunc_t, typename inputs_t, typename outputs_t,
std::size_t... Is, std::size_t... Os>
inline void enzyme_fwddiff(
qfunc_t &qfunc,
const BlockVector &xq,
const BlockVector &shadow_xq,
BlockVector &yq,
const int &gnqp,
const std::array<std::vector<int>, sizeof...(Is)>& in_layouts,
const std::array<std::vector<int>, sizeof...(Os)>& out_layouts,
std::index_sequence<Is...>,
std::index_sequence<Os...>)
{
#ifdef MFEM_USE_ENZYME
constexpr std::size_t ninputs = sizeof...(Is);
constexpr std::size_t noutputs = sizeof...(Os);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
using qf_return_t = typename qf_signature::return_t;
constexpr auto activity_map = make_activity_map<derivative_id>(inputs_t{});
static_assert(activity_map.size() == ninputs, "activity map size mismatch");
std::cout << "activity_map: ";
for (const auto &v : activity_map)
{
std::cout << v << " ";
}
std::cout << "\n";
auto inputs = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<Is, qf_param_ts>::type>>>(
xq.GetBlock(Is).Read(), &in_layouts[Is], gnqp)...);
auto shadows = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<Is, qf_param_ts>::type>>>(
shadow_xq.GetBlock(Is).Read(), &in_layouts[Is], gnqp)...);
std::array<Vector, noutputs> primal_storage;
((primal_storage[Os].SetSize(yq.GetBlock(Os).Size())), ...);
auto primals_out = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<ninputs + Os, qf_param_ts>::type>>>(
primal_storage[Os].ReadWrite(), &out_layouts[Os], gnqp)...);
auto derivs_out = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<ninputs + Os, qf_param_ts>::type>>>(
yq.GetBlock(Os).ReadWrite(), &out_layouts[Os], gnqp)...);
using wrapper_fn_t = qf_return_t (*)(
const qfunc_t &,
std::remove_reference_t<decltype(std::get<Is>(inputs))>...,
std::remove_reference_t<decltype(std::get<Os>(primals_out))>...);
constexpr wrapper_fn_t wrapper_fn =
qfunction_wrapper<qfunc_t,
std::remove_reference_t<decltype(std::get<Is>(inputs))>...,
std::remove_reference_t<decltype(std::get<Os>(primals_out))>...>;
// wrapper_fn travels as a non-type template parameter throughout without
// being stored.
enzyme_detail::process_inputs<
wrapper_fn,
qf_return_t,
0,
ninputs,
activity_map[Is]...
>(inputs, shadows,
primals_out, derivs_out,
enzyme_const, &qfunc // seed: qfunc is always inactive
);
#else
MFEM_ABORT("enzyme_fwddiff requires MFEM_USE_ENZYME");
#endif
}
} // namespace detail
// Create quadrature function fop to fields map
template <typename fops_t, size_t N = tuple_size<fops_t>::value, size_t M>
void create_fop_to_fd(const fops_t &fops,
const std::vector<FieldDescriptor> &fields,
std::array<size_t, M> &fop_to_fd)
{
static_assert(N == M, "sizes must match");
constexpr_for<0, N>([&](auto i)
{
const auto fop = get<i>(fops);
fop_to_fd[i] = std::numeric_limits<size_t>::max();
for (size_t j = 0; j < fields.size(); j++)
{
// TODO: output.GetFieldId() should probably store/return size_t
if (static_cast<int>(fields[j].id) == fop.GetFieldId())
{
fop_to_fd[i] = j;
}
}
// Handle Weight type. There is no FieldDescriptor for the weight.
// TODO: Create weight descriptor for the weight for internal use?
// TODO: this is a hack...
if (is_weight_fop<std::remove_cv_t<decltype(fop)>>::value)
{
fop_to_fd[i] = 0;
}
else if (fop_to_fd[i] == std::numeric_limits<size_t>::max())
{
MFEM_ABORT("not found");
}
});
}
template <typename fops_t, size_t nfops>
void create_qlayouts(const fops_t &fops,
const std::unordered_map<std::type_index, std::vector<int>> &a,
std::array<std::vector<int>, nfops> &b)
{
constexpr_for<0, nfops>([&](auto i)
{
using fop_t =
std::remove_cv_t<std::remove_reference_t<decltype(get<i>(fops))>>;
auto it = a.find(std::type_index(typeid(fop_t)));
if (it != a.end()) { b[i] = it->second; }
else { b[i].clear(); }
});
}
}
+96 -21
View File
@@ -11,44 +11,119 @@
#include "doperator.hpp"
#include <algorithm>
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
void DifferentiableOperator::SetParameters(std::vector<Vector *> p) const
DifferentiableOperator::DifferentiableOperator(
const std::vector<FieldDescriptor> &infds,
const std::vector<FieldDescriptor> &outfds,
const ParMesh &mesh) :
Operator(),
mesh(mesh),
infds(infds),
outfds(outfds)
{
MFEM_ASSERT(parameters.size() == p.size(),
"number of parameters doesn't match descriptors");
for (size_t i = 0; i < parameters.size(); i++)
unionfds.clear();
unionfds.insert(unionfds.end(), infds.begin(), infds.end());
unionfds.insert(unionfds.end(), outfds.begin(), outfds.end());
std::sort(unionfds.begin(), unionfds.end());
auto last = std::unique(unionfds.begin(), unionfds.end());
unionfds.erase(last, unionfds.end());
infields_l.resize(infds.size());
for (size_t i = 0; i < infds.size(); i++)
{
p[i]->Read();
parameters_l[i] = *p[i];
infields_l[i] = new Vector(GetVSize(infds[i]));
}
infields_e.resize(infds.size());
}
DifferentiableOperator::DifferentiableOperator(
const std::vector<FieldDescriptor> &solutions,
const std::vector<FieldDescriptor> &parameters,
const ParMesh &mesh) :
mesh(mesh),
solutions(solutions),
parameters(parameters)
void DifferentiableOperator::SetMultLevel(MultLevel level)
{
fields.resize(solutions.size() + parameters.size());
fields_e.resize(fields.size());
solutions_l.resize(solutions.size());
parameters_l.resize(parameters.size());
mult_level = level;
}
for (size_t i = 0; i < solutions.size(); i++)
void DifferentiableOperator::Mult(const Vector &x, Vector &y) const
{
MFEM_ASSERT(!action_callbacks.empty(),
"no integrators have been set");
MFEM_ASSERT(dynamic_cast<const BlockVector*>(&x),
"x needs to be a BlockVector");
MFEM_ASSERT(dynamic_cast<const BlockVector*>(&y),
"y needs to be a BlockVector");
const auto &bx = static_cast<const BlockVector &>(x);
auto &by = static_cast<BlockVector &>(y);
Mult(bx, by);
}
void DifferentiableOperator::DisableTensorProductStructure(bool disable)
{
use_tensor_product_structure = !disable;
}
std::shared_ptr<DerivativeOperator> DifferentiableOperator::GetDerivative(
size_t derivative_id, const Vector &x)
{
MFEM_ASSERT(derivative_action_callbacks.find(derivative_id) !=
derivative_action_callbacks.end(),
"no derivative action has been found for ID " << derivative_id);
const size_t dfidx = FindIdx(derivative_id, infds);
// Get transpose callbacks if available, otherwise pass empty vector
std::vector<derivative_action_t> transpose_callbacks;
auto it = daction_transpose_callbacks.find(derivative_id);
if (it != daction_transpose_callbacks.end())
{
fields[i] = solutions[i];
transpose_callbacks = it->second;
}
for (size_t i = 0; i < parameters.size(); i++)
return std::make_shared<DerivativeOperator>(
height,
GetTrueVSize(infds[dfidx]),
derivative_action_callbacks[derivative_id],
transpose_callbacks,
infds[dfidx],
x,
infds,
outfds);
}
std::shared_ptr<DerivativeOperator> DifferentiableOperator::GetDerivative(
size_t derivative_id, const MultiVector &x)
{
MFEM_ASSERT(derivative_action_callbacks.find(derivative_id) !=
derivative_action_callbacks.end(),
"no derivative action has been found for ID " << derivative_id);
const size_t dfidx = FindIdx(derivative_id, infds);
// Get transpose callbacks if available, otherwise pass empty vector
std::vector<derivative_action_t> transpose_callbacks;
auto it = daction_transpose_callbacks.find(derivative_id);
if (it != daction_transpose_callbacks.end())
{
fields[i + solutions.size()] = parameters[i];
transpose_callbacks = it->second;
}
return std::make_shared<DerivativeOperator>(
height,
GetTrueVSize(infds[dfidx]),
derivative_action_callbacks[derivative_id],
transpose_callbacks,
infds[dfidx],
x,
infds,
outfds);
}
#endif // MFEM_USE_MPI
+236 -896
View File
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "../fespace.hpp"
#include "parameterspace.hpp"
namespace mfem::future
{
/// @brief FieldDescriptor struct
///
/// This struct is used to store information about a field.
struct FieldDescriptor
{
using data_variant_t =
std::variant<const FiniteElementSpace *,
const ParFiniteElementSpace *,
const QuadratureFunction *,
const ParameterSpace *>;
/// Field ID
std::size_t id;
/// Field variant
data_variant_t data;
/// Default constructor
FieldDescriptor() :
id(SIZE_MAX), data(data_variant_t{}) {}
/// Constructor
template <typename T>
FieldDescriptor(std::size_t field_id, const T* v) :
id(field_id), data(v) {}
bool operator==(const FieldDescriptor& other) const
{
return id == other.id;
}
bool operator<(const FieldDescriptor& other) const
{
return id < other.id;
}
friend void swap(FieldDescriptor& a, FieldDescriptor& b)
{
using std::swap;
swap(a.id, b.id);
swap(a.data, b.data);
}
};
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "util.hpp"
namespace mfem::future
{
struct IntegratorContext
{
const ParMesh &mesh;
const Array<int> *elem_attr;
Array<int> attr;
int nentities;
const std::vector<FieldDescriptor> &infds;
const std::vector<FieldDescriptor> &outfds;
const std::vector<FieldDescriptor> &unionfds;
const IntegrationRule &ir;
std::unordered_map<std::type_index, std::vector<int>> &in_qlayouts;
std::unordered_map<std::type_index, std::vector<int>> &out_qlayouts;
};
}
+94 -9
View File
@@ -9,8 +9,23 @@
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
// #define NVTX_COLOR nvtx::kPeru
#include "util.hpp"
#include "fem/kernels.hpp"
///////////////////////////////////////////////////////////////////////////////
template <class T>
inline std::enable_if_t<!std::numeric_limits<T>::is_integer, bool>
AlmostEq(T x, T y, T tolerance = 15.0 * std::numeric_limits<T>::epsilon())
{
const T neg = std::abs(x - y);
constexpr T min = std::numeric_limits<T>::min();
constexpr T eps = std::numeric_limits<T>::epsilon();
const T min_abs = std::min(std::abs(x), std::abs(y));
if (std::abs(min_abs) == 0.0) { return neg < eps; }
return (neg / (1.0 + std::max(min, min_abs))) < tolerance;
}
namespace mfem::future
{
@@ -30,6 +45,7 @@ void map_field_to_quadrature_data_tensor_product_3d(
if constexpr (is_value_fop<std::decay_t<field_operator_t>>::value)
{
dbg("Value");
auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e[0], d1d, d1d, d1d, vdim);
@@ -94,10 +110,11 @@ void map_field_to_quadrature_data_tensor_product_3d(
else if constexpr (
is_gradient_fop<std::decay_t<field_operator_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
// dbg("Gradient");
const auto [q1d, B_dim, d1d] = B.GetShape();
const int vdim = input.vdim;
const int dim = input.dim;
const auto field = Reshape(&field_e[0], d1d, d1d, d1d, vdim);
const auto field = Reshape(&std::as_const(field_e[0]), d1d, d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, dim, q1d, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, d1d, q1d);
@@ -106,7 +123,30 @@ void map_field_to_quadrature_data_tensor_product_3d(
auto s3 = Reshape(&scratch_mem[3](0), d1d, q1d, q1d);
auto s4 = Reshape(&scratch_mem[4](0), d1d, q1d, q1d);
for (int vd = 0; vd < vdim; vd++)
// constexpr int MQ1 = T_Q1D > 0 ? T_Q1D : 8;
// static constexpr int DIM = 3;
// MFEM_VERIFY(q1d <= MQ1, "q1d > MQ1");
// MFEM_SHARED real_t smem[MQ1][MQ1];
// kernels::internal::d_regs3d_t<DIM, MQ1> r0, r1;
// real_t sB[MQ1][MQ1], sG[MQ1][MQ1];
/*
{
assert(B_dim == 1 && "1D B required!");
kernels::internal::LoadMatrix(d1d, q1d, B, sB);
kernels::internal::LoadMatrix(d1d, q1d, G, sG);
for (int qx = 0; qx < q1d; qx++)
{
for (int dx = 0; dx < d1d; dx++)
{
assert(AlmostEq(B(qx, 0, dx), sB[dx][qx]));
assert(AlmostEq(G(qx, 0, dx), sG[dx][qx]));
}
}
}*/
for (int c = 0; c < vdim; c++)
{
MFEM_FOREACH_THREAD(dz, z, d1d)
{
@@ -117,7 +157,7 @@ void map_field_to_quadrature_data_tensor_product_3d(
real_t uv[2] = {0.0, 0.0};
for (int dx = 0; dx < d1d; dx++)
{
const real_t f = field(dx, dy, dz, vd);
const real_t f = field(dx, dy, dz, c);
uv[0] += f * B(qx, 0, dx);
uv[1] += f * G(qx, 0, dx);
}
@@ -163,19 +203,59 @@ void map_field_to_quadrature_data_tensor_product_3d(
uvw[1] += s3(dz, qy, qx) * B(qz, 0, dz);
uvw[2] += s4(dz, qy, qx) * G(qz, 0, dz);
}
fqp(vd, 0, qx, qy, qz) = uvw[0];
fqp(vd, 1, qx, qy, qz) = uvw[1];
fqp(vd, 2, qx, qy, qz) = uvw[2];
fqp(c, 0, qx, qy, qz) = uvw[0];
fqp(c, 1, qx, qy, qz) = uvw[1];
fqp(c, 2, qx, qy, qz) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
}
/*
{
for (int c = 0; c < vdim; c++)
{
kernels::internal::LoadDofs3d(d1d, c, field, r0);
for (int d = 0; d < DIM; d++)
{
for (int dz = 0; dz < d1d; dz++)
{
for (int dy = 0; dy < d1d; dy++)
{
for (int dx = 0; dx < d1d; dx++)
{
const real_t f = field(dx, dy, dz, c);
assert(AlmostEq(f, r0[d][dz][dy][dx]));
}
}
}
}
kernels::internal::Grad3d(d1d, q1d, smem, sB, sG, r0, r1, c);
for (int qz = 0; qz < q1d; qz++)
{
for (int qy = 0; qy < q1d; qy++)
{
for (int qx = 0; qx < q1d; qx++)
{
if (!AlmostEq(fqp(c, d, qx, qy, qz), r1[d][qz][qy][qx]))
{
dbg("\x1b[31m[{}:d] {} {}", c, fqp(c, d, qx, qy, qz), r1[d][qz][qy][qx]);
dbg("❌❌❌"), std::exit(EXIT_FAILURE);
}
}
}
}
}
// dbg("✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅");//, std::exit(EXIT_SUCCESS);
}*/
}
// TODO: Create separate function for clarity
else if constexpr (
std::is_same_v<std::decay_t<field_operator_t>, Weight>)
{
// dbg("None");
const int num_qp = integration_weights.GetShape()[0];
// TODO: eeek
const int q1d = (int)floor(std::pow(num_qp, 1.0/input.dim) + 0.5);
@@ -518,6 +598,9 @@ void map_fields_to_quadrature_data(
const int &dimension,
const bool &use_sum_factorization = false)
{
// dbg();
assert(use_sum_factorization && "❌ use_sum_factorization required");
// When the input_to_field map returns -1, this means the requested input
// is the integration weight. Weights don't have a user defined field
// attached to them and we create a dummy field which is not accessed
@@ -578,6 +661,7 @@ void map_field_to_quadrature_data_conditional(
const int &dimension,
const bool &use_sum_factorization = false)
{
assert(false && "❌ condition not implemented");
if (condition)
{
if (use_sum_factorization)
@@ -619,6 +703,7 @@ void map_fields_to_quadrature_data_conditional(
const std::array<bool, num_inputs> &conditions,
const bool &use_sum_factorization = false)
{
assert(false && "❌ condition not implemented");
for_constexpr<num_inputs>([&](auto i)
{
map_field_to_quadrature_data_conditional(
@@ -627,7 +712,7 @@ void map_fields_to_quadrature_data_conditional(
});
}
template <size_t num_inputs, typename field_operator_ts>
template <int T_Q1D, size_t num_inputs, typename field_operator_ts>
MFEM_HOST_DEVICE
void map_direction_to_quadrature_data_conditional(
std::array<DeviceTensor<2>, num_inputs> &directions_qp,
@@ -660,7 +745,7 @@ void map_direction_to_quadrature_data_conditional(
}
else if (dimension == 3)
{
map_field_to_quadrature_data_tensor_product_3d(
map_field_to_quadrature_data_tensor_product_3d<T_Q1D>(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights, scratch_mem);
}
+18 -3
View File
@@ -43,7 +43,7 @@ public:
/// Get spatial dimension
///
/// returns always 1.
int Dimension() const
constexpr int Dimension() const
{
return 1;
}
@@ -74,11 +74,14 @@ public:
return elem_restr.get();
}
virtual const Operator* GetB() const = 0;
virtual const Operator* GetBt() const = 0;
protected:
int vdim;
DofToQuad dtq;
mutable std::unique_ptr<Operator> prolongation;
mutable std::unique_ptr<Operator> elem_restr;
mutable std::unique_ptr<Operator> prolongation, elem_restr, B, Bt;
};
/// @brief Uniform parameter space
@@ -122,6 +125,18 @@ public:
return lsize;
}
const Operator* GetB() const override
{
MFEM_ABORT("UniformParameterSpace does not support GetB");
return nullptr;
}
const Operator* GetBt() const override
{
MFEM_ABORT("UniformParameterSpace does not support GetBt");
return nullptr;
}
private:
/// T-vector size
int tsize;
+2
View File
@@ -243,6 +243,8 @@ void process_qf_arg(
}
}
// const tensor<real_t, DIM> ∇u
// const tensor<real_t, DIM, DIM> D (PA_DATA)
template <typename arg_type>
MFEM_HOST_DEVICE inline
void process_qf_arg(const DeviceTensor<2> &u, arg_type &arg, int qp)
+76
View File
@@ -0,0 +1,76 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "tuple.hpp"
#include "../linalg/tensor.hpp"
using namespace mfem::future;
using mfem::future::tensor;
// Helper to add dimension to tensor type
template<typename T, int qp>
struct AddQPDimension;
// Specialization for tensor<real_t, dim>
template<typename real_t, int dim, int qp>
struct AddQPDimension<tensor<real_t, dim>, qp>
{
using type = tensor<real_t, dim, qp>;
};
// Specialization for tensor<real_t, dim, dim>
template<typename real_t, int dim, int qp>
struct AddQPDimension<tensor<real_t, dim, dim>, qp>
{
using type = tensor<real_t, dim, dim, qp>;
};
// Specialization for real_t (transforms to tensor<real_t, qp>)
template<typename real_t, int qp>
struct AddQPDimension
{
using type = tensor<real_t, qp>;
};
// Helper to transform tuple
template<typename Tuple, int qp>
struct TransformTupleQP {};
// Specialization for mfem::future::tuple
template<int qp, typename... Types>
struct TransformTupleQP<mfem::future::tuple<Types...>, qp>
{
using type = mfem::future::tuple<typename AddQPDimension<Types, qp>::type...>;
};
template<int qp, typename... Types>
struct TransformTupleQP<std::tuple<Types...>, qp>
{
using type = std::tuple<typename AddQPDimension<Types, qp>::type...>;
};
// Function to transform tuple type with qp dimension
template<int qp, typename qf_param_ts>
struct add_qp_dimension
{
using type = typename TransformTupleQP<qf_param_ts, qp>::type;
};
// Helper alias template for cleaner usage
template<int qp, typename qf_param_ts>
using add_qp_dimension_t = typename add_qp_dimension<qp, qf_param_ts>::type;
// ...AddDomainIntegrator...
// {
// constexpr int Q1D = 4;
// using qf_param_augmentd_ts = add_qp_dimension_t<Q1D, decay_tuple<qf_param_ts>>;
// }
+533 -50
View File
@@ -21,6 +21,7 @@
#include <type_traits>
#include <numeric>
#include <iomanip>
#include <typeindex>
#include "../../general/communication.hpp"
#include "../../general/forall.hpp"
@@ -28,13 +29,19 @@
#include "../fe/fe_base.hpp"
#include "../fespace.hpp"
#include "../pfespace.hpp"
#include "../qfunction.hpp"
#include "../../mesh/mesh.hpp"
#include "../../linalg/dtensor.hpp"
#include "../quadinterpolator.hpp"
#include "fielddescriptor.hpp"
#include "fieldoperator.hpp"
#include "parameterspace.hpp"
#include "tuple.hpp"
#undef NVTX_COLOR
#define NVTX_COLOR ::nvtx::kLightBlue
namespace mfem::future
{
@@ -75,7 +82,7 @@ constexpr void for_constexpr(lambda&& f,
}
template <typename lambda>
constexpr void for_constexpr(lambda&& f, std::integer_sequence<std::size_t>) {}
constexpr void for_constexpr(lambda&&, std::integer_sequence<std::size_t>) {}
template <int... n, typename lambda>
constexpr void for_constexpr(lambda&& f)
@@ -84,7 +91,7 @@ constexpr void for_constexpr(lambda&& f)
}
template <typename lambda, typename arg_t>
constexpr void for_constexpr_with_arg(lambda&& f, arg_t&& arg,
constexpr void for_constexpr_with_arg(lambda&&, arg_t&&,
std::integer_sequence<std::size_t>)
{
// Base case - do nothing for empty sequence
@@ -108,6 +115,16 @@ constexpr void for_constexpr_with_arg(lambda&& f, arg_t&& arg)
indices{});
}
template <auto start, auto end, auto inc = 1, typename F>
constexpr void constexpr_for(F&& f)
{
if constexpr (start < end)
{
f(std::integral_constant<decltype(start), start>());
constexpr_for<start + inc, end, inc>(f);
}
}
template <std::size_t I, typename Tuple, std::size_t... Is>
std::array<bool, sizeof...(Is)>
make_dependency_array(const Tuple& inputs, std::index_sequence<Is...>)
@@ -444,6 +461,21 @@ struct create_function_signature<output_t (*)(input_ts...)>
using type = FunctionSignature<output_t(input_ts...)>;
};
template <typename...>
using void_t = void;
template <typename T, typename = void>
struct get_function_signature
{
using type = typename create_function_signature<T>::type;
};
template <typename T>
struct get_function_signature<T, void_t<decltype(&T::operator())>>
{
using type = typename create_function_signature<decltype(&T::operator())>::type;
};
template <typename T>
constexpr int GetFieldId()
{
@@ -538,38 +570,12 @@ auto get_marked_entries(
/// @param t the tuple to filter fields from.
/// @returns a tuple containing only the fields with field IDs not equal to -1.
template <typename... Ts>
constexpr auto filter_fields(const std::tuple<Ts...>& t)
constexpr auto filter_fields(const std::tuple<Ts...>&)
{
return std::tuple_cat(
std::conditional_t<Ts::GetFieldId() != -1, std::tuple<Ts>, std::tuple<>> {}...);
}
/// @brief FieldDescriptor struct
///
/// This struct is used to store information about a field.
struct FieldDescriptor
{
using data_variant_t =
std::variant<const FiniteElementSpace *,
const ParFiniteElementSpace *,
const ParameterSpace *>;
/// Field ID
std::size_t id;
/// Field variant
data_variant_t data;
/// Default constructor
FieldDescriptor() :
id(SIZE_MAX), data(data_variant_t{}) {}
/// Constructor
template <typename T>
FieldDescriptor(std::size_t field_id, const T* v) :
id(field_id), data(v) {}
};
namespace dfem
{
template <class... T> constexpr bool always_false = false;
@@ -599,7 +605,7 @@ struct ThreadBlocks
#if defined(MFEM_USE_CUDA_OR_HIP)
template <typename func_t>
__global__ void forall_kernel_shmem(func_t f, int n)
__global__ void forall_kernel_extern_shmem(func_t f, int n)
{
int i = blockIdx.x;
extern __shared__ real_t shmem[];
@@ -608,23 +614,48 @@ __global__ void forall_kernel_shmem(func_t f, int n)
f(i, shmem);
}
}
template <typename func_t>
__global__ void forall_kernel_static_smem(func_t f, int n)
{
int i = blockIdx.x;
if (i >= n) { return; }
f(i, nullptr);
}
template <int MAX_THREADS_PER_BLOCK, typename func_t>
__global__
MFEM_LAUNCH_BOUNDS(MAX_THREADS_PER_BLOCK)
static void forall_kernel_static_smem_launch_bounds(func_t f, int n)
{
for (int k = blockIdx.x; k < n; k += gridDim.x) { f(k, nullptr); }
}
#endif
template <typename func_t>
template </*typename kernel_tag,*/ typename func_t>
void forall(func_t f,
const int &N,
const ThreadBlocks &blocks,
int num_shmem = 0,
[[maybe_unused]] const ThreadBlocks &blocks,
[[maybe_unused]] int num_shmem = 0,
real_t *shmem = nullptr)
{
db1();
if (Device::Allows(Backend::CUDA_MASK) ||
Device::Allows(Backend::HIP_MASK))
{
#if defined(MFEM_USE_CUDA_OR_HIP)
// int gridsize = (N + Z - 1) / Z;
int num_bytes = num_shmem * sizeof(decltype(shmem));
db1("num_bytes:{}", num_bytes);
db1("block: {}x{}x{}", blocks.x, blocks.y, blocks.z);
dim3 block_size(blocks.x, blocks.y, blocks.z);
forall_kernel_shmem<<<N, block_size, num_bytes>>>(f, N);
// ForallKernel<kernel_tag>::run<<<N, block_size, num_bytes>>>(f, N);
if (num_bytes > 0)
{
forall_kernel_extern_shmem<<<N, block_size, num_bytes>>>(f, N);
}
else
{
forall_kernel_static_smem<<<N, block_size>>>(f, N);
}
#if defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaGetLastError());
#elif defined(MFEM_USE_HIP)
@@ -635,6 +666,7 @@ void forall(func_t f,
}
else if (Device::Allows(Backend::CPU_MASK))
{
db1("CPU_MASK");
MFEM_ASSERT(!((bool)num_shmem != (bool)shmem),
"Backend::CPU needs a pre-allocated shared memory block");
for (int i = 0; i < N; i++)
@@ -648,6 +680,69 @@ void forall(func_t f,
}
}
namespace dfem
{
template <int MAX_THREADS_PER_BLOCK = 0, typename func_t>
void forall(func_t f,
const int &N,
[[maybe_unused]] const ThreadBlocks &blocks,
[[maybe_unused]] int num_shmem = 0,
real_t *shmem = nullptr)
{
db1();
if (Device::Allows(Backend::CUDA_MASK) ||
Device::Allows(Backend::HIP_MASK))
{
#if defined(MFEM_USE_CUDA_OR_HIP)
int num_bytes = num_shmem * sizeof(decltype(shmem));
db1("num_bytes:{}", num_bytes);
db1("block: {}x{}x{}", blocks.x, blocks.y, blocks.z);
db1("MAX_THREADS_PER_BLOCK:{}", MAX_THREADS_PER_BLOCK);
dim3 block_size(blocks.x, blocks.y, blocks.z);
if constexpr (MAX_THREADS_PER_BLOCK > 0)
{
assert(num_bytes == 0);
forall_kernel_static_smem_launch_bounds
<MAX_THREADS_PER_BLOCK><<<N, block_size>>> (f, N);
}
else
{
static_assert(MAX_THREADS_PER_BLOCK == 0);
if (num_bytes == 0)
{
forall_kernel_static_smem<<<N, block_size>>>(f, N);
}
else
{
forall_kernel_extern_shmem<<<N, block_size, num_bytes>>>(f, N);
}
}
#if defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaGetLastError());
#elif defined(MFEM_USE_HIP)
MFEM_GPU_CHECK(hipGetLastError());
#endif
// MFEM_DEVICE_SYNC; // ⚠️
#endif
}
else if (Device::Allows(Backend::CPU_MASK))
{
db1("CPU_MASK");
MFEM_ASSERT(!((bool)num_shmem != (bool)shmem),
"Backend::CPU needs a pre-allocated shared memory block");
for (int i = 0; i < N; i++)
{
f(i, shmem);
}
}
else
{
MFEM_ABORT("no compute backend available");
}
}
}
/// @todo To be removed.
class FDJacobian : public Operator
{
@@ -772,6 +867,10 @@ int GetVSize(const FieldDescriptor &f)
{
return arg->GetVSize();
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return arg->Size();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetVSize();
@@ -810,6 +909,10 @@ void GetElementVDofs(const FieldDescriptor &f, int el, Array<int> &vdofs)
{
arg->GetElementVDofs(el, vdofs);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
MFEM_ABORT("internal error");
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
MFEM_ABORT("internal error");
@@ -844,6 +947,10 @@ int GetTrueVSize(const FieldDescriptor &f)
{
return arg->GetTrueVSize();
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return arg->Size();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetTrueVSize();
@@ -874,6 +981,10 @@ int GetVDim(const FieldDescriptor &f)
{
return arg->GetVDim();
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return arg->GetVDim();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetVDim();
@@ -909,6 +1020,10 @@ int GetDimension(const FieldDescriptor &f)
return arg->GetMesh()->Dimension() - 1;
}
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return arg->GetSpace()->GetMesh()->Dimension();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->Dimension();
@@ -921,6 +1036,36 @@ int GetDimension(const FieldDescriptor &f)
}, f.data);
}
inline
std::variant<const QuadratureInterpolator *, const Operator *>get_qinterp(
const FieldDescriptor &f,
const IntegrationRule &ir)
{
return std::visit([&ir](auto && arg) -> const QuadratureInterpolator*
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, const FiniteElementSpace *> ||
std::is_same_v<T, const ParFiniteElementSpace *>)
{
return arg->GetQuadratureInterpolator(ir);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
// QuadratureFunction doesn't need a QuadratureInterpolator
return nullptr;
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return nullptr;
}
else
{
static_assert(dfem::always_false<T>, "internal error");
}
return nullptr; // Unreachable, but avoids compiler warning
}, f.data);
}
/// @brief Get the prolongation operator for a field descriptor.
///
@@ -929,6 +1074,7 @@ int GetDimension(const FieldDescriptor &f)
inline
const Operator *get_prolongation(const FieldDescriptor &f)
{
NVTX("get P");
return std::visit([](auto&& arg) -> const Operator*
{
using T = std::decay_t<decltype(arg)>;
@@ -937,6 +1083,10 @@ const Operator *get_prolongation(const FieldDescriptor &f)
{
return arg->GetProlongationMatrix();
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return nullptr;
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetProlongationMatrix();
@@ -959,6 +1109,7 @@ inline
const Operator *get_element_restriction(const FieldDescriptor &f,
ElementDofOrdering o)
{
NVTX("get ER");
return std::visit([&o](auto&& arg) -> const Operator*
{
using T = std::decay_t<decltype(arg)>;
@@ -967,6 +1118,10 @@ const Operator *get_element_restriction(const FieldDescriptor &f,
{
return arg->GetElementRestriction(o);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return nullptr;
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetElementRestriction(o);
@@ -994,6 +1149,7 @@ const Operator *get_face_restriction(const FieldDescriptor &f,
FaceType ft,
L2FaceValues m)
{
NVTX("get FR");
return std::visit([&o, &ft, &m](auto&& arg) -> const Operator*
{
using T = std::decay_t<decltype(arg)>;
@@ -1002,6 +1158,11 @@ const Operator *get_face_restriction(const FieldDescriptor &f,
{
return arg->GetFaceRestriction(o, ft, m);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
// QuadratureFunction does not support face restrictions
MFEM_ABORT("internal error");
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
// ParameterSpace does not support face restrictions
@@ -1027,6 +1188,7 @@ inline
const Operator *get_restriction(const FieldDescriptor &f,
const ElementDofOrdering &o)
{
NVTX("get R");
if constexpr (std::is_same_v<entity_t, Entity::Element>)
{
return get_element_restriction(f, o);
@@ -1052,12 +1214,14 @@ inline std::tuple<std::function<void(const Vector&, Vector&)>, int>
get_restriction_transpose(
const FieldDescriptor &f,
const ElementDofOrdering &o,
const fop_t &fop)
[[maybe_unused]] const fop_t &fop)
{
NVTX("get R^T");
if constexpr (is_sum_fop<fop_t>::value)
{
auto RT = [=](const Vector &v_e, Vector &v_l)
{
NVTX("R^T sum");
v_l += v_e;
};
return std::make_tuple(RT, 1);
@@ -1067,6 +1231,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)
{
NVTX("R^T+");
R->AddMultTranspose(x, y);
};
return std::make_tuple(RT, R->Height());
@@ -1086,11 +1251,26 @@ get_restriction_transpose(
inline
void prolongation(const FieldDescriptor field, const Vector &x, Vector &field_l)
{
NVTX("P");
const auto P = get_prolongation(field);
NVTX_INI("SetSize");
field_l.SetSize(P->Height());
NVTX_END("SetSize");
NVTX_INI("P->Mult");
P->Mult(x, field_l);
}
inline
void prolongation_transpose(
const FieldDescriptor &field, const Vector &field_l, Vector &x)
{
const auto P = get_prolongation(field);
x.SetSize(P->Width());
P->MultTranspose(field_l, x);
}
/// @brief Apply the prolongation operator to a vector of fields.
///
/// x is a long vector containing the data for all fields on tdofs and
@@ -1107,6 +1287,7 @@ void prolongation(const std::array<FieldDescriptor, N> fields,
const Vector &x,
std::array<Vector, M> &fields_l)
{
NVTX("P");
int data_offset = 0;
for (int i = 0; i < N; i++)
{
@@ -1114,9 +1295,14 @@ void prolongation(const std::array<FieldDescriptor, N> fields,
const int width = P->Width();
// const Vector x_i(x.GetData() + data_offset, width);
const Vector x_i(const_cast<Vector&>(x), data_offset, width);
fields_l[i].SetSize(P->Height());
NVTX_INI("SetSize");
fields_l[i].SetSize(P->Height());
NVTX_END("SetSize");
NVTX_INI("P->Mult");
P->Mult(x_i, fields_l[i]);
NVTX_END("P->Mult");
data_offset += width;
}
}
@@ -1130,20 +1316,259 @@ void prolongation(const std::array<FieldDescriptor, N> fields,
/// @param fields the array of field descriptors.
/// @param x the input vector in tdofs.
/// @param fields_l the array of output vectors in vdofs.
// inline
// void prolongation(const std::vector<FieldDescriptor> fields,
// const Vector &x,
// std::vector<Vector> &fields_l)
// {
// int data_offset = 0;
// for (std::size_t i = 0; i < fields.size(); i++)
// {
// const auto P = get_prolongation(fields[i]);
// const int width = P->Width();
// const Vector x_i(const_cast<Vector&>(x), data_offset, width);
// fields_l[i].SetSize(P->Height());
// P->Mult(x_i, fields_l[i]);
// data_offset += width;
// }
// }
inline
void prolongation(const std::vector<FieldDescriptor> fields,
const Vector &x,
std::vector<Vector> &fields_l)
void prolongation(
const std::vector<FieldDescriptor> fields,
const BlockVector &x,
std::vector<Vector *> &x_l)
{
int data_offset = 0;
for (std::size_t i = 0; i < fields.size(); i++)
MFEM_ASSERT(x.NumBlocks() == static_cast<int>(x_l.size()),
"error " << x.NumBlocks() << " vs " << x_l.size());
for (int i = 0; i < x.NumBlocks(); i++)
{
const auto P = get_prolongation(fields[i]);
const int width = P->Width();
const Vector x_i(const_cast<Vector&>(x), data_offset, width);
fields_l[i].SetSize(P->Height());
P->Mult(x_i, fields_l[i]);
data_offset += width;
// If nullptr, assume Identity.
if (P == nullptr)
{
*x_l[i] = x.GetBlock(i);
}
else
{
const auto P = get_prolongation(fields[i]);
MFEM_ASSERT(P->Width() == x.GetBlock(i).Size(),
"prolongation not applicable to given input data size " <<
P->Width() << " vs " << x.GetBlock(i).Size());
MFEM_ASSERT(P->Height() == x_l[i]->Size(),
"prolongation not applicable to given output data size " <<
P->Height() << " vs " << x_l[i]->Size());
P->Mult(x.GetBlock(i), *x_l[i]);
}
}
}
inline
void prolongation(
const std::vector<FieldDescriptor> fields,
const MultiVector &x,
std::vector<Vector *> &x_l)
{
MFEM_ASSERT(x.NumBlocks() == static_cast<int>(x_l.size()),
"error " << x.NumBlocks() << " vs " << x_l.size());
for (int i = 0; i < x.NumBlocks(); i++)
{
const auto P = get_prolongation(fields[i]);
// If nullptr, assume Identity.
if (P == nullptr)
{
*x_l[i] = x[i];
}
else
{
const auto P = get_prolongation(fields[i]);
MFEM_ASSERT(P->Width() == x[i].Size(),
"prolongation not applicable to given input data size " <<
P->Width() << " vs " << x[i].Size());
MFEM_ASSERT(P->Height() == x_l[i]->Size(),
"prolongation not applicable to given output data size " <<
P->Height() << " vs " << x_l[i]->Size());
P->Mult(x[i], *x_l[i]);
}
}
}
inline
void prolongation_transpose(
const std::vector<FieldDescriptor> fields,
const std::vector<Vector *> &x_l,
BlockVector &x)
{
MFEM_ASSERT(static_cast<int>(x_l.size()) == x.NumBlocks(),
"error " << x_l.size() << " vs " << x.NumBlocks());
for (size_t i = 0; i < x_l.size(); i++)
{
const auto P = get_prolongation(fields[i]);
// If nullptr, assume Identity.
if (P == nullptr)
{
x.GetBlock(i) = *x_l[i];
}
else
{
MFEM_ASSERT(P->Height() == x_l[i]->Size(),
"prolongation not applicable to given input data size " <<
P->Height() << " vs " << x_l[i]->Size());
MFEM_ASSERT(P->Width() == x.GetBlock(i).Size(),
"prolongation not applicable to given output data size " <<
P->Width() << " vs " << x.GetBlock(i).Size());
P->MultTranspose(*x_l[i], x.GetBlock(i));
}
}
}
inline
void prolongation_transpose(
const std::vector<FieldDescriptor> fields,
const std::vector<Vector *> &x_l,
MultiVector &x)
{
MFEM_ASSERT(static_cast<int>(x_l.size()) == x.NumBlocks(),
"error " << x_l.size() << " vs " << x.NumBlocks());
for (size_t i = 0; i < x_l.size(); i++)
{
const auto P = get_prolongation(fields[i]);
// If nullptr, assume Identity.
if (P == nullptr)
{
x[i] = *x_l[i];
}
else
{
MFEM_ASSERT(P->Height() == x_l[i]->Size(),
"prolongation not applicable to given input data size " <<
P->Height() << " vs " << x_l[i]->Size());
MFEM_ASSERT(P->Width() == x[i].Size(),
"prolongation not applicable to given output data size " <<
P->Width() << " vs " << x[i].Size());
P->MultTranspose(*x_l[i], x[i]);
}
}
}
template <typename entity_t>
void restriction(
const std::vector<FieldDescriptor> fields,
const std::vector<Vector *> &x_l,
std::vector<Vector *> &x_e)
{
MFEM_ASSERT(x_l.size() == x_e.size(),
"internal error " << x_l.size() << " vs " << x_e.size());
for (size_t i = 0; i < fields.size(); i++)
{
int s = 0;
const auto R = get_restriction<entity_t>(
fields[i], ElementDofOrdering::LEXICOGRAPHIC);
// If nullptr, assume Identity.
if (R == nullptr)
{
s = x_l[i]->Size();
}
else
{
s = R->Height();
}
// TODO
if (x_e[i] == nullptr)
{
x_e[i] = new Vector(s);
}
x_e[i]->SetSize(s);
if (R == nullptr)
{
x_e[i] = x_l[i];
}
else
{
MFEM_ASSERT(R->Width() == x_l[i]->Size(),
"restriction not applicable to given input data size " <<
R->Width() << " vs " << x_l[i]->Size());
R->Mult(*x_l[i], *x_e[i]);
}
}
}
template <typename entity_t>
void prepare_residual(
const std::vector<FieldDescriptor> &fields,
std::vector<Vector *> &r_e)
{
for (size_t i = 0; i < fields.size(); i++)
{
int s = 0;
if (std::holds_alternative<const QuadratureFunction *>(fields[i].data))
{
const auto fd = std::get<const QuadratureFunction *>(fields[i].data);
s = fd->Size();
}
else
{
const auto R = get_restriction<entity_t>(
fields[i], ElementDofOrdering::LEXICOGRAPHIC);
s = R->Height();
}
// TODO
if (r_e[i] == nullptr)
{
r_e[i] = new Vector(s);
}
else
{
r_e[i]->SetSize(s);
}
}
}
template <typename entity_t>
void restriction_transpose(
const std::vector<FieldDescriptor> &fields,
const std::vector<Vector *> &x_e,
std::vector<Vector *> &x_l)
{
for (size_t i = 0; i < fields.size(); i++)
{
int s = 0;
const auto R = get_restriction<entity_t>(
fields[i], ElementDofOrdering::LEXICOGRAPHIC);
// TODO: if nullptr, assume Identity
if (R == nullptr)
{
s = x_e[i]->Size();
}
else
{
s = R->Width();
}
// TODO
if (x_l[i] == nullptr)
{
x_l[i] = new Vector(s);
}
x_l[i]->SetSize(s);
// TODO: if nullptr, assume Identity
if (R == nullptr)
{
x_l[i] = x_e[i];
}
else
{
R->MultTranspose(*x_e[i], *x_l[i]);
}
}
}
@@ -1152,6 +1577,7 @@ void get_lvectors(const std::vector<FieldDescriptor> fields,
const Vector &x,
std::vector<Vector> &fields_l)
{
NVTX("get_lvectors");
int data_offset = 0;
for (std::size_t i = 0; i < fields.size(); i++)
{
@@ -1178,13 +1604,15 @@ template <typename fop_t>
inline
std::function<void(const Vector&, Vector&)> get_prolongation_transpose(
const FieldDescriptor &f,
const fop_t &fop,
[[maybe_unused]] const fop_t &fop,
MPI_Comm mpi_comm)
{
NVTX("get P^T");
if constexpr (is_sum_fop<fop_t>::value)
{
auto PT = [=](const Vector &r_local, Vector &y)
{
NVTX("P^T sum");
MFEM_ASSERT(y.Size() == 1, "output size doesn't match kernel description");
real_t local_sum = r_local.Sum();
MPI_Allreduce(&local_sum, y.GetData(), 1, MPI_DOUBLE, MPI_SUM, mpi_comm);
@@ -1195,6 +1623,7 @@ std::function<void(const Vector&, Vector&)> get_prolongation_transpose(
{
auto PT = [=](const Vector &r_local, Vector &y)
{
NVTX("P^T Identity");
y = r_local;
};
return PT;
@@ -1202,6 +1631,7 @@ std::function<void(const Vector&, Vector&)> get_prolongation_transpose(
const Operator *P = get_prolongation(f);
auto PT = [=](const Vector &r_local, Vector &y)
{
NVTX("P^T");
P->MultTranspose(r_local, y);
};
return PT;
@@ -1220,12 +1650,19 @@ void restriction(const FieldDescriptor u,
Vector &field_e,
ElementDofOrdering ordering)
{
NVTX("R");
const auto R = get_restriction<entity_t>(u, ordering);
MFEM_ASSERT(R->Width() == u_l.Size(),
"restriction not applicable to given data size");
const int height = R->Height();
NVTX_INI("SetSize");
field_e.SetSize(height);
NVTX_END("SetSize");
NVTX_INI("R->Mult");
R->Mult(u_l, field_e);
NVTX_END("R->Mult");
}
/// @brief Apply the restriction operator to a vector of fields.
@@ -1243,14 +1680,29 @@ void restriction(const std::vector<FieldDescriptor> u,
ElementDofOrdering ordering,
const int offset = 0)
{
NVTX("R");
for (std::size_t i = 0; i < u.size(); i++)
{
const auto R = get_restriction<entity_t>(u[i], ordering);
MFEM_ASSERT(R->Width() == u_l[i].Size(),
"restriction not applicable to given data size");
const int height = R->Height();
// NVTX_INI("SetSize");
fields_e[i + offset].SetSize(height);
R->Mult(u_l[i], fields_e[i + offset]);
// NVTX_END("SetSize");
// NVTX_INI("R->Mult");
if (dynamic_cast<const IdentityOperator*>(R))
{
NVTX("Identity");
fields_e[i + offset].NewMemoryAndSize(u_l[i].GetMemory(), u_l[i].Size(), false);
}
else
{
R->Mult(u_l[i], fields_e[i + offset]);
}
// NVTX_END("R->Mult");
}
}
@@ -1262,14 +1714,21 @@ void element_restriction(const std::array<FieldDescriptor, N> u,
ElementDofOrdering ordering,
const int offset = 0)
{
NVTX("ER");
for (int i = 0; i < N; i++)
{
const auto R = get_element_restriction(u[i], ordering);
MFEM_ASSERT(R->Width() == u_l[i].Size(),
"element restriction not applicable to given data size");
const int height = R->Height();
NVTX_INI("SetSize");
fields_e[i + offset].SetSize(height);
NVTX_END("SetSize");
NVTX_INI("R->Mult");
R->Mult(u_l[i], fields_e[i + offset]);
NVTX_END("R->Mult");
}
}
@@ -1326,6 +1785,10 @@ const DofToQuad *GetDofToQuad(const FieldDescriptor &f,
return &arg->GetTypicalTraceElement()->GetDofToQuad(ir, mode);
}
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return nullptr;
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return &arg->GetDofToQuad();
@@ -1457,7 +1920,7 @@ create_descriptors_to_fields_map(
auto f = [&](auto &fop, auto &map)
{
if constexpr (std::is_same_v<std::decay_t<decltype(fop)>, Weight>)
if constexpr (is_weight_fop<std::decay_t<decltype(fop)>>::value)
{
// TODO-bug: stealing dimension from the first field
fop.dim = GetDimension<entity_t>(fields[0]);
@@ -1587,7 +2050,7 @@ get_shmem_info(
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps,
const std::vector<FieldDescriptor> &fields,
const int &num_entities,
const input_t &inputs,
[[maybe_unused]] const input_t &inputs,
const int &num_qp,
const std::vector<int> &input_size_on_qp,
const int &residual_size_on_qp,
@@ -2342,5 +2805,25 @@ std::array<DofToQuadMap, num_fields> create_dtq_maps(
std::make_index_sequence<num_fields> {});
}
struct QLayoutEntry
{
std::type_index type;
std::vector<int> layout;
template <class Fop>
QLayoutEntry(Fop, std::initializer_list<int> idx) :
type(typeid(Fop)), layout(idx) {}
};
static void ExtractQLayouts(
const std::initializer_list<QLayoutEntry> entries,
std::unordered_map<std::type_index, std::vector<int>>& out)
{
for (const auto& e : entries)
{
out[e.type] = e.layout;
}
}
} // namespace mfem::future
#endif
+1 -1
View File
@@ -52,7 +52,7 @@
#include "bounds.hpp"
#include "particleset.hpp"
#include "dfem/doperator.hpp"
// #include "dfem/doperator.hpp"
#ifdef MFEM_USE_MPI
#include "pfespace.hpp"
+18 -8
View File
@@ -1064,6 +1064,8 @@ inline void SmemPADiffusionApply3D(const int NE,
}
}
MFEM_SYNC_THREAD;
// Grad X
MFEM_FOREACH_THREAD_DIRECT(dz,z,D1D)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,D1D)
@@ -1084,6 +1086,8 @@ inline void SmemPADiffusionApply3D(const int NE,
}
}
MFEM_SYNC_THREAD;
// Grad Y
MFEM_FOREACH_THREAD_DIRECT(dz,z,D1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,Q1D)
@@ -1105,6 +1109,8 @@ inline void SmemPADiffusionApply3D(const int NE,
}
}
MFEM_SYNC_THREAD;
// Grad Z + Q-function
MFEM_FOREACH_THREAD_DIRECT(qz,z,Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,Q1D)
@@ -1217,20 +1223,23 @@ inline void SmemPADiffusionApply3D(const int NE,
namespace
{
using ApplyKernelType = DiffusionIntegrator::ApplyKernelType;
using DiagonalKernelType = DiffusionIntegrator::DiagonalKernelType;
using DiffusionApplyKernelType =
DiffusionIntegrator::DiffusionApplyKernelType;
using DiffusionDiagonalKernelType =
DiffusionIntegrator::DiffusionDiagonalKernelType;
}
template<int DIM, int T_D1D, int T_Q1D>
ApplyKernelType DiffusionIntegrator::ApplyPAKernels::Kernel()
DiffusionApplyKernelType DiffusionIntegrator::DiffusionApplyPAKernel::Kernel()
{
if constexpr (DIM == 2) { return internal::SmemPADiffusionApply2D<T_D1D,T_Q1D>; }
else if constexpr (DIM == 3) { return internal::SmemPADiffusionApply3D<T_D1D, T_Q1D>; }
MFEM_ABORT("");
}
inline
ApplyKernelType DiffusionIntegrator::ApplyPAKernels::Fallback(int DIM, int, int)
inline DiffusionApplyKernelType
DiffusionIntegrator::DiffusionApplyPAKernel::Fallback(int DIM, int, int)
{
if (DIM == 2) { return internal::PADiffusionApply2D; }
else if (DIM == 3) { return internal::PADiffusionApply3D; }
@@ -1238,15 +1247,16 @@ ApplyKernelType DiffusionIntegrator::ApplyPAKernels::Fallback(int DIM, int, int)
}
template<int DIM, int D1D, int Q1D>
DiagonalKernelType DiffusionIntegrator::DiagonalPAKernels::Kernel()
DiffusionDiagonalKernelType
DiffusionIntegrator::DiffusionDiagonalPAKernel::Kernel()
{
if constexpr (DIM == 2) { return internal::SmemPADiffusionDiagonal2D<D1D,Q1D>; }
else if constexpr (DIM == 3) { return internal::SmemPADiffusionDiagonal3D<D1D, Q1D>; }
MFEM_ABORT("");
}
inline DiagonalKernelType
DiffusionIntegrator::DiagonalPAKernels::Fallback(int DIM, int, int)
inline DiffusionDiagonalKernelType
DiffusionIntegrator::DiffusionDiagonalPAKernel::Fallback(int DIM, int, int)
{
if (DIM == 2) { return internal::PADiffusionDiagonal2D; }
else if (DIM == 3) { return internal::PADiffusionDiagonal3D; }
+7 -7
View File
@@ -31,8 +31,8 @@ void DiffusionIntegrator::AssembleDiagonalPA(Vector &diag)
const Array<real_t> &B = maps->B;
const Array<real_t> &G = maps->G;
const Vector &Dv = pa_data;
DiagonalPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Dv,
diag, dofs1D, quad1D);
DiffusionDiagonalPAKernel::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Dv,
diag, dofs1D, quad1D);
}
}
@@ -68,8 +68,8 @@ void DiffusionIntegrator::AddMultPA(const Vector &x, Vector &y) const
}
#endif // MFEM_USE_OCCA
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Bt,
Gt, Dv, x, y, dofs1D, quad1D);
DiffusionApplyPAKernel::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Bt,
Gt, Dv, x, y, dofs1D, quad1D);
}
}
@@ -174,9 +174,9 @@ void DiffusionIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
abs_pa_data.Abs();
auto abs_maps = maps->Abs();
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric,
abs_maps.B, abs_maps.G, abs_maps.Bt, abs_maps.Gt,
abs_pa_data, x, y, dofs1D, quad1D);
DiffusionApplyPAKernel::Run(dim, dofs1D, quad1D, ne, symmetric,
abs_maps.B, abs_maps.G, abs_maps.Bt, abs_maps.Gt,
abs_pa_data, x, y, dofs1D, quad1D);
}
void DiffusionIntegrator::AddAbsMultTransposePA(const Vector &x,
+44
View File
@@ -207,6 +207,28 @@ inline MFEM_HOST_DEVICE void WriteDofs2d(const int e, const int d1d,
MFEM_SYNC_THREAD;
}
/// Load 3D input DIM vector at element offset into given register tensor
template <int VDIM, int DIM, int MQ1>
inline MFEM_HOST_DEVICE void LoadDofs3d(const int d1d, const int c,
const DeviceTensor<4, const real_t> &X,
vd_regs3d_t<VDIM, DIM, MQ1> &Y)
{
for (int d = 0; d < DIM; d++)
{
for (int dz = 0; dz < d1d; ++dz)
{
MFEM_FOREACH_THREAD_DIRECT(dy, y, d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx, x, d1d)
{
Y[c][d][dz][dy][dx] = X(dx, dy, dz, c);
}
}
}
}
MFEM_SYNC_THREAD;
}
/// Load 3D input VDIM*DIM vector into given register tensor, specific component
template <int VDIM, int DIM, int MQ1>
inline MFEM_HOST_DEVICE void LoadDofs3d(const int e, const int d1d, const int c,
@@ -332,6 +354,28 @@ inline MFEM_HOST_DEVICE void WriteDofs3d(const int e, const int d1d,
MFEM_SYNC_THREAD;
}
/// Write 3D DIM vector into given device tensor for specific component
template <int VDIM, int DIM, int MQ1>
inline MFEM_HOST_DEVICE void WriteDofs3d(const int d1d, const int c,
vd_regs3d_t<VDIM, DIM, MQ1> &X,
DeviceTensor<4, real_t> &Y)
{
for (int dz = 0; dz < d1d; ++dz)
{
MFEM_FOREACH_THREAD_DIRECT(dy, y, d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx, x, d1d)
{
for (int d = 0; d < DIM; ++d)
{
Y(dx, dy, dz, c) += X(c, d, dz, dy, dx);
}
}
}
}
MFEM_SYNC_THREAD;
}
/// 2D scalar contraction, X direction
template <bool Transpose, int MQ1>
inline MFEM_HOST_DEVICE void ContractX2d(const int d1d, const int q1d,
+332
View File
@@ -0,0 +1,332 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "../config/config.hpp"
#include "../general/forall.hpp"
#include "../linalg/dtensor.hpp"
#include "kernels.hpp" // IWYU pragma: keep
namespace mfem::kernels::internal::low
{
#if ((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
template <int DIM, int N>
// struct regs3d_device_wrapper: mfem::future::tensor<real_t, DIM, 0, 0, 0> {};
struct regs3d_device_wrapper: mfem::future::tensor<real_t, 0, 0, 0, DIM> {};
template <int DIM, int N>
using regs3d_t = regs3d_device_wrapper<DIM, N>;
#else
template <int DIM, int N>
using regs3d_t = mfem::future::tensor<real_t, N, N, N, DIM>;
// using regs3d_t = mfem::future::tensor<real_t, DIM, N, N, N>;
#endif
///////////////////////////////////////////////////////////////////////////////
/// Load 2D matrix into shared memory
template <int MQ1>
inline MFEM_HOST_DEVICE void LoadMatrix(const int d1d, const int q1d,
const real_t *M, real_t (*N)[MQ1])
{
if (MFEM_THREAD_ID(z) == 0)
{
MFEM_FOREACH_THREAD_DIRECT(dy, y, d1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
N[dy][qx] = M[dy * q1d + qx];
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
template <int DIM, int MQ1>
inline MFEM_HOST_DEVICE void LoadDofs3d(const int e, const int d1d,
const DeviceTensor<5, const real_t> &XE,
real_t (&sm0)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
sm0[dz][dy][dx][0] = XE(dx, dy, dz, 0, e);
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient, 1/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradX(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
const real_t (&sm0)[MQ1][MQ1][MQ1][DIM],
real_t (&sm1)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
real_t u = 0.0, v = 0.0;
MFEM_UNROLL(MQ1)
for (int dx = 0; dx < d1d; ++dx)
{
const auto x = sm0[dz][dy][dx][0];
u = std::fma(B[dx][qx], x, u);
v = std::fma(G[dx][qx], x, v);
}
sm1[dz][dy][qx][0] = u;
sm1[dz][dy][qx][1] = v;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient, 2/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradY(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
const real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
real_t u = 0.0, v = 0.0, w = 0.0;
MFEM_UNROLL(MQ1)
for (int dy = 0; dy < d1d; ++dy)
{
u = std::fma(sm1[dz][dy][qx][1], B[dy][qy], u);
v = std::fma(sm1[dz][dy][qx][0], G[dy][qy], v);
w = std::fma(sm1[dz][dy][qx][0], B[dy][qy], w);
}
sm0[dz][qy][qx][0] = u;
sm0[dz][qy][qx][1] = v;
sm0[dz][qy][qx][2] = w;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient, 3/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradZ(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
const real_t (&sm0)[MQ1][MQ1][MQ1][DIM],
regs3d_t<DIM,MQ1> &reg)
{
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
real_t u[3] = {0.0, 0.0, 0.0};
MFEM_UNROLL(MQ1)
for (int dz = 0; dz < d1d; ++dz)
{
u[0] = std::fma(B[dz][qz], sm0[dz][qy][qx][0], u[0]);
u[1] = std::fma(B[dz][qz], sm0[dz][qy][qx][1], u[1]);
u[2] = std::fma(G[dz][qz], sm0[dz][qy][qx][2], u[2]);
}
reg[qz][qy][qx][0] = u[0];
reg[qz][qy][qx][1] = u[1];
reg[qz][qy][qx][2] = u[2];
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D scalar gradient
template <int DIM, int MQ1>
inline MFEM_HOST_DEVICE void Grad3d(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM],
real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
regs3d_t<DIM,MQ1> &reg)
{
GradX(d1d, q1d, B, G, sm0, sm1); // Grad X
GradY(d1d, q1d, B, G, sm1, sm0); // Grad Y
GradZ(d1d, q1d, B, G, sm0, reg); // Grad Z
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient Transposed, 1/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradTranspose3dX(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
regs3d_t<DIM,MQ1> &reg,
real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
sm1[qz][qy][qx][0] = reg[qz][qy][qx][0];
sm1[qz][qy][qx][1] = reg[qz][qy][qx][1];
sm1[qz][qy][qx][2] = reg[qz][qy][qx][2];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
real_t u = 0.0, v = 0.0, w = 0.0;
MFEM_UNROLL(MQ1)
for (int qx = 0; qx < q1d; ++qx)
{
u = std::fma(sm1[qz][qy][qx][0], G[dx][qx], u);
v = std::fma(sm1[qz][qy][qx][1], B[dx][qx], v);
w = std::fma(sm1[qz][qy][qx][2], B[dx][qx], w);
}
sm0[qz][qy][dx][0] = u;
sm0[qz][qy][dx][1] = v;
sm0[qz][qy][dx][2] = w;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient Transposed, 2/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradTranspose3dY(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM],
real_t (&sm1)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
real_t u = 0.0, v = 0.0, w = 0.0;
MFEM_UNROLL(MQ1)
for (int qy = 0; qy < q1d; ++qy)
{
u = std::fma(sm0[qz][qy][dx][0], B[dy][qy], u);
v = std::fma(sm0[qz][qy][dx][1], G[dy][qy], v);
w = std::fma(sm0[qz][qy][dx][2], B[dy][qy], w);
}
sm1[qz][dy][dx][0] = u;
sm1[qz][dy][dx][1] = v;
sm1[qz][dy][dx][2] = w;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient Transposed, 3/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradTranspose3dZ(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
regs3d_t<DIM,MQ1> &reg)
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
real_t u = 0.0, v = 0.0, w = 0.0;
MFEM_UNROLL(MQ1)
for (int qz = 0; qz < q1d; ++qz)
{
u = std::fma(sm1[qz][dy][dx][0], B[dz][qz], u);
v = std::fma(sm1[qz][dy][dx][1], B[dz][qz], v);
w = std::fma(sm1[qz][dy][dx][2], G[dz][qz], w);
}
reg[dz][dy][dx][0] = u;
reg[dz][dy][dx][1] = v;
reg[dz][dy][dx][2] = w;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D scalar gradient transposed
template <int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradTranspose3d(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
regs3d_t<DIM,MQ1> &reg,
real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM])
{
GradTranspose3dX(d1d, q1d, B, G, reg, sm1, sm0); // Grad^T X
GradTranspose3dY(d1d, q1d, B, G, sm0, sm1); // Grad^T Y
GradTranspose3dZ(d1d, q1d, B, G, sm1, reg); // Grad^T Z
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient Transposed, 3/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void WriteDofs3d(const int d1d,
const int c, const int e,
regs3d_t<DIM,MQ1> &reg,
const DeviceTensor<5, real_t> &YE)
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
const real_t u = reg[dz][dy][dx][0];
const real_t v = reg[dz][dy][dx][1];
const real_t w = reg[dz][dy][dx][2];
YE(dx, dy, dz, c, e) += (u + v + w);
}
}
}
}
} // namespace mfem::kernels::internal
+3
View File
@@ -224,6 +224,9 @@ public:
/** @see GetGradient(const Vector &) */
Operator &GetGradient(const Vector &x, bool finalize) const;
/// Suppress a warning about hiding overloaded virtual function.
using Operator::GetGradient;
/// Update the NonlinearForm to propagate updates of the associated FE space.
/** After calling this method, the essential boundary conditions need to be
set again. */
+44
View File
@@ -0,0 +1,44 @@
// 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 "../quadinterpolator.hpp"
#include "eval_transpose.hpp"
namespace mfem
{
/// @cond Suppress_Doxygen_warnings
QuadratureInterpolator::TensorEvalTransposeKernelType
QuadratureInterpolator::TensorEvalTransposeKernels::Fallback(
int DIM, QVectorLayout Q_LAYOUT, int, int, int)
{
using namespace internal::quadrature_interpolator;
if (Q_LAYOUT == QVectorLayout::byNODES)
{
if (DIM == 1) { return ValuesTranspose1D<QVectorLayout::byNODES>; }
else if (DIM == 2) { return ValuesTranspose2D<QVectorLayout::byNODES>; }
else if (DIM == 3) { return ValuesTranspose3D<QVectorLayout::byNODES>; }
}
else
{
if (DIM == 1) { return ValuesTranspose1D<QVectorLayout::byVDIM>; }
else if (DIM == 2) { return ValuesTranspose2D<QVectorLayout::byVDIM>; }
else if (DIM == 3) { return ValuesTranspose3D<QVectorLayout::byVDIM>; }
}
MFEM_ABORT("Invalid dimension");
return nullptr;
}
/// @endcond
} // namespace mfem
+300
View File
@@ -0,0 +1,300 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "../quadinterpolator.hpp"
#include "../../general/forall.hpp"
#include "../../linalg/dtensor.hpp"
#include "../../linalg/kernels.hpp"
#include "../kernels.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
template<QVectorLayout Q_LAYOUT>
static void ValuesTranspose1D(const int NE,
const real_t *b_,
const real_t *q_,
real_t *e_,
const int vdim,
const int d1d,
const int q1d)
{
const auto b = Reshape(b_, q1d, d1d);
const auto qd = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, q1d, vdim, NE) :
Reshape(q_, vdim, q1d, NE);
auto e = Reshape(e_, d1d, vdim, NE);
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int el)
{
for (int c = 0; c < vdim; c++)
{
for (int d = 0; d < d1d; d++)
{
real_t u = 0.0;
for (int q = 0; q < q1d; q++)
{
const real_t qval = Q_LAYOUT == QVectorLayout::byVDIM ?
qd(c, q, el) : qd(q, c, el);
u += b(q, d) * qval;
}
e(d, c, el) += u;
}
}
});
}
template<QVectorLayout Q_LAYOUT,
int T_VDIM = 0, int T_D1D = 0, int T_Q1D = 0,
int T_NBZ = 1>
static void ValuesTranspose2D(const int NE,
const real_t *b_,
const real_t *q_,
real_t *e_,
const int vdim = 0,
const int d1d = 0,
const int q1d = 0)
{
static constexpr int NBZ = T_NBZ ? T_NBZ : 1;
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
const auto b = Reshape(b_, Q1D, D1D);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, Q1D, Q1D, VDIM, NE) :
Reshape(q_, VDIM, Q1D, Q1D, NE);
auto e = Reshape(e_, D1D, D1D, VDIM, NE);
mfem::forall_2D_batch(NE, D1D, D1D, NBZ, [=] MFEM_HOST_DEVICE (int el)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_Q1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
constexpr int MDQ = (MQ1 > MD1) ? MQ1 : MD1;
const int tidz = MFEM_THREAD_ID(z);
MFEM_SHARED real_t sB[MQ1*MD1];
MFEM_SHARED real_t sm0[NBZ][MDQ*MDQ];
MFEM_SHARED real_t sm1[NBZ][MDQ*MDQ];
kernels::internal::LoadB<MD1,MQ1>(D1D,Q1D,b,sB);
ConstDeviceMatrix B(sB, D1D, Q1D);
DeviceMatrix QQ(sm0[tidz], MQ1, MQ1);
DeviceMatrix DQ(sm1[tidz], MD1, MQ1);
DeviceMatrix DD(sm0[tidz], MD1, MD1);
for (int c = 0; c < VDIM; c++)
{
// Load Q data
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
QQ(qx,qy) = Q_LAYOUT == QVectorLayout::byVDIM ?
q(c,qx,qy,el) : q(qx,qy,c,el);
}
}
MFEM_SYNC_THREAD;
// Transpose in y: QQ -> DQ (apply B^T in y-direction)
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * QQ(qx,qy);
}
DQ(dy,qx) = u;
}
}
MFEM_SYNC_THREAD;
// Transpose in x: DQ -> DD (apply B^T in x-direction)
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * DQ(dy,qx);
}
DD(dx,dy) = u;
}
}
MFEM_SYNC_THREAD;
// Store result
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,c,el) += DD(dx,dy);
}
}
MFEM_SYNC_THREAD;
}
});
}
template<QVectorLayout Q_LAYOUT,
int T_VDIM = 0, int T_D1D = 0, int T_Q1D = 0>
static void ValuesTranspose3D(const int NE,
const real_t *b_,
const real_t *q_,
real_t *e_,
const int vdim = 0,
const int d1d = 0,
const int q1d = 0)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
const auto b = Reshape(b_, Q1D, D1D);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, Q1D, Q1D, Q1D, VDIM, NE) :
Reshape(q_, VDIM, Q1D, Q1D, Q1D, NE);
auto e = Reshape(e_, D1D, D1D, D1D, VDIM, NE);
mfem::forall_3D(NE, D1D, D1D, D1D, [=] MFEM_HOST_DEVICE (int el)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_INTERP_1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_INTERP_1D;
constexpr int MDQ = (MQ1 > MD1) ? MQ1 : MD1;
MFEM_SHARED real_t sB[MQ1*MD1];
MFEM_SHARED real_t sm0[MDQ*MDQ*MDQ];
MFEM_SHARED real_t sm1[MDQ*MDQ*MDQ];
kernels::internal::LoadB<MD1,MQ1>(D1D,Q1D,b,sB);
ConstDeviceMatrix B(sB, D1D, Q1D);
DeviceCube QQQ(sm0, MQ1, MQ1, MQ1);
DeviceCube DQQ(sm1, MD1, MQ1, MQ1);
DeviceCube DDQ(sm0, MD1, MD1, MQ1);
DeviceCube DDD(sm1, MD1, MD1, MD1);
for (int c = 0; c < VDIM; c++)
{
// Load Q data
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
QQQ(qx,qy,qz) = Q_LAYOUT == QVectorLayout::byVDIM ?
q(c,qx,qy,qz,el) : q(qx,qy,qz,c,el);
}
}
}
MFEM_SYNC_THREAD;
// Transpose in z
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qz = 0; qz < Q1D; ++qz)
{
u += B(dz,qz) * QQQ(qx,qy,qz);
}
DQQ(dz,qx,qy) = u;
}
}
}
MFEM_SYNC_THREAD;
// Transpose in y
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * DQQ(dz,qx,qy);
}
DDQ(dz,dy,qx) = u;
}
}
}
MFEM_SYNC_THREAD;
// Transpose in x
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * DDQ(dz,dy,qx);
}
DDD(dx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,dz,c,el) += DDD(dx,dy,dz);
}
}
}
MFEM_SYNC_THREAD;
}
});
}
} // namespace quadrature_interpolator
} // namespace internal
template<int DIM, QVectorLayout Q_LAYOUT,
int VDIM, int D1D, int Q1D, int NBZ>
QuadratureInterpolator::TensorEvalTransposeKernelType
QuadratureInterpolator::TensorEvalTransposeKernels::Kernel()
{
if (DIM == 1) { return internal::quadrature_interpolator::ValuesTranspose1D<Q_LAYOUT>; }
else if (DIM == 2) { return internal::quadrature_interpolator::ValuesTranspose2D<Q_LAYOUT, VDIM, D1D, Q1D, NBZ>; }
else if (DIM == 3) { return internal::quadrature_interpolator::ValuesTranspose3D<Q_LAYOUT, VDIM, D1D, Q1D>; }
else { MFEM_ABORT(""); }
}
} // namespace mfem
+61
View File
@@ -0,0 +1,61 @@
// 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 "../quadinterpolator.hpp"
#include "eval_transpose.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
void InitEvalTransposeByVDimKernels()
{
using k = QuadratureInterpolator::TensorEvalTransposeKernels;
constexpr auto L = QVectorLayout::byVDIM;
// 2D
k::Specialization<2,L,1,2,4>::Opt<8>::Add();
k::Specialization<2,L,1,3,6>::Opt<4>::Add();
k::Specialization<2,L,1,4,8>::Opt<2>::Add();
k::Specialization<2,L,2,2,4>::Opt<8>::Add();
k::Specialization<2,L,2,3,4>::Opt<8>::Add();
k::Specialization<2,L,2,3,6>::Opt<4>::Add();
k::Specialization<2,L,2,4,6>::Opt<2>::Add();
k::Specialization<2,L,2,4,8>::Opt<2>::Add();
// 3D
k::Specialization<3,L,1,2,4>::Opt<1>::Add();
k::Specialization<3,L,1,3,6>::Opt<1>::Add();
k::Specialization<3,L,1,4,8>::Opt<1>::Add();
k::Specialization<3,L,3,2,4>::Opt<1>::Add();
k::Specialization<3,L,3,3,6>::Opt<1>::Add();
k::Specialization<3,L,3,4,8>::Opt<1>::Add();
k::Specialization<3,L,3,2,2>::Opt<1>::Add();
k::Specialization<3,L,3,3,3>::Opt<1>::Add();
k::Specialization<3,L,3,4,4>::Opt<1>::Add();
k::Specialization<3,L,3,5,5>::Opt<1>::Add();
k::Specialization<3,L,3,6,6>::Opt<1>::Add();
k::Specialization<3,L,3,7,7>::Opt<1>::Add();
k::Specialization<3,L,3,8,8>::Opt<1>::Add();
k::Specialization<3,L,3,9,9>::Opt<1>::Add();
k::Specialization<3,L,3,4,6>::Opt<1>::Add();
k::Specialization<3,L,3,3,4>::Opt<1>::Add();
}
} // namespace quadrature_interpolator
} // namespace internal
} // namespace mfem
+62
View File
@@ -0,0 +1,62 @@
// 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 "../quadinterpolator.hpp"
#include "grad_transpose.hpp"
namespace mfem
{
/// @cond Suppress_Doxygen_warnings
QuadratureInterpolator::GradTransposeKernelType
QuadratureInterpolator::GradTransposeKernels::Fallback(
int DIM, QVectorLayout Q_LAYOUT, bool GRAD_PHYS, int, int, int)
{
using namespace internal::quadrature_interpolator;
if (Q_LAYOUT == QVectorLayout::byNODES)
{
if (GRAD_PHYS)
{
if (DIM == 1) { return DerivativesTranspose1D<QVectorLayout::byNODES, true>; }
else if (DIM == 2) { return DerivativesTranspose2D<QVectorLayout::byNODES, true>; }
else if (DIM == 3) { return DerivativesTranspose3D<QVectorLayout::byNODES, true>; }
}
else
{
if (DIM == 1) { return DerivativesTranspose1D<QVectorLayout::byNODES, false>; }
else if (DIM == 2) { return DerivativesTranspose2D<QVectorLayout::byNODES, false>; }
else if (DIM == 3) { return DerivativesTranspose3D<QVectorLayout::byNODES, false>; }
}
}
else
{
if (GRAD_PHYS)
{
if (DIM == 1) { return DerivativesTranspose1D<QVectorLayout::byVDIM, true>; }
else if (DIM == 2) { return DerivativesTranspose2D<QVectorLayout::byVDIM, true>; }
else if (DIM == 3) { return DerivativesTranspose3D<QVectorLayout::byVDIM, true>; }
}
else
{
if (DIM == 1) { return DerivativesTranspose1D<QVectorLayout::byVDIM, false>; }
else if (DIM == 2) { return DerivativesTranspose2D<QVectorLayout::byVDIM, false>; }
else if (DIM == 3) { return DerivativesTranspose3D<QVectorLayout::byVDIM, false>; }
}
}
MFEM_ABORT("Invalid dimension");
return nullptr;
}
/// @endcond
} // namespace mfem
+737
View File
@@ -0,0 +1,737 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "../quadinterpolator.hpp"
#include "../../general/forall.hpp"
#include "../../linalg/dtensor.hpp"
#include "../../linalg/kernels.hpp"
#include "../kernels.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
// Transpose gradient operation: integrate against shape function derivatives
// This is the adjoint of the Derivatives operation
template<QVectorLayout Q_LAYOUT, bool GRAD_PHYS>
static void DerivativesTranspose1D(const int NE,
const real_t *b_,
const real_t *g_,
const real_t *j_,
const real_t *q_,
real_t *e_,
const int sdim,
const int vdim,
const int d1d,
const int q1d)
{
MFEM_CONTRACT_VAR(b_);
const int SDIM = GRAD_PHYS ? sdim : 1;
const auto g = Reshape(g_, q1d, d1d);
const auto j = Reshape(j_, q1d, SDIM, NE);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, q1d, vdim, SDIM, NE):
Reshape(q_, vdim, SDIM, q1d, NE);
auto e = Reshape(e_, d1d, vdim, NE);
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int el)
{
for (int c = 0; c < vdim; c++)
{
for (int d = 0; d < d1d; d++)
{
real_t u = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
// Load gradient from q-vector
real_t dq[3] = {0.0, 0.0, 0.0};
for (int s = 0; s < SDIM; ++s)
{
if (Q_LAYOUT == QVectorLayout::byVDIM) { dq[s] = q(c, s, qx, el); }
if (Q_LAYOUT == QVectorLayout::byNODES) { dq[s] = q(qx, c, s, el); }
}
// Apply inverse Jacobian transpose (adjoint of physical gradient)
real_t du = dq[0];
if (GRAD_PHYS)
{
if (SDIM == 1) { du = dq[0] / j(qx, 0, el); }
else if (SDIM == 2)
{
const real_t Jloc[2] = {j(qx,0,el), j(qx,1,el)};
real_t Jinv[3];
kernels::CalcLeftInverse<2,1>(Jloc, Jinv);
du = Jinv[0]*dq[0] + Jinv[1]*dq[1];
}
else // SDIM == 3
{
const real_t Jloc[3] = {j(qx,0,el), j(qx,1,el), j(qx,2,el)};
real_t Jinv[3];
kernels::CalcLeftInverse<3,1>(Jloc, Jinv);
du = Jinv[0]*dq[0] + Jinv[1]*dq[1] + Jinv[2]*dq[2];
}
}
// Accumulate contribution (transpose of G matrix)
u += g(qx, d) * du;
}
e(d, c, el) += u;
}
}
});
}
template<QVectorLayout Q_LAYOUT, bool GRAD_PHYS,
int T_VDIM = 0, int T_D1D = 0, int T_Q1D = 0,
int T_NBZ = 1>
static void DerivativesTranspose2D(const int NE,
const real_t *b_,
const real_t *g_,
const real_t *j_,
const real_t *q_,
real_t *e_,
const int sdim = 2,
const int vdim = 0,
const int d1d = 0,
const int q1d = 0)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
const int SDIM = GRAD_PHYS ? sdim : 2;
static constexpr int NBZ = T_NBZ ? T_NBZ : 1;
const auto b = Reshape(b_, Q1D, D1D);
const auto g = Reshape(g_, Q1D, D1D);
const auto j = Reshape(j_, Q1D, Q1D, SDIM, 2, NE);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, Q1D, Q1D, VDIM, SDIM, NE):
Reshape(q_, VDIM, SDIM, Q1D, Q1D, NE);
auto e = Reshape(e_, D1D, D1D, VDIM, NE);
mfem::forall_2D_batch(NE, D1D, D1D, NBZ, [=] MFEM_HOST_DEVICE (int el)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_Q1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
constexpr int MDQ = (MQ1 > MD1) ? MQ1 : MD1;
const int tidz = MFEM_THREAD_ID(z);
MFEM_SHARED real_t BG[2][MQ1*MD1];
kernels::internal::LoadBG<MD1,MQ1>(D1D,Q1D,b,g,BG);
DeviceMatrix B(BG[0], D1D, Q1D);
DeviceMatrix G(BG[1], D1D, Q1D);
MFEM_SHARED real_t sm0[NBZ][MDQ*MDQ];
MFEM_SHARED real_t sm1[NBZ][MDQ*MDQ];
DeviceMatrix QQ(sm0[tidz], MQ1, MQ1);
DeviceMatrix DQ0(sm1[tidz], MD1, MQ1);
DeviceMatrix DQ1(sm1[tidz], MD1, MQ1); // Reuse sm1 after DQ0 is done
DeviceMatrix DD(sm0[tidz], MD1, MD1); // Reuse sm0 after QQ is done
for (int c = 0; c < VDIM; c++)
{
// Load Q data and apply inverse Jacobian
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
// Load gradient components
real_t dq[3] = {0.0, 0.0, 0.0};
for (int d = 0; d < SDIM; ++d)
{
if (Q_LAYOUT == QVectorLayout::byVDIM) { dq[d] = q(c, d, qx, qy, el); }
else { dq[d] = q(qx, qy, c, d, el); }
}
// Apply inverse Jacobian transpose (adjoint of physical gradient)
real_t du[2] = {dq[0], dq[1]};
if (GRAD_PHYS)
{
if (SDIM == 2)
{
real_t Jloc[4], Jinv[4];
Jloc[0] = j(qx,qy,0,0,el);
Jloc[1] = j(qx,qy,1,0,el);
Jloc[2] = j(qx,qy,0,1,el);
Jloc[3] = j(qx,qy,1,1,el);
kernels::CalcInverse<2>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[2]*dq[1];
const real_t V = Jinv[1]*dq[0] + Jinv[3]*dq[1];
du[0] = U;
du[1] = V;
}
else // SDIM == 3
{
real_t Jloc[6], Jinv[6];
Jloc[0] = j(qx,qy,0,0,el);
Jloc[1] = j(qx,qy,1,0,el);
Jloc[2] = j(qx,qy,2,0,el);
Jloc[3] = j(qx,qy,0,1,el);
Jloc[4] = j(qx,qy,1,1,el);
Jloc[5] = j(qx,qy,2,1,el);
kernels::CalcLeftInverse<3,2>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[2]*dq[1] + Jinv[4]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[3]*dq[1] + Jinv[5]*dq[2];
du[0] = U;
du[1] = V;
}
}
QQ(qx, qy) = du[0]; // Store du/dx component
}
}
MFEM_SYNC_THREAD;
// Apply B^T in y-direction: QQ -> DQ0
// (Transpose of d/dx which uses DQ1(dy,qx)*B(dy,qy))
// Must produce DQ0(dy,qx) to match forward's DQ1 indexing
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * QQ(qx,qy);
}
DQ0(dy,qx) = u;
}
}
MFEM_SYNC_THREAD;
// Apply G^T in x-direction: DQ0 -> DD
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += G(dx,qx) * DQ0(dy,qx);
}
DD(dx,dy) = u;
}
}
MFEM_SYNC_THREAD;
// Accumulate to output
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,c,el) += DD(dx,dy);
}
}
MFEM_SYNC_THREAD;
// Now process du/dy component
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
// Load gradient components
real_t dq[3] = {0.0, 0.0, 0.0};
for (int d = 0; d < SDIM; ++d)
{
if (Q_LAYOUT == QVectorLayout::byVDIM) { dq[d] = q(c, d, qx, qy, el); }
else { dq[d] = q(qx, qy, c, d, el); }
}
// Apply inverse Jacobian transpose
real_t du[2] = {dq[0], dq[1]};
if (GRAD_PHYS)
{
if (SDIM == 2)
{
real_t Jloc[4], Jinv[4];
Jloc[0] = j(qx,qy,0,0,el);
Jloc[1] = j(qx,qy,1,0,el);
Jloc[2] = j(qx,qy,0,1,el);
Jloc[3] = j(qx,qy,1,1,el);
kernels::CalcInverse<2>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[2]*dq[1];
const real_t V = Jinv[1]*dq[0] + Jinv[3]*dq[1];
du[0] = U;
du[1] = V;
}
else // SDIM == 3
{
real_t Jloc[6], Jinv[6];
Jloc[0] = j(qx,qy,0,0,el);
Jloc[1] = j(qx,qy,1,0,el);
Jloc[2] = j(qx,qy,2,0,el);
Jloc[3] = j(qx,qy,0,1,el);
Jloc[4] = j(qx,qy,1,1,el);
Jloc[5] = j(qx,qy,2,1,el);
kernels::CalcLeftInverse<3,2>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[2]*dq[1] + Jinv[4]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[3]*dq[1] + Jinv[5]*dq[2];
du[0] = U;
du[1] = V;
}
}
QQ(qx, qy) = du[1]; // Store du/dy component
}
}
MFEM_SYNC_THREAD;
// Apply G^T in y-direction: QQ -> DQ1
// (Transpose of d/dy which uses DQ0(dy,qx)*G(dy,qy))
// Must produce DQ1(dy,qx) to match forward's DQ0 indexing
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += G(dy,qy) * QQ(qx,qy);
}
DQ1(dy,qx) = u;
}
}
MFEM_SYNC_THREAD;
// Apply B^T in x-direction: DQ1 -> DD
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * DQ1(dy,qx);
}
DD(dx,dy) = u;
}
}
MFEM_SYNC_THREAD;
// Accumulate to output
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,c,el) += DD(dx,dy);
}
}
MFEM_SYNC_THREAD;
}
});
}
template<QVectorLayout Q_LAYOUT, bool GRAD_PHYS,
int T_VDIM = 0, int T_D1D = 0, int T_Q1D = 0>
static void DerivativesTranspose3D(const int NE,
const real_t *b_,
const real_t *g_,
const real_t *j_,
const real_t *q_,
real_t *e_,
const int sdim = 3,
const int vdim = 0,
const int d1d = 0,
const int q1d = 0)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
const auto b = Reshape(b_, Q1D, D1D);
const auto g = Reshape(g_, Q1D, D1D);
const auto j = Reshape(j_, Q1D, Q1D, Q1D, 3, 3, NE);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, Q1D, Q1D, Q1D, VDIM, 3, NE):
Reshape(q_, VDIM, 3, Q1D, Q1D, Q1D, NE);
auto e = Reshape(e_, D1D, D1D, D1D, VDIM, NE);
mfem::forall_3D(NE, Q1D, Q1D, Q1D, [=] MFEM_HOST_DEVICE (int el)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_INTERP_1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_INTERP_1D;
MFEM_SHARED real_t BG[2][MQ1*MD1];
kernels::internal::LoadBG<MD1,MQ1>(D1D,Q1D,b,g,BG);
DeviceMatrix B(BG[0], D1D, Q1D);
DeviceMatrix G(BG[1], D1D, Q1D);
MFEM_SHARED real_t sm0[3][MQ1*MQ1*MQ1];
MFEM_SHARED real_t sm1[3][MQ1*MQ1*MQ1];
DeviceCube QQQ(sm0[0], MQ1, MQ1, MQ1);
DeviceCube DQQ(sm1[0], MD1, MQ1, MQ1);
DeviceCube DDQ(sm0[0], MD1, MD1, MQ1);
DeviceCube DDD(sm1[0], MD1, MD1, MD1);
for (int c = 0; c < VDIM; c++)
{
// Process du/dx component
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t dq[3];
if (Q_LAYOUT == QVectorLayout::byVDIM)
{
dq[0] = q(c,0,qx,qy,qz,el);
dq[1] = q(c,1,qx,qy,qz,el);
dq[2] = q(c,2,qx,qy,qz,el);
}
else
{
dq[0] = q(qx,qy,qz,c,0,el);
dq[1] = q(qx,qy,qz,c,1,el);
dq[2] = q(qx,qy,qz,c,2,el);
}
real_t du[3] = {dq[0], dq[1], dq[2]};
if (GRAD_PHYS)
{
real_t Jloc[9], Jinv[9];
for (int col = 0; col < 3; col++)
{
for (int row = 0; row < 3; row++)
{
Jloc[row+3*col] = j(qx,qy,qz,row,col,el);
}
}
kernels::CalcInverse<3>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[3]*dq[1] + Jinv[6]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[4]*dq[1] + Jinv[7]*dq[2];
const real_t W = Jinv[2]*dq[0] + Jinv[5]*dq[1] + Jinv[8]*dq[2];
du[0] = U; du[1] = V; du[2] = W;
}
QQQ(qx,qy,qz) = du[0];
}
}
}
MFEM_SYNC_THREAD;
// Apply G^T in x: QQQ -> DQQ (transpose of G⊗B⊗B)
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += G(dx,qx) * QQQ(qx,qy,qz);
}
DQQ(dx,qy,qz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in y: DQQ -> DDQ
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * DQQ(dx,qy,qz);
}
DDQ(dx,dy,qz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in z: DDQ -> DDD
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qz = 0; qz < Q1D; ++qz)
{
u += B(dz,qz) * DDQ(dx,dy,qz);
}
DDD(dx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Accumulate result
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,dz,c,el) += DDD(dx,dy,dz);
}
}
}
MFEM_SYNC_THREAD;
// Process du/dy component
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t dq[3];
if (Q_LAYOUT == QVectorLayout::byVDIM)
{
dq[0] = q(c,0,qx,qy,qz,el);
dq[1] = q(c,1,qx,qy,qz,el);
dq[2] = q(c,2,qx,qy,qz,el);
}
else
{
dq[0] = q(qx,qy,qz,c,0,el);
dq[1] = q(qx,qy,qz,c,1,el);
dq[2] = q(qx,qy,qz,c,2,el);
}
real_t du[3] = {dq[0], dq[1], dq[2]};
if (GRAD_PHYS)
{
real_t Jloc[9], Jinv[9];
for (int col = 0; col < 3; col++)
{
for (int row = 0; row < 3; row++)
{
Jloc[row+3*col] = j(qx,qy,qz,row,col,el);
}
}
kernels::CalcInverse<3>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[3]*dq[1] + Jinv[6]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[4]*dq[1] + Jinv[7]*dq[2];
const real_t W = Jinv[2]*dq[0] + Jinv[5]*dq[1] + Jinv[8]*dq[2];
du[0] = U; du[1] = V; du[2] = W;
}
QQQ(qx,qy,qz) = du[1];
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in x: QQQ -> DQQ (transpose of B⊗G⊗B)
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * QQQ(qx,qy,qz);
}
DQQ(dx,qy,qz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply G^T in y: DQQ -> DDQ
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += G(dy,qy) * DQQ(dx,qy,qz);
}
DDQ(dx,dy,qz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in z: DDQ -> DDD
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qz = 0; qz < Q1D; ++qz)
{
u += B(dz,qz) * DDQ(dx,dy,qz);
}
DDD(dx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Accumulate result
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,dz,c,el) += DDD(dx,dy,dz);
}
}
}
MFEM_SYNC_THREAD;
// Process du/dz component
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t dq[3];
if (Q_LAYOUT == QVectorLayout::byVDIM)
{
dq[0] = q(c,0,qx,qy,qz,el);
dq[1] = q(c,1,qx,qy,qz,el);
dq[2] = q(c,2,qx,qy,qz,el);
}
else
{
dq[0] = q(qx,qy,qz,c,0,el);
dq[1] = q(qx,qy,qz,c,1,el);
dq[2] = q(qx,qy,qz,c,2,el);
}
real_t du[3] = {dq[0], dq[1], dq[2]};
if (GRAD_PHYS)
{
real_t Jloc[9], Jinv[9];
for (int col = 0; col < 3; col++)
{
for (int row = 0; row < 3; row++)
{
Jloc[row+3*col] = j(qx,qy,qz,row,col,el);
}
}
kernels::CalcInverse<3>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[3]*dq[1] + Jinv[6]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[4]*dq[1] + Jinv[7]*dq[2];
const real_t W = Jinv[2]*dq[0] + Jinv[5]*dq[1] + Jinv[8]*dq[2];
du[0] = U; du[1] = V; du[2] = W;
}
QQQ(qx,qy,qz) = du[2];
}
}
}
MFEM_SYNC_THREAD;
// Apply G^T in z: QQQ -> DQQ (transpose of B⊗B⊗G)
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qz = 0; qz < Q1D; ++qz)
{
u += G(dz,qz) * QQQ(qx,qy,qz);
}
DQQ(qx,qy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in y: DQQ -> DDQ
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * DQQ(qx,qy,dz);
}
DDQ(qx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in x: DDQ -> DDD
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * DDQ(qx,dy,dz);
}
DDD(dx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Accumulate result
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,dz,c,el) += DDD(dx,dy,dz);
}
}
}
MFEM_SYNC_THREAD;
}
});
}
} // namespace quadrature_interpolator
} // namespace internal
template<int DIM, QVectorLayout Q_LAYOUT, bool GRAD_PHYS, int VDIM, int D1D,
int Q1D, int NBZ>
QuadratureInterpolator::GradTransposeKernelType
QuadratureInterpolator::GradTransposeKernels::Kernel()
{
if (DIM == 1) { return internal::quadrature_interpolator::DerivativesTranspose1D<Q_LAYOUT, GRAD_PHYS>; }
else if (DIM == 2) { return internal::quadrature_interpolator::DerivativesTranspose2D<Q_LAYOUT, GRAD_PHYS, VDIM, D1D, Q1D, NBZ>; }
else if (DIM == 3) { return internal::quadrature_interpolator::DerivativesTranspose3D<Q_LAYOUT, GRAD_PHYS, VDIM, D1D, Q1D>; }
else { MFEM_ABORT(""); }
}
} // namespace mfem
+50
View File
@@ -0,0 +1,50 @@
// 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 "../quadinterpolator.hpp"
#include "grad_transpose.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
template <bool P>
void InitGradTransposeByNodesKernels()
{
using k = QuadratureInterpolator::GradTransposeKernels;
constexpr auto L = QVectorLayout::byNODES;
// 2D
k::Specialization<2,L,P,1,3,4>::template Opt<8>::Add();
k::Specialization<2,L,P,1,4,6>::template Opt<4>::Add();
k::Specialization<2,L,P,1,5,8>::template Opt<2>::Add();
k::Specialization<2,L,P,2,3,3>::template Opt<8>::Add();
k::Specialization<2,L,P,2,3,4>::template Opt<8>::Add();
k::Specialization<2,L,P,2,4,6>::template Opt<4>::Add();
k::Specialization<2,L,P,2,5,8>::template Opt<2>::Add();
// 3D
k::Specialization<3,L,P,1,3,4>::Add();
k::Specialization<3,L,P,1,4,6>::Add();
k::Specialization<3,L,P,1,5,8>::Add();
k::Specialization<3,L,P,3,3,4>::Add();
k::Specialization<3,L,P,3,4,6>::Add();
k::Specialization<3,L,P,3,5,8>::Add();
}
template void InitGradTransposeByNodesKernels<false>();
template void InitGradTransposeByNodesKernels<true>();
} // namespace quadrature_interpolator
} // namespace internal
} // namespace mfem
+50
View File
@@ -0,0 +1,50 @@
// 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 "../quadinterpolator.hpp"
#include "grad_transpose.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
template <bool P>
void InitGradTransposeByVDimKernels()
{
using k = QuadratureInterpolator::GradTransposeKernels;
constexpr auto L = QVectorLayout::byVDIM;
// 2D
k::Specialization<2,L,P,1,3,4>::template Opt<8>::Add();
k::Specialization<2,L,P,1,4,6>::template Opt<4>::Add();
k::Specialization<2,L,P,1,5,8>::template Opt<2>::Add();
k::Specialization<2,L,P,2,3,3>::template Opt<8>::Add();
k::Specialization<2,L,P,2,3,4>::template Opt<8>::Add();
k::Specialization<2,L,P,2,4,6>::template Opt<4>::Add();
k::Specialization<2,L,P,2,5,8>::template Opt<2>::Add();
// 3D
k::Specialization<3,L,P,1,3,4>::Add();
k::Specialization<3,L,P,1,4,6>::Add();
k::Specialization<3,L,P,1,5,8>::Add();
k::Specialization<3,L,P,3,3,4>::Add();
k::Specialization<3,L,P,3,4,6>::Add();
k::Specialization<3,L,P,3,5,8>::Add();
}
template void InitGradTransposeByVDimKernels<false>();
template void InitGradTransposeByVDimKernels<true>();
} // namespace quadrature_interpolator
} // namespace internal
} // namespace mfem
+63 -9
View File
@@ -11,6 +11,7 @@
#include "quadinterpolator.hpp"
#include "qinterp/grad.hpp"
#include "qinterp/grad_transpose.hpp"
#include "qinterp/eval.hpp"
#include "qspace.hpp"
#include "../general/forall.hpp"
@@ -30,7 +31,10 @@ void InitEvalKernels();
void InitDetKernels();
template <bool P> void InitGradByNodesKernels();
template <bool P> void InitGradByVDimKernels();
template <bool P> void InitGradTransposeByNodesKernels();
template <bool P> void InitGradTransposeByVDimKernels();
void InitTensorEvalHDivKernels();
void InitEvalTransposeByVDimKernels();
struct Kernels
{
Kernels()
@@ -45,12 +49,19 @@ struct Kernels
// Phys grad kernels
InitGradByNodesKernels<true>();
InitGradByVDimKernels<true>();
// Non-phys grad transpose kernels
InitGradTransposeByNodesKernels<false>();
InitGradTransposeByVDimKernels<false>();
// Phys grad transpose kernels
InitGradTransposeByNodesKernels<true>();
InitGradTransposeByVDimKernels<true>();
// Determinants
InitDetKernels();
// Non-tensor
InitEvalKernels();
// Tensor (quad,hex) H(div)
InitTensorEvalHDivKernels();
InitEvalTransposeByVDimKernels();
}
};
}
@@ -646,16 +657,59 @@ void QuadratureInterpolator::MultHDiv(const Vector &e_vec,
MFEM_CONTRACT_VAR(q_div);
}
void QuadratureInterpolator::MultTranspose(unsigned eval_flags,
const Vector &q_val,
const Vector &q_der,
Vector &e_vec) const
void QuadratureInterpolator::AddMultTranspose(unsigned eval_flags,
const Vector &q_val,
const Vector &q_der,
Vector &e_vec) const
{
MFEM_CONTRACT_VAR(eval_flags);
MFEM_CONTRACT_VAR(q_val);
MFEM_CONTRACT_VAR(q_der);
MFEM_CONTRACT_VAR(e_vec);
MFEM_ABORT("this method is not implemented yet");
const int ne = fespace->GetNE();
if (ne == 0) { return; }
const FiniteElement *fe = fespace->GetFE(0);
const int vdim = fespace->GetVDim();
const int sdim = fespace->GetMesh()->SpaceDimension();
const bool use_tensor_eval =
use_tensor_products &&
dynamic_cast<const TensorBasisElement*>(fe) != nullptr;
const IntegrationRule *ir =
IntRule ? IntRule : &qspace->GetElementIntRule(0);
const DofToQuad::Mode mode =
use_tensor_eval ? DofToQuad::TENSOR : DofToQuad::FULL;
const DofToQuad &maps = fe->GetDofToQuad(*ir, mode);
const int dim = maps.FE->GetDim();
const int nd = maps.ndof;
const int nq = maps.nqpt;
const GeometricFactors *geom = nullptr;
if (eval_flags & PHYSICAL_DERIVATIVES)
{
const int jacobians = GeometricFactors::JACOBIANS;
geom = fespace->GetMesh()->GetGeometricFactors(*ir, jacobians);
}
if (use_tensor_eval)
{
if (eval_flags & (VALUES | PHYSICAL_VALUES))
{
TensorEvalTransposeKernels::Run(dim, q_layout, vdim, nd, nq, ne,
maps.B.Read(), q_val.Read(),
e_vec.ReadWrite(), vdim, nd, nq);
}
if (eval_flags & (DERIVATIVES | PHYSICAL_DERIVATIVES))
{
const bool phys = (eval_flags & PHYSICAL_DERIVATIVES);
const real_t *J = phys ? geom->J.Read() : nullptr;
const int s_dim = phys ? sdim : dim;
GradTransposeKernels::Run(dim, q_layout, phys, vdim, nd, nq, ne,
maps.B.Read(), maps.G.Read(), J,
q_der.Read(), e_vec.ReadWrite(),
s_dim, vdim, nd, nq);
}
}
else
{
MFEM_ABORT("Non-tensor MultTranspose not yet implemented");
}
}
void QuadratureInterpolator::Values(const Vector &e_vec,
+13 -2
View File
@@ -152,8 +152,8 @@ public:
void Determinants(const Vector &e_vec, Vector &q_det) const;
/// Perform the transpose operation of Mult(). (TODO)
void MultTranspose(unsigned eval_flags, const Vector &q_val,
const Vector &q_der, Vector &e_vec) const;
void AddMultTranspose(unsigned eval_flags, const Vector &q_val,
const Vector &q_der, Vector &e_vec) const;
/// @brief Returns true if the given finite element space is supported by
/// QuadratureInterpolator.
@@ -178,6 +178,13 @@ public:
using TensorEvalHDivKernelType =
void(*)(const int, const real_t *, const real_t *, const real_t *,
const real_t *, real_t *, const int, const int);
using TensorEvalTransposeKernelType = void(*)(const int, const real_t *,
const real_t *,
real_t *, const int, const int, const int);
using GradTransposeKernelType = void(*)(const int, const real_t *,
const real_t *, const real_t *,
const real_t *, real_t *,
const int, const int, const int, const int);
MFEM_REGISTER_KERNELS(TensorEvalKernels, TensorEvalKernelType,
(int, QVectorLayout, int, int, int), (int));
@@ -189,6 +196,10 @@ public:
(int, QVectorLayout, bool, int, int), (int));
MFEM_REGISTER_KERNELS(TensorEvalHDivKernels, TensorEvalHDivKernelType,
(int, QVectorLayout, unsigned, int, int));
MFEM_REGISTER_KERNELS(TensorEvalTransposeKernels, TensorEvalTransposeKernelType,
(int, QVectorLayout, int, int, int), (int));
MFEM_REGISTER_KERNELS(GradTransposeKernels, GradTransposeKernelType,
(int, QVectorLayout, bool, int, int, int), (int));
};
}
+16
View File
@@ -27,6 +27,14 @@
#endif
#include "hip.hpp"
#if defined(MFEM_USE_CUDA)
#define Gpu(...) Cu##__VA_ARGS__
#elif defined(MFEM_USE_HIP)
#define Gpu(...) Hip##__VA_ARGS__
#else
#define Gpu(...) __VA_ARGS__
#endif
#ifdef MFEM_USE_OCCA
#include "occa.hpp"
#endif
@@ -48,6 +56,7 @@ constexpr bool mfem_use_gpu = false;
#define MFEM_DEVICE
#define MFEM_HOST
#define MFEM_LAMBDA
#define MFEM_CONSTANT
// #define MFEM_HOST_DEVICE // defined in config/config.hpp
// MFEM_DEVICE_SYNC is made available for debugging purposes
#define MFEM_DEVICE_SYNC
@@ -65,6 +74,13 @@ constexpr bool mfem_use_gpu = false;
#define MFEM_THREAD_SIZE(k) 1
#define MFEM_FOREACH_THREAD(i,k,N) for(int i=0; i<N; i++)
#define MFEM_FOREACH_THREAD_DIRECT(i,k,N) MFEM_FOREACH_THREAD(i,k,N)
inline const void* MemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes)
{
memcpy(const_cast<void *>(d_sym), h_src, bytes);
return d_sym;
}
#endif
// 'double' and 'float' atomicAdd implementation for previous versions of CUDA
+11
View File
@@ -175,6 +175,17 @@ void* CuMemcpyDtoHAsync(void *dst, const void *src, size_t bytes)
return dst;
}
const void* CuMemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes)
{
#ifdef MFEM_USE_CUDA
MFEM_GPU_CHECK(cudaMemcpyToSymbol(d_sym, h_src, bytes));
return d_sym;
#endif
MFEM_ABORT("CUDA has no shadow host copy of device symbols");
return memcpy(const_cast<void*>(d_sym), h_src, bytes);
}
void CuCheckLastError()
{
#ifdef MFEM_USE_CUDA
+6
View File
@@ -25,6 +25,8 @@ constexpr bool mfem_use_gpu = true;
#define MFEM_HOST __host__
#define MFEM_LAMBDA __host__
#define MFEM_LAUNCH_BOUNDS __launch_bounds__
#define MFEM_DEVICE __device__
#define MFEM_CONSTANT __constant__
// #define MFEM_HOST_DEVICE __host__ __device__ // defined in config/config.hpp
#define MFEM_DEVICE_SYNC MFEM_GPU_CHECK(cudaDeviceSynchronize())
#define MFEM_STREAM_SYNC MFEM_GPU_CHECK(cudaStreamSynchronize(0))
@@ -94,6 +96,10 @@ void* CuMemcpyDtoH(void *h_dst, const void *d_src, size_t bytes);
/// Copies memory from Device to Host
void* CuMemcpyDtoHAsync(void *h_dst, const void *d_src, size_t bytes);
/// Copies data to the given symbol on the device.
const void* CuMemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes);
/// Check the error code returned by cudaGetLastError(), aborting on error.
void CuCheckLastError();
+6
View File
@@ -1090,6 +1090,12 @@ inline void forall_2D_batch(int N, int X, int Y, int BZ, lambda &&body)
ForallWrap<2>(true, N, body, X, Y, BZ);
}
template<int MAX_THREADS_PER_BLOCK, typename lambda>
inline void forall_2D_batch(int N, int X, int Y, int BZ, lambda &&body)
{
ForallWrap<2, MAX_THREADS_PER_BLOCK>(true, N, body, X, Y, BZ);
}
template<typename lambda>
inline void forall_3D(int N, int X, int Y, int Z, lambda &&body)
{
+9
View File
@@ -175,6 +175,15 @@ void* HipMemcpyDtoHAsync(void *dst, const void *src, size_t bytes)
return dst;
}
const void* HipMemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes)
{
#ifdef MFEM_USE_HIP
MFEM_GPU_CHECK(hipMemcpyToSymbol(d_sym, h_src, bytes));
#endif
return memcpy(const_cast<void*>(d_sym), h_src, bytes);
}
void HipCheckLastError()
{
#ifdef MFEM_USE_HIP
+6 -1
View File
@@ -21,8 +21,9 @@
#if defined(MFEM_USE_HIP) && defined(__HIP__)
#define MFEM_USE_CUDA_OR_HIP
constexpr bool mfem_use_gpu = true;
#define MFEM_DEVICE __device__
#define MFEM_HOST __host__
#define MFEM_DEVICE __device__
#define MFEM_CONSTANT __constant__
#define MFEM_LAMBDA __host__ __device__
#define MFEM_LAUNCH_BOUNDS __launch_bounds__
// #define MFEM_HOST_DEVICE __host__ __device__ // defined in config/config.hpp
@@ -96,6 +97,10 @@ void* HipMemcpyDtoH(void *h_dst, const void *d_src, size_t bytes);
/// Copies memory from Device to Host
void* HipMemcpyDtoHAsync(void *h_dst, const void *d_src, size_t bytes);
/// Copies data to the given symbol on the device.
const void* HipMemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes);
/// Check the error code returned by hipGetLastError(), aborting on error.
void HipCheckLastError();
+1
View File
@@ -0,0 +1 @@
../../stash/debug/nvtx.hpp
+2
View File
@@ -27,6 +27,7 @@ list(APPEND SRCS
handle.cpp
matrix.cpp
mma.cpp
multivector.cpp
ode.cpp
operator.cpp
ordering.cpp
@@ -63,6 +64,7 @@ list(APPEND HDRS
linalg.hpp
matrix.hpp
mma.hpp
multivector.hpp
ode.hpp
operator.hpp
ordering.hpp
+2
View File
@@ -15,6 +15,7 @@
// Linear algebra header file
#include "vector.hpp"
#include "multivector.hpp"
#include "operator.hpp"
#include "matrix.hpp"
#include "sparsemat.hpp"
@@ -37,6 +38,7 @@
#include "batched/gpu_blas.hpp"
#include "batched/solver.hpp"
#include "tensor.hpp"
#include "tensor_arrays.hpp"
#include "filteredsolver.hpp"
#include "ordering.hpp"
#include "particlevector.hpp"
+60
View File
@@ -0,0 +1,60 @@
// 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 "multivector.hpp"
namespace mfem
{
MultiVector::MultiVector(const Array<int> &vector_sizes)
{
SetSizes(vector_sizes);
}
MultiVector::MultiVector(const Array<int> &vector_sizes, MemoryType mt)
{
SetSizes(vector_sizes, mt);
}
MultiVector::MultiVector(Vector &base, const Array<int> &vector_sizes)
{
MakeRef(base, vector_sizes);
}
void MultiVector::SetSizes(const Array<int> &vector_sizes)
{
blocks.resize(vector_sizes.Size());
for (int i = 0; i < vector_sizes.Size(); i++)
{
operator[](i).SetSize(vector_sizes[i]);
}
}
void MultiVector::SetSizes(const Array<int> &vector_sizes, MemoryType mt)
{
blocks.resize(vector_sizes.Size());
for (int i = 0; i < vector_sizes.Size(); i++)
{
operator[](i).SetSize(vector_sizes[i], mt);
}
}
void MultiVector::MakeRef(Vector &base, const Array<int> &vector_sizes)
{
blocks.resize(vector_sizes.Size());
for (int offset = 0, i = 0; i < vector_sizes.Size(); i++)
{
blocks[i].emplace<0>(base, offset, vector_sizes[i]);
offset += vector_sizes[i];
}
}
} // namespace mfem
+198
View File
@@ -0,0 +1,198 @@
// 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_MULTIVECTOR_HPP
#define MFEM_MULTIVECTOR_HPP
#include "../general/array.hpp"
#include "vector.hpp"
#include <vector>
#include <array>
#include <variant>
namespace mfem
{
/// Class representing an array of Vectors with generally different sizes.
/** This class is similar to BlockVector with the following two main
differences:
- the data for the individual Vector blocks does not need to be part of one
big contiguous memory allocation;
- this class does not inherit from class Vector (as a consequence of the
first bullet).
Internally, each Vector block is represented as either:
- (default) a Vector object constructed and owned by this class; this
object, in turn, as any Vector object, can own its Memory allocation or
refer to a sub-Memory of another Memory object; or
- a pointer to an externally allocated Vector or classes derived from
Vector. */
class MultiVector
{
private:
std::vector<std::variant<Vector,Vector*>> blocks;
public:
/// Create an empty MultiVector with zero blocks.
MultiVector() = default;
/** @brief Create a MultiVector with @a num_blocks blocks. The individual
Vector blocks are default initialized, i.e. they all have size zero. */
MultiVector(int num_blocks)
: blocks(num_blocks) { }
/** @brief Construct a MultiVector with number of blocks and individual block
Vector sizes given by @a vector_sizes.
@note The memory of the individual Vector blocks is NOT initialized. */
MultiVector(const Array<int> &vector_sizes);
/** @brief Construct a MultiVector with number of blocks and individual block
Vector sizes given by @a vector_sizes. All Vector blocks use the
MemoryType @a mt.
@note The memory of the individual Vector blocks is NOT initialized. */
MultiVector(const Array<int> &vector_sizes, MemoryType mt);
/** @brief Construct a MultiVector referencing data within a given monolithic
Vector @a base.
With this constructor, the Memory flags of @a base and of the individual
Vector blocks may need to be explicitly synchronized when data is moved
between host and device. */
MultiVector(Vector &base, const Array<int> &vector_sizes);
/** @brief Construct a MultiVector referencing multiple Vectors given as
arguments.
The VectorTypes reference arguments are expected to be static_cast-able
to (Vector &) which is the case if the types are derived from Vector,
e.g. HypreParVector, GridFunction, etc.
With this constructor, operations on individual Vector blocks are
performed directly on the objects @a vs. In particular, there is no need
to synchronize the Memory flags of @a vs and the ones of the individual
Vector blocks when data is moved between host and device. */
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<VectorTypes&,Vector&>...>, bool> = true>
MultiVector(VectorTypes &...vs) { MakeRef(vs...); }
/// Return the number of Vectors in the MultiVector.
int NumBlocks() const { return blocks.size(); }
/** @brief Set the number of Vectors in the MultiVector. Existing Vector
blocks will remain unmodified. New Vector blocks will be default
initialized, i.e. they all have size zero. */
void SetNumBlocks(int num_blocks) { blocks.resize(num_blocks); }
/// Read-write access to the i-th Vector.
inline Vector &operator[](int i);
/// Read-only access to the i-th Vector.
inline const Vector &operator[](int i) const;
/** @brief Update the MultiVector according to the given @a vector_sizes.
This method can be used to add or remove blocks. The individual Vector
sizes are updated using the method Vector::SetSize(int). */
void SetSizes(const Array<int> &vector_sizes);
/** @brief Update the MultiVector according to the given @a vector_sizes and
MemoryType @a mt.
This method can be used to add or remove blocks. The individual Vector
sizes and MemoryType are updated using the method
Vector::SetSize(int, MemoryType). */
void SetSizes(const Array<int> &vector_sizes, MemoryType mt);
/** @brief Update the MultiVector to reference data within a given monolithic
Vector @a base.
After calling this method, the Memory flags of @a base and of the
individual Vector blocks may need to be explicitly synchronized when data
is moved between host and device.*/
void MakeRef(Vector &base, const Array<int> &vector_sizes);
/** @brief Update the @a i-th MultiVector block to reference data within the
given monolithic Vector @a base at the given @a offset and with the given
@a size.
After calling this method, the Memory flags of @a base and of the @a i-th
Vector block may need to be explicitly synchronized when data is moved
between host and device.*/
inline void MakeRef(int i, Vector &base, int offset, int size)
{
blocks[i].emplace<0>(base, offset, size);
}
/** @brief Update the MultiVector to reference multiple Vectors given as
arguments.
The VectorTypes reference arguments are expected to be static_cast-able
to (Vector &) which is the case if the types are derived from Vector,
e.g. HypreParVector, GridFunction, etc.
After calling this method, operations on individual Vector blocks are
performed directly on the objects @a vs. In particular, there is no need
to synchronize the Memory flags of @a vs and the ones of the individual
Vector blocks when data is moved between host and device. */
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<VectorTypes&,Vector&>...>, bool> = true>
inline void MakeRef(VectorTypes &...vs);
/** @brief Update the @a i-th MultiVector block to reference the given
Vector @a v.
After calling this method, operations on the @a i-th Vector block are
performed directly on the Vector @a v. In particular, there is no need
to synchronize the Memory flags of @a v and the ones of the @a i-th
Vector blocks when data is moved between host and device. */
inline void MakeRef(int i, Vector &v) { blocks[i] = &v; }
};
// Inline and template methods
inline Vector &MultiVector::operator[](int i)
{
auto &bi = blocks[i];
return (bi.index() == 0) ? std::get<0>(bi) : *std::get<1>(bi);
}
inline const Vector &MultiVector::operator[](int i) const
{
auto &bi = blocks[i];
return (bi.index() == 0) ? std::get<0>(bi) : *std::get<1>(bi);
}
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<VectorTypes&,Vector&>...>, bool>>
inline void MultiVector::MakeRef(VectorTypes &...vs)
{
blocks.resize(sizeof...(vs));
if constexpr (sizeof...(vs) > 0)
{
const std::array vs_p{&static_cast<Vector&>(vs)...};
for (std::size_t i = 0; i < sizeof...(vs); i++)
{
blocks[i] = vs_p[i];
}
}
}
} // namespace mfem
#endif // MFEM_MULTIVECTOR_HPP
+10
View File
@@ -111,6 +111,16 @@ void Operator::ArrayAddMultTranspose(const Array<const Vector *> &X,
}
}
void Operator::Mult(const MultiVector &, MultiVector &)
{
MFEM_ABORT("this method is not overriden for this class!");
}
Operator &Operator::GetGradient(const MultiVector &) const
{
MFEM_ABORT("this method is not overriden for this class!");
}
void Operator::FormLinearSystem(const Array<int> &ess_tdof_list,
Vector &x, Vector &b,
Operator* &Aout, Vector &X, Vector &B,
+21
View File
@@ -13,6 +13,7 @@
#define MFEM_OPERATOR
#include "vector.hpp"
#include "multivector.hpp"
namespace mfem
{
@@ -129,6 +130,16 @@ public:
virtual void ArrayAddMultTranspose(const Array<const Vector *> &X,
Array<Vector *> &Y, const real_t a = 1.0) const;
/** @brief Operator application, y = A(x), where the input @a x and the
output @a y are MultiVector objects, i.e. they generally use
non-contiguous memory representation.
The typical use case for this method are block operators like
DifferentiableOperator.
The base class implementation for the method is to generate an error. */
virtual void Mult(const MultiVector &x, MultiVector &y);
/** @brief Evaluate the gradient operator at the point @a x. The default
behavior in class Operator is to generate an error. */
virtual Operator &GetGradient(const Vector &x) const
@@ -137,6 +148,16 @@ public:
return const_cast<Operator &>(*this);
}
/** @brief Evaluate the gradient operator at the point @a x. The input @a x
is provided as a MultiVector, i.e. it generally uses non-contiguous
memory representation.
The typical use case for this method are block operators like
DifferentiableOperator.
The base class implementation for the method is to generate an error. */
virtual Operator &GetGradient(const MultiVector &x) const;
/** @brief Computes the diagonal entries into @a diag. Typically, this
operation only makes sense for linear Operator%s. In some cases, only an
approximation of the diagonal is computed. */
+106 -9
View File
@@ -33,17 +33,62 @@ struct tensor;
/// The implementation can be drastically generalized by using concepts of the
/// c++17 standard.
template < typename T >
template <typename T>
struct tensor<T>
{
using type = T;
static constexpr int ndim = 1;
static constexpr int first_dim = 0;
MFEM_HOST_DEVICE T& operator[](int /*unused*/) { return values; }
MFEM_HOST_DEVICE const T& operator[](int /*unused*/) const { return values; }
MFEM_HOST_DEVICE T& operator()(int /*unused*/) { return values; }
MFEM_HOST_DEVICE const T& operator()(int /*unused*/) const { return values; }
MFEM_HOST_DEVICE tensor() = default;
MFEM_HOST_DEVICE tensor(T v) : values(v) {}
MFEM_HOST_DEVICE T& operator[](int) { return values; }
MFEM_HOST_DEVICE const T& operator[](int) const { return values; }
MFEM_HOST_DEVICE T& operator()(int) { return values; }
MFEM_HOST_DEVICE const T& operator()(int) const { return values; }
MFEM_HOST_DEVICE T& operator()() { return values; }
MFEM_HOST_DEVICE const T& operator()() const { return values; }
MFEM_HOST_DEVICE operator T() const { return values; }
MFEM_HOST_DEVICE constexpr const T& scalar() const { return values; }
// A * tensor<T> -> A * T
template <typename A,
std::enable_if_t<!std::is_same_v<std::decay_t<A>, tensor>, int> = 0>
MFEM_HOST_DEVICE friend auto operator*(const A& a, const tensor& s)
-> decltype(a * s.scalar())
{
return a * s.scalar();
}
// tensor<T> * A -> T * A
template <typename A,
std::enable_if_t<!std::is_same_v<std::decay_t<A>, tensor>, int> = 0>
MFEM_HOST_DEVICE friend auto operator*(const tensor& s, const A& a)
-> decltype(s.scalar() * a)
{
return s.scalar() * a;
}
// A / tensor<T>, tensor<T> / A
template <typename A,
std::enable_if_t<!std::is_same_v<std::decay_t<A>, tensor>, int> = 0>
MFEM_HOST_DEVICE friend auto operator/(const A& a, const tensor& s)
-> decltype(a / s.scalar())
{
return a / s.scalar();
}
template <typename A,
std::enable_if_t<!std::is_same_v<std::decay_t<A>, tensor>, int> = 0>
MFEM_HOST_DEVICE friend auto operator/(const tensor& s, const A& a)
-> decltype(s.scalar() / a)
{
return s.scalar() / a;
}
T values;
};
@@ -120,6 +165,23 @@ struct tensor<T, n0, n1, n2>
tensor < T, n1, n2 > values[n0];
};
template < typename T, int n1, int n2 >
struct tensor<T, 0, n1, n2>
{
using type = T;
static constexpr int ndim = 3;
static constexpr int first_dim = 0;
MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator[](int /*i*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator[](int /*i*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator()(int /*i*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator()(int /*i*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n2 >& operator()(int /*i*/, int j) { return values[j]; }
MFEM_HOST_DEVICE const tensor< T, n2 >& operator()(int i, int j) const { return values[i][j]; }
MFEM_HOST_DEVICE T& operator()(int /*i*/, int j, int k) { return values[j][k]; }
MFEM_HOST_DEVICE const T& operator()(int /*i*/, int j, int k) const { return values[j][k]; }
tensor < T, n1, n2 > values;
};
template < typename T, int n0, int n1, int n2, int n3 >
struct tensor<T, n0, n1, n2, n3>
{
@@ -139,6 +201,26 @@ struct tensor<T, n0, n1, n2, n3>
tensor < T, n1, n2, n3 > values[n0];
};
template < typename T, int n1, int n2, int n3 >
struct tensor<T, 0, n1, n2, n3>
{
using type = T;
static constexpr int ndim = 4;
static constexpr int first_dim = 0;
MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator[](int /*i*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator[](int /*i*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator()(int /*i*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator()(int /*i*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n2, n3 >& operator()(int /*i*/, int j) { return values[j]; }
MFEM_HOST_DEVICE const tensor< T, n2, n3 >& operator()(int /*i*/, int j) const { return values[j]; }
MFEM_HOST_DEVICE tensor< T, n3 >& operator()(int /*i*/, int j, int k) { return values[j][k]; }
MFEM_HOST_DEVICE const tensor< T, n3 >& operator()(int /*i*/, int j,
int k) const { return values[j][k]; }
MFEM_HOST_DEVICE T& operator()(int /*i*/, int j, int k, int l) { return values[j][k][l]; }
MFEM_HOST_DEVICE const T& operator()(int /*i*/, int j, int k, int l) const { return values[j][k][l]; }
tensor < T, n1, n2, n3 > values;
};
template < typename T, int n0, int n1, int n2, int n3, int n4 >
struct tensor<T, n0, n1, n2, n3, n4>
{
@@ -541,13 +623,21 @@ MFEM_HOST_DEVICE auto operator*(S scale, const tensor<T, n...>& A) ->
tensor<decltype(S {} * T{}), n...>
{
tensor<decltype(S{} * T{}), n...> C{};
for (int i = 0; i < tensor<T, n...>::first_dim; i++)
if constexpr (sizeof...(n) == 0)
{
C[i] = scale * A[i];
C.values = scale * A.values;
}
else
{
for (int i = 0; i < tensor<T, n...>::first_dim; i++)
{
C[i] = scale * A[i];
}
}
return C;
}
/**
* @brief multiply a tensor by a scalar value
* @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
@@ -563,9 +653,16 @@ MFEM_HOST_DEVICE auto operator*(const tensor<T, n...>& A, S scale) ->
tensor<decltype(T {} * S{}), n...>
{
tensor<decltype(T{} * S{}), n...> C{};
for (int i = 0; i < tensor<T, n...>::first_dim; i++)
if constexpr (sizeof...(n) == 0)
{
C[i] = A[i] * scale;
C.values = A.values * scale;
}
else
{
for (int i = 0; i < tensor<T, n...>::first_dim; i++)
{
C[i] = A[i] * scale;
}
}
return C;
}
+348
View File
@@ -0,0 +1,348 @@
// 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_TENSOR_ARRAYS_HPP
#define MFEM_TENSOR_ARRAYS_HPP
#include "tensor.hpp"
#include <array> // std::array, std::size_t (indirectly)
#include <type_traits> /* std::remove_cv_t, std::remove_reference_t,
std::is_const_v */
#include <utility> /* std::forward, std::index_sequence,
std::make_index_sequence */
#include <algorithm> // std::min
#include <tuple> // std::apply, std::tuple_size_v
#include <numeric> // std::iota
namespace mfem
{
namespace future
{
template <std::size_t... Is, typename Fn>
constexpr inline void for_unrolled_simple(std::index_sequence<Is...>, Fn &&fn)
{
(fn(Is), ...);
}
template <int... loop_sizes>
__attribute__((annotate("enzyme_inactive")))
constexpr inline auto to_multiindex(std::size_t i)
{
constexpr auto dims = sizeof...(loop_sizes);
constexpr std::array<std::size_t,dims> sizes{loop_sizes...};
std::array<std::size_t,dims> is{}; // value initialization with zeros
for (std::size_t d = 0; d < dims; d++)
{
is[d] = i%sizes[d];
i /= sizes[d];
}
return is;
}
/// lambda_t:
/// - input: const std::array<std::size_t,sizeof...(loop_sizes)> &
/// - output: void
/// Note: 0D loop executes the lambda one time with an array of dim 0.
template <int... loop_sizes, typename lambda_t>
constexpr inline void for_multiindex(lambda_t f)
{
constexpr auto dims = sizeof...(loop_sizes);
if constexpr (dims == 0)
{
f(std::array<std::size_t,0> {});
}
else
{
if constexpr (std::min({loop_sizes...}) <= 0) { return; }
constexpr auto total_loop_size = (loop_sizes * ...);
for_unrolled_simple(std::make_index_sequence<total_loop_size> {},
[&f](std::size_t i)
{
f(to_multiindex<loop_sizes...>(i));
});
}
}
/// Extend std::apply to work with 0-size arrays.
template <typename Fn, typename Tuple>
inline constexpr decltype(auto) apply(Fn&& f, Tuple&& t)
{
if constexpr (std::tuple_size_v<std::remove_reference_t<Tuple>> == 0)
{ return f(); }
return std::apply(std::forward<Fn>(f), std::forward<Tuple>(t));
}
/// Multi-dimensional array of tensors of the same size.
/** The array sizes are dynamic while the tensor sizes are static, i.e. template
parameters.
This class provides flexible global data layout where the dynamic (array)
dimnsions and the tensor dimnsions are stored in memory using a runtime
defined strided layout. */
template <typename scalar_t, int ndims, int... tensor_sizes>
class tensor_ndarray
{
public:
typedef scalar_t scalar_type;
typedef tensor<std::remove_cv_t<scalar_t>,tensor_sizes...> tensor_type;
static constexpr std::integer_sequence<size_t, tensor_sizes...> tensor_sizes_;
static constexpr auto tensor_dims = sizeof...(tensor_sizes);
static constexpr auto total_dims = ndims + tensor_dims;
static constexpr std::array<std::size_t,tensor_dims>
tensor_sizes_array{tensor_sizes...};
private:
scalar_t *data; /// Not owned
std::array<std::size_t,ndims> dyn_sizes;
std::array<std::size_t,total_dims> strides;
public:
/** @brief Constructor with the default, column-major or left, layout where
the dynamic dimensions are first, on the left, and the tensor dimensions
are second. */
tensor_ndarray(scalar_t *ptr, std::array<std::size_t,ndims> dynamic_sizes)
: data(ptr), dyn_sizes(dynamic_sizes)
{
std::array<std::size_t,total_dims> default_perm;
std::iota(default_perm.begin(), default_perm.end(), 0); // 0, 1, 2, ...
set_layout(default_perm);
}
/// Number of dynamic array dimensions.
static constexpr std::size_t rank() { return ndims; }
/// Array size in the @a k-th dynamic dimension.
std::size_t size(int k = 0) const { return dyn_sizes[k]; }
/// Returns the product of all sizes of the dynamic dimensions.
std::size_t total_size() const
{
std::size_t t = 1;
for (int d = 0; d < ndims; d++)
{
t *= dyn_sizes[d];
}
return t;
}
/// Number of tensor (static) dimensions.
static constexpr std::size_t tensor_rank()
{ return sizeof...(tensor_sizes); }
/// Tensor size in the @a k-th tensor (static) dimension.
static constexpr std::size_t tensor_size(int k = 0)
{ return tensor_sizes_array[k]; }
/// Returns the product of all sizes of the static (tensor) dimensions.
static constexpr std::size_t total_tensor_size()
{ return (tensor_sizes * ...); }
/// Set the global data layout based on the given permutation @a perm.
/** The entries of @a perm are numbers identifying either a dynamic or a
tensor (static) dimension. Values in the range [0,rank()) identify the
dynamic dimensions and values in the range [rank(),rank()+tensor_rank())
identify the tensor dymensions. The first entry in @a perm determines
which dynamic or tensor dimension will have stride 1. The k-th entry of
@a perm determines which dimension will use the next stride which is
defined as the product of the sizes of all k-1 previous dimensions from
@a perm.
@note The default layout corresponds to the identity permutation:
{ 0, 1, ..., rank()+tensor_rank()-1 }.
@note This method does not permute the global 1D data array. */
void set_layout(std::array<std::size_t,rank()+tensor_rank()> perm)
{
std::size_t stride = 1;
for (std::size_t d_g = 0; d_g < total_dims; d_g++)
{
const auto d_l = perm[d_g];
strides[d_l] = stride;
stride *= (d_l < ndims) ? dyn_sizes[d_l] :
tensor_sizes_array[d_l-ndims];
}
}
/** @brief Comute the dynamic offset for a given dynamic multi-index @a is.
The total offset in the global data array is the sum of the dynamic and
static (tensor) offsets. */
std::size_t get_dynamic_offset(
const std::array<std::size_t,rank()> &is) const
{
std::size_t dynamic_offset = 0;
for (std::size_t d = 0; d < ndims; d++)
{
dynamic_offset += is[d]*strides[d];
}
return dynamic_offset;
}
/** @brief Comute the static (tensor) offset for a given tensor multi-index
@a js. The total offset in the global data array is the sum of the
dynamic and static (tensor) offsets. */
std::size_t get_static_offset(
const std::array<std::size_t,tensor_rank()> &js) const
{
std::size_t static_offset = 0;
for (std::size_t d = 0; d < tensor_dims; d++)
{
static_offset += js[d]*strides[ndims+d];
}
return static_offset;
}
/** @brief Return a local tensor extracted from the global data array
corresponding to the given dynamic multi-index @a is. */
/** @note Return a const tensor to prevent attempts to assign to the
temporary object which is considered a mistake. */
const tensor_type get_tensor(std::array<std::size_t,rank()> is) const
{
tensor_type result;
const std::size_t dynamic_offset = get_dynamic_offset(is);
for_multiindex<tensor_sizes...>(
[&result, this, dynamic_offset](
const std::array<std::size_t,tensor_rank()> &js)
{
::mfem::future::apply(result, js) =
data[dynamic_offset + get_static_offset(js)];
});
return result;
}
/** @brief Return a local tensor extracted from the global data array
corresponding to the given dynamic indices @a is. */
/** @note Return a const tensor to prevent attempts to assign to the
temporary object which is considered a mistake. */
template <typename... index_types>
const tensor_type get_tensor(index_types... is) const
{
static_assert(sizeof...(is) == rank(), "invalid number of indices!");
return get_tensor(std::array<std::size_t,rank()> {std::size_t(is)...});
}
/** @brief Returns one of the following depending on the type scalar_t:
- get_tensor(std::array<std::size_t,rank()>) iff scalar_t is const,
- get_accessor(std::array<std::size_t,rank()>) iff scalar_t is not
const. */
decltype(auto) operator()(std::array<std::size_t,rank()> is) const
{
if constexpr (std::is_const_v<scalar_t>) { return get_tensor(is); }
else { return get_accessor(is); }
}
/** @brief Returns one of the following depending on the type scalar_t:
- get_tensor(index_types...) iff scalar_t is const,
- get_accessor(index_types...) iff scalar_t is not const. */
template <typename... index_types>
decltype(auto) operator()(index_types... is) const
{
if constexpr (std::is_const_v<scalar_t>) { return get_tensor(is...); }
else { return get_accessor(is...); }
}
/** @brief Helper class facilitating the reading/writing of local tensor
objects to the global data array of the tensor_ndarray. */
class tensor_accessor
{
private:
const tensor_ndarray &base_array;
scalar_t *offset_data; /// Not owned
public:
/** @brief Construct a tensor_accessor to @a base for the given dynamic
multi-index @a is.
During its life time, this object assumes that the @a base object
remains unmodified. */
tensor_accessor(const tensor_ndarray &base,
const std::array<std::size_t,rank()> &is)
: base_array(base)
{
offset_data = base_array.data + base_array.get_dynamic_offset(is);
}
/// Read-write access to a particular entry of the referenced tensor.
/** The returned reference points to the corresponding entry in the global
data array of the base tensor_ndarray. */
scalar_t &operator()(const std::array<std::size_t,tensor_rank()> &js)
{
return offset_data[base_array.get_static_offset(js)];
}
/** @brief Write a tensor to the referenced tensor in the global data
array of the base tensor_ndarray. */
tensor_accessor &operator=(const tensor_type &rhs)
{
for_multiindex<tensor_sizes...>(
[&](const std::array<std::size_t,tensor_rank()> &js)
{
operator()(js) = ::mfem::future::apply(rhs, js);
});
return *this;
}
};
/** @brief Get a tensor_accessor object referencing the tensor stored at the
dynamic multi-index @a is. This object can be used to write tensor
objects into the global data array of the tensor_ndarray. */
tensor_accessor get_accessor(std::array<std::size_t,rank()> is) const
{
return tensor_accessor(*this, is);
}
/** @brief Get a tensor_accessor object referencing the tensor stored at the
dynamic indices @a is. This object can be used to write tensor objects
into the global data array of the tensor_ndarray. */
template <typename... index_types>
tensor_accessor get_accessor(index_types... is) const
{
static_assert(sizeof...(is) == rank(), "invalid number of indices!");
return get_accessor(std::array<std::size_t,rank()> {std::size_t(is)...});
}
};
/** @brief Construct a tensor_ndarray where only the tensor sizes have to be
explicitly given as template parameters, the rest can be deduced from the
function call arguments. */
template <int... tensor_sizes, typename scalar_t, typename... dyn_sizes_t>
decltype(auto) make_tensor_ndarray(scalar_t *ptr, dyn_sizes_t... dynamic_sizes)
{
return tensor_ndarray<scalar_t,sizeof...(dynamic_sizes),tensor_sizes...>(
ptr, {std::size_t(dynamic_sizes)...});
}
/// Alias for make_tensor_ndarray = make_tensor_array.
template <int... tensor_sizes, typename scalar_t, typename... dyn_sizes_t>
decltype(auto) make_tensor_array(scalar_t *ptr, dyn_sizes_t... dynamic_sizes)
{
return tensor_ndarray<scalar_t,sizeof...(dynamic_sizes),tensor_sizes...>(
ptr, {std::size_t(dynamic_sizes)...});
}
/// Short name for one-dimensional tensor_ndarray: tensor_array.
template <typename scalar_t, int... tensor_sizes>
using tensor_array = tensor_ndarray<scalar_t, 1, tensor_sizes...>;
} // namespace mfem::future
} // namespace mfem
#endif // MFEM_TENSOR_ARRAYS_HPP
+1 -1
View File
@@ -807,7 +807,7 @@ FORMAT_EXCLUDE = general/tinyxml2.cpp tests/unit/catch.hpp
FORMAT_LIST = $(filter-out $(FORMAT_EXCLUDE),$(wildcard $(FORMAT_FILES)))
COUT_CERR_FILES = $(foreach dir,$(DIRS),$(dir)/*.[ch]pp)
COUT_CERR_EXCLUDE = '^general/error\.cpp' '^general/globals\.[ch]pp'
COUT_CERR_EXCLUDE = '^general/error\.cpp' '^general/globals\.[ch]pp' '^general/nvtx\.hpp'
DEPRECATION_WARNING := \
"This feature is planned for removal in the next release."\
@@ -80,6 +80,8 @@ public:
// limitations
void MultRT_2D(const Vector &x, Vector &y, Mode mode) const;
void MultRT_3D(const Vector &x, Vector &y, Mode mode) const;
// suppress warning about hiding overloaded virtual function:
using Operator::Mult;
};
} // namespace mfem
+6 -1
View File
@@ -32,7 +32,11 @@ function(add_benchmark name)
endif(MFEM_USE_CUDA)
add_executable(bench_${name} ${${NAME}_BENCH_SRCS})
target_link_libraries(bench_${name} mfem pthread)
if (fmt_FOUND)
target_link_libraries(bench_${name} mfem pthread fmt::fmt)
else()
target_link_libraries(bench_${name} mfem pthread)
endif()
add_dependencies(${MFEM_ALL_BENCHMARKS_TARGET_NAME} bench_${name})
add_test(NAME bench_${name}_cpu
@@ -51,6 +55,7 @@ endfunction(add_benchmark)
#-------------------------------------------------------------------------------
add_benchmark(assembly_levels)
add_benchmark(ceed)
add_benchmark(dfem)
add_benchmark(dg_amr)
add_benchmark(elasticity)
add_benchmark(tmop)
+845
View File
@@ -0,0 +1,845 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "bench.hpp" // IWYU pragma: keep
#ifdef MFEM_USE_BENCHMARK
#include <cstdlib>
#include <memory>
#include <fem/qinterp/det.cpp>
#include <fem/qinterp/grad.hpp> // IWYU pragma: keep
#include "fem/integ/lininteg_domain_kernels.hpp" // IWYU pragma: keep
#include "fem/dfem/doperator.hpp"
#include <linalg/tensor.hpp>
#include <fem/kernels3d.hpp>
namespace ker = mfem::kernels::internal;
namespace low = mfem::kernels::internal::low;
#include "bench_dfem_mma.hpp"
#undef NVTX_COLOR
#define NVTX_COLOR ::nvtx::kNvidia
using namespace mfem;
using mfem::future::tuple;
using mfem::future::tensor;
using future::DifferentiableOperator;
using future::UniformParameterSpace;
using future::ParameterFunction;
using future::FieldDescriptor;
using future::make_tensor;
using future::Gradient;
using future::Weight;
using future::Identity;
/// info //////////////////////////////////////////////////////////////////////
static void DumpVersionInfo()
{
mfem::out << "\x1b[33m";
mfem::out << "version 0: PA std" << std::endl;
mfem::out << "version 1: PA reg" << std::endl; // can do high order
mfem::out << "version 2: PA low" << std::endl;
mfem::out << "version 3: PA mma" << std::endl;
// mfem::out << "version 4: PA ∂fem new, not specialized" << std::endl;
mfem::out << "version 5: PA ∂fem new, specialized" << std::endl;
// mfem::out << "version 6: PA ∂fem std" << std::endl; // ⚠️ max p=3
// mfem::out << "version 7: MF ∂fem std" << std::endl;
// mfem::out << "version 8: MF ∂fem new" << std::endl; // ⚠️ not supported
mfem::out << "\x1b[m" << std::endl;
}
// Custom benchmark arguments generator ///////////////////////////////////////
static void CustomArguments(bm::Benchmark *b) noexcept
{
constexpr int MAX_NDOFS = 8 * 1024 * (mfem_use_gpu ? 1024 : 8);
const auto versions = { 0, 1, 2, 3, /*4,*/ 5, /*6, 7, 8*/ };
const auto orders = { 6, 5, 4, 3, 2, 1 };
constexpr auto ndofs = [](int n) constexpr noexcept -> int
{
return (n + 1) * (n + 1) * (n + 1);
};
constexpr auto inc = [](int n) constexpr noexcept -> int
{
return n < 160 ? 4 : n < 240 ? 8 : n < 320 ? 16 : 32;
};
for (auto k : versions)
{
for (auto p : orders)
{
for (int n = 16; ndofs(n) <= MAX_NDOFS; n += inc(n))
{
b->Args({k, p, n});
}
}
}
}
/// Basic Kernels Specializations /////////////////////////////////////////////
static void AddBasicKernelSpecializations()
{
using Det = QuadratureInterpolator::DetKernels;
Det::Specialization<3, 3, 2, 2>::Add();
Det::Specialization<3, 3, 2, 3>::Add();
Det::Specialization<3, 3, 2, 5>::Add();
Det::Specialization<3, 3, 2, 6>::Add();
// Others might exceed memory limits
using Grad = QuadratureInterpolator::GradKernels;
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 3>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 4>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 5>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 6>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 7>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 8>::Add();
Grad::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 7>::Add();
Grad::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 8>::Add();
using LIN = DomainLFIntegrator::AssembleKernels;
LIN::Specialization<3, 7, 7>::Add();
LIN::Specialization<3, 6, 6>::Add();
LIN::Specialization<3, 8, 8>::Add();
}
/// Globals ///////////////////////////////////////////////////////////////////
Device *device_ptr = nullptr;
static int gD1D = 0, gQ1D = 0;
/// StiffnessIntegrator ///////////////////////////////////////////////////////
struct StiffnessIntegrator : public BilinearFormIntegrator
{
const FiniteElementSpace *fes;
const real_t *B, *G, *DX;
int ne, d1d, q1d;
Vector J0, dx;
Vector &qdata;
public:
StiffnessIntegrator(Vector &qdata): qdata(qdata)
{
StiffnessKernels::Specialization<2,3>::Add(); // 1
StiffnessKernels::Specialization<3,4>::Add(); // 2
StiffnessKernels::Specialization<4,5>::Add(); // 3
StiffnessKernels::Specialization<5,6>::Add(); // 4
StiffnessKernels::Specialization<6,7>::Add(); // 5
StiffnessKernels::Specialization<7,8>::Add(); // 6
StiffnessKernels::Specialization<9,10>::Add(); // 8
}
void AssemblePA(const FiniteElementSpace &fespace) override
{
NVTX();
fes = &fespace;
auto *mesh = fes->GetMesh();
const int DIM = mesh->Dimension();
ne = mesh->GetNE();
const auto p = fes->GetFE(0)->GetOrder();
const auto q = 2 * p + mesh->GetElementTransformation(0)->OrderW();
const auto type = mesh->GetElementBaseGeometry(0);
const IntegrationRule &ir = IntRules.Get(type, q);
const int NQPT = ir.GetNPoints();
d1d = p + 1;
q1d = IntRules.Get(Geometry::SEGMENT, ir.GetOrder()).GetNPoints();
MFEM_VERIFY(d1d == gD1D, "D1D mismatch: " << d1d << " != " << gD1D);
MFEM_VERIFY(q1d == gQ1D, "Q1D mismatch: " << q1d << " != " << gQ1D);
MFEM_VERIFY(NQPT == q1d * q1d * q1d, "");
const DofToQuad *maps =
&fes->GetFE(0)->GetDofToQuad(ir, DofToQuad::TENSOR);
const GridFunction *nodes = (mesh->EnsureNodes(), mesh->GetNodes());
const FiniteElementSpace *nfes = nodes->FESpace();
const int nVDIM = nfes->GetVDim();
dx.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
J0.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
dx.UseDevice(true), J0.UseDevice(true);
B = maps->B.Read(), G = maps->G.Read(), DX = dx.Read();
const Operator *NR =
nfes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
const QuadratureInterpolator *nqi = nfes->GetQuadratureInterpolator(ir);
nqi->SetOutputLayout(QVectorLayout::byVDIM);
const int nd = nfes->GetFE(0)->GetDof();
Vector xe(nVDIM * nd * ne, Device::GetDeviceMemoryType());
NR->Mult(*nodes, (xe.UseDevice(true), xe));
nqi->Derivatives(xe, J0);
const int Q1D = q1d;
const auto w_r = ir.GetWeights().Read();
const auto W = Reshape(w_r, q1d, q1d, q1d);
const auto J = Reshape(J0.Read(), 3, 3, q1d, q1d, q1d, ne);
auto DX_w = Reshape(dx.Write(), 3, 3, q1d, q1d, q1d, ne);
mfem::forall_3D(ne, Q1D, Q1D, Q1D,[=] MFEM_HOST_DEVICE(int e)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, Q1D)
{
const real_t w = W(qx, qy, qz);
const real_t *Jtr = &J(0, 0, qx, qy, qz, e);
const real_t detJ = kernels::Det<3>(Jtr);
const real_t wd = w * detJ;
const real_t D[9] = { wd, 0.0, 0.0,
0.0, wd, 0.0,
0.0, 0.0, wd
};
real_t Jrt[9], A[9];
kernels::CalcInverse<3>(Jtr, Jrt);
kernels::MultABt(3, 3, 3, D, Jrt, A);
kernels::Mult(3, 3, 3, A, Jrt, &DX_w(0, 0, qx, qy, qz, e));
}
}
}
MFEM_SYNC_THREAD;
});
qdata = dx;
}
//////////////////////////////////////////////////////////////////
template <int T_D1D = 0, int T_Q1D = 0>
static void StiffnessMult(const int NE, const real_t *b, const real_t *g,
const real_t *dx, const real_t *xe, real_t *ye,
const int d1d, const int q1d)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
constexpr int DIM = 3, VDIM = 1;
const auto XE = Reshape(xe, D1D, D1D, D1D, VDIM, NE);
const auto DX = Reshape(dx, 3, 3, Q1D, Q1D, Q1D, NE);
auto YE = Reshape(ye, D1D, D1D, D1D, VDIM, NE);
mfem::forall_2D<T_Q1D*T_Q1D>(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e)
{
constexpr int MD1 = T_D1D > 0 ? kernels::internal::SetMaxOf(T_D1D) : 8;
constexpr int MQ1 = T_Q1D > 0 ? kernels::internal::SetMaxOf(T_Q1D) : 8;
MFEM_SHARED real_t smem[MQ1][MQ1];
MFEM_SHARED real_t sB[MD1][MQ1], sG[MD1][MQ1];
ker::vd_regs3d_t<VDIM, DIM, MQ1> r0, r1;
ker::LoadMatrix(D1D, Q1D, b, sB);
ker::LoadMatrix(D1D, Q1D, g, sG);
ker::LoadDofs3d(e, D1D, XE, r0);
ker::Grad3d(D1D, Q1D, smem, sB, sG, r0, r1);
for (int qz = 0; qz < Q1D; qz++)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, Q1D)
{
real_t v[3], u[3] = { r1[0][0][qz][qy][qx],
r1[0][1][qz][qy][qx],
r1[0][2][qz][qy][qx]
};
const real_t *dx = &DX(0, 0, qx, qy, qz, e);
kernels::Mult(3, 3, dx, u, v);
r0[0][0][qz][qy][qx] = v[0];
r0[0][1][qz][qy][qx] = v[1];
r0[0][2][qz][qy][qx] = v[2];
}
}
}
ker::GradTranspose3d(D1D, Q1D, smem, sB, sG, r0, r1);
ker::WriteDofs3d(e, D1D, r1, YE);
});
}
using StiffnessKernelType = decltype(&StiffnessMult<>);
MFEM_REGISTER_KERNELS(StiffnessKernels, StiffnessKernelType, (int, int));
void AddMultPA(const Vector &x, Vector &y) const override
{
db1("\x1b[32md1d:{} q1d:{}", d1d, q1d);
StiffnessKernels::Run(d1d, q1d,
ne, B, G, DX, x.Read(), y.ReadWrite(),
d1d, q1d);
}
};
template <int D1D, int Q1D>
StiffnessIntegrator::StiffnessKernelType
StiffnessIntegrator::StiffnessKernels::Kernel()
{
db1("D1D:{} Q1D:{}", D1D, Q1D);
return StiffnessMult<D1D, Q1D>;
}
StiffnessIntegrator::StiffnessKernelType
StiffnessIntegrator::StiffnessKernels::Fallback([[maybe_unused]] int d1d,
[[maybe_unused]] int q1d)
{
dbg("\x1b[33mFallback d1d:{} q1d:{}", d1d, q1d);
// MFEM_ABORT("No kernel for d1d=" << d1d << " q1d=" << q1d);
// return nullptr;
return StiffnessMult<>;
}
/// PADiffLowIntegrator ///////////////////////////////////////////////////////
struct PADiffLowIntegrator : public BilinearFormIntegrator
{
const FiniteElementSpace *fes;
const real_t *B, *G, *DX;
int ne, d1d, q1d;
Vector J0, dx;
public: // for nvcc
//////////////////////////////////////////////////////////////////
template <int T_Q1D = 0>
static void PADiffLowMult(const int ne, const int d1d,
const real_t *b, const real_t *g,
const real_t *dx, const real_t *xe,
real_t *ye,
const int q1d)
{
constexpr int DIM = 3, VDIM = 1;
const auto XE = Reshape(xe, d1d, d1d, d1d, VDIM, ne);
auto YE = Reshape(ye, d1d, d1d, d1d, VDIM, ne);
mfem::forall_3D<T_Q1D*T_Q1D*T_Q1D>(ne, q1d, q1d, q1d,
[=] MFEM_HOST_DEVICE(int e)
{
constexpr int MQ1 = T_Q1D;
MFEM_SHARED real_t sm0[MQ1][MQ1][MQ1][3];
MFEM_SHARED real_t sm1[MQ1][MQ1][MQ1][3];
MFEM_SHARED real_t sB[MQ1][MQ1];
MFEM_SHARED real_t sG[MQ1][MQ1];
low::regs3d_t<DIM, MQ1> reg;
low::LoadMatrix(d1d, q1d, b, sB);
low::LoadMatrix(d1d, q1d, g, sG);
low::LoadDofs3d(e, d1d, XE, sm0); // Load & sync
// Grad: sm0 -X-> sm1 -Y-> sm0 -Z-> reg
low::Grad3d(d1d, q1d, sB, sG, sm0, sm1, reg); // Grad 3D
// Q-function
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
// pull
real_t v[3], u[3] = { reg[qz][qy][qx][0],
reg[qz][qy][qx][1],
reg[qz][qy][qx][2]
};
// Q-function
kernels::Mult(3, 3, dx + 9*(qx*q1d*q1d + qy*q1d + qz), u, v);
// push
reg[qz][qy][qx][0] = v[0];
reg[qz][qy][qx][1] = v[1];
reg[qz][qy][qx][2] = v[2];
}
}
}
MFEM_SYNC_THREAD;
// Grad^T: reg -=-> sm1 -X^T-> sm0 -Y^T-> sm1 -Z^T-> reg -> YE
low::GradTranspose3d(d1d, q1d, sB, sG, reg, sm1, sm0); // Grad^T 3D
low::WriteDofs3d(d1d, 0, e, reg, YE); // Write YE
});
}
using PADiffLowKernelType = decltype(&PADiffLowMult<>);
MFEM_REGISTER_KERNELS(PADiffLowKernels, PADiffLowKernelType, (int));
public:
PADiffLowIntegrator()
{
PADiffLowKernels::Specialization<3>::Add(); // 1
PADiffLowKernels::Specialization<4>::Add(); // 2
PADiffLowKernels::Specialization<5>::Add(); // 3
PADiffLowKernels::Specialization<6>::Add(); // 4
PADiffLowKernels::Specialization<7>::Add(); // 5
PADiffLowKernels::Specialization<8>::Add(); // 6
}
void AssemblePA(const FiniteElementSpace &fespace) override
{
NVTX();
fes = &fespace;
auto *mesh = fes->GetMesh();
const int DIM = mesh->Dimension();
ne = mesh->GetNE();
const auto p = fes->GetFE(0)->GetOrder();
const auto q = 2 * p + mesh->GetElementTransformation(0)->OrderW();
const auto type = mesh->GetElementBaseGeometry(0);
const IntegrationRule &ir = IntRules.Get(type, q);
const int NQPT = ir.GetNPoints();
d1d = p + 1;
q1d = IntRules.Get(Geometry::SEGMENT, ir.GetOrder()).GetNPoints();
MFEM_VERIFY(d1d == gD1D, "D1D mismatch: " << d1d << " != " << gD1D);
MFEM_VERIFY(q1d == gQ1D, "Q1D mismatch: " << q1d << " != " << gQ1D);
MFEM_VERIFY(NQPT == q1d * q1d * q1d, "");
const DofToQuad *maps =
&fes->GetFE(0)->GetDofToQuad(ir, DofToQuad::TENSOR);
const GridFunction *nodes = (mesh->EnsureNodes(), mesh->GetNodes());
const FiniteElementSpace *nfes = nodes->FESpace();
const int nVDIM = nfes->GetVDim();
dx.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
J0.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
dx.UseDevice(true), J0.UseDevice(true);
B = maps->B.Read(), G = maps->G.Read(), DX = dx.Read();
const Operator *NR =
nfes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
const QuadratureInterpolator *nqi = nfes->GetQuadratureInterpolator(ir);
nqi->SetOutputLayout(QVectorLayout::byVDIM);
const int nd = nfes->GetFE(0)->GetDof();
Vector xe(nVDIM * nd * ne, Device::GetDeviceMemoryType());
NR->Mult(*nodes, (xe.UseDevice(true), xe));
nqi->Derivatives(xe, J0);
const int Q1D = q1d;
const auto w_r = ir.GetWeights().Read();
const auto W = Reshape(w_r, q1d, q1d, q1d);
const auto J = Reshape(J0.Read(), 3, 3, q1d, q1d, q1d, ne);
auto DX_w = Reshape(dx.Write(), 3, 3, q1d, q1d, q1d, ne);
mfem::forall_3D(ne, Q1D, Q1D, Q1D,[=] MFEM_HOST_DEVICE(int e)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, Q1D)
{
const real_t w = W(qx, qy, qz);
const real_t *Jtr = &J(0, 0, qx, qy, qz, e);
const real_t detJ = kernels::Det<3>(Jtr);
const real_t wd = w * detJ;
const real_t D[9] = { wd, 0.0, 0.0,
0.0, wd, 0.0,
0.0, 0.0, wd
};
real_t Jrt[9], A[9];
kernels::CalcInverse<3>(Jtr, Jrt);
kernels::MultABt(3, 3, 3, D, Jrt, A);
kernels::Mult(3, 3, 3, A, Jrt, &DX_w(0, 0, qz, qy, qx, e));
}
}
}
MFEM_SYNC_THREAD;
});
}
void AddMultPA(const Vector &x, Vector &y) const override
{
db1("\x1b[32md1d:{} q1d:{}", d1d, q1d);
PADiffLowKernels::Run(q1d,
ne, d1d, B, G, DX, x.Read(), y.ReadWrite(),
q1d);
}
};
template <int Q1D>
PADiffLowIntegrator::PADiffLowKernelType
PADiffLowIntegrator::PADiffLowKernels::Kernel()
{
db1("Q1D:{}", Q1D);
return PADiffLowMult<Q1D>;
}
PADiffLowIntegrator::PADiffLowKernelType
PADiffLowIntegrator::PADiffLowKernels::Fallback(int q1d)
{
dbg("\x1b[33mFallback d1d:{} q1d:{}", q1d);
MFEM_ABORT("No kernel for q1d=" << q1d);
return nullptr;
// return StiffnessMult<>;
}
/// BakeOff ///////////////////////////////////////////////////////////////////
template <int VDIM, bool GLL>
struct BakeOff
{
static constexpr int DIM = 3;
const int p, c, q, n, nx, ny, nz;
const bool check_x, check_y, check_z, checked;
Mesh smesh;
ParMesh pmesh;
H1_FECollection fec;
ParFiniteElementSpace pfes;
const Geometry::Type geom_type;
IntegrationRules irs;
const IntegrationRule *ir;
ConstantCoefficient one;
Vector uvec;
VectorConstantCoefficient unit_vec;
const int dofs;
ParGridFunction *nodes;
ParFiniteElementSpace& mfes;
ParGridFunction x, y;
ParBilinearForm a;
std::unique_ptr<DifferentiableOperator> dop;
const int elem_size, total_size, d1d, q1d;
UniformParameterSpace qd_ps;
ParameterFunction qdata;
double mdofs{};
BakeOff(int p, int side):
p(p), c(side), q(2 * p + (GLL ? -1 : 3)), n((assert(c >= p), c / p)),
nx(n + (p * (n + 1) * p * n * p * n < c * c * c ? 1 : 0)),
ny(n + (p * (n + 1) * p * (n + 1) * p * n < c * c * c ? 1 : 0)), nz(n),
check_x(p * nx * p * ny * p * nz <= c * c * c),
check_y(p * (nx + 1) * p * (ny + 1) * p * nz > c * c * c),
check_z(p * (nx + 1) * p * (ny + 1) * p * (nz + 1) > c * c * c),
checked((assert(check_x &&check_y &&check_z), true)),
smesh(Mesh::MakeCartesian3D(nx, ny, nz, Element::HEXAHEDRON)),
pmesh(MPI_COMM_WORLD, (smesh.EnsureNodes(), smesh)),
fec(p, DIM, BasisType::GaussLobatto),
pfes(&pmesh, &fec, VDIM),//, Ordering::byNODES),
geom_type(pmesh.GetTypicalElementGeometry()),
irs(0, GLL ? Quadrature1D::GaussLobatto : Quadrature1D::GaussLegendre),
ir(&irs.Get(geom_type, q)), one(1.0), uvec(DIM),
unit_vec((uvec = 1.0, uvec /= uvec.Norml2(), uvec)),
dofs(pfes.GetTrueVSize()),
nodes(static_cast<ParGridFunction*>(pmesh.GetNodes())),
mfes(*nodes->ParFESpace()),
x(&pfes),
y(&pfes),
a(&pfes),
elem_size(DIM * DIM * ir->GetNPoints()),
total_size(elem_size * pmesh.GetNE()),
d1d(p + 1),
q1d(IntRules.Get(Geometry::SEGMENT, ir->GetOrder()).GetNPoints()),
qd_ps(pmesh, *ir, DIM*DIM),
qdata(qd_ps)
{
NVTX_MARK_FUNCTION;
dbg("p:{} q:{}", p, q);
smesh.Clear();
x = 0.0;
gD1D = d1d, gQ1D = q1d;
dbg("D1D: {}, Q1D: {}", gD1D, gQ1D);
qdata.UseDevice(true);
qdata = 0.0;
MFEM_VERIFY(q1d*q1d*q1d == ir->GetNPoints(), "");
}
virtual void Benchmark() { MFEM_ABORT("Not implemented."); }
[[nodiscard]] double SumMdofs() const noexcept { return mdofs; }
[[nodiscard]] double MDofs() const noexcept { return 1e-6 * dofs; }
};
/// Q-Functions ///////////////////////////////////////////////////////////////
template<int DIM>
struct MFApply
{
MFEM_HOST_DEVICE inline
auto operator()(const tensor<real_t, DIM>& Gu,
const tensor<real_t, DIM, DIM>& J,
const real_t& w) const
{
auto invJ = inv(J);
return tuple{((Gu * invJ)) * transpose(invJ) * det(J) * w};
}
};
template<int DIM>
struct PASetup
{
MFEM_HOST_DEVICE inline
auto operator()([[maybe_unused]] const real_t &u,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)const
{
return tuple{inv(J) * transpose(inv(J)) * det(J) * w};
}
};
template<int DIM>
struct PAApply
{
MFEM_HOST_DEVICE inline
auto operator()(const tensor<real_t, DIM> &Gu,
const tensor<real_t, DIM, DIM> &q) const
{
return tuple{q * Gu};
};
};
/// Diffusion /////////////////////////////////////////////////////////////////
template <int VDIM = 1, bool GLL = false>
struct Diffusion : public BakeOff<VDIM, GLL>
{
static constexpr int DIM = 3;
static constexpr int U = 0, Ξ = 1, Q = 2;
const real_t rtol = 0.0;
const int max_it = 32, print_lvl = -1;
Array<int> ess_tdof_list, ess_bdr, all_domain_attr;
ParLinearForm b;
FieldDescriptor u_fd, Ξ_fd, q_fd;
std::vector<FieldDescriptor> u_sol, q_param, Ξ_q_params;
OperatorPtr A;
Operator *A_ptr;
Vector B, X;
CGSolver cg;
using BakeOff<VDIM, GLL>::a;
using BakeOff<VDIM, GLL>::ir;
using BakeOff<VDIM, GLL>::one;
using BakeOff<VDIM, GLL>::pmesh;
using BakeOff<VDIM, GLL>::pfes;
using BakeOff<VDIM, GLL>::mfes;
using BakeOff<VDIM, GLL>::x;
using BakeOff<VDIM, GLL>::y;
using BakeOff<VDIM, GLL>::mdofs;
using BakeOff<VDIM, GLL>::dop;
using BakeOff<VDIM, GLL>::nodes;
using BakeOff<VDIM, GLL>::qdata;
using BakeOff<VDIM, GLL>::qd_ps;
using BakeOff<VDIM, GLL>::dofs;
Diffusion(int version, int order, int side):
BakeOff<VDIM, GLL>(order, side),
ess_bdr(pmesh.bdr_attributes.Max()),
all_domain_attr(pmesh.bdr_attributes.Max()),
b(&pfes),
u_fd{U, &pfes}, Ξ_fd{Ξ, &mfes}, q_fd{Q, &qd_ps},
u_sol{u_fd},
q_param {q_fd},
Ξ_q_params {Ξ_fd, q_fd},
cg(MPI_COMM_WORLD)
{
// dbg("pmesh.bdr_attributes.Max():{}",pmesh.bdr_attributes.Max());
static_assert(VDIM == 1 && GLL == false);
ess_bdr = 1;
all_domain_attr = 1;
pfes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
b.AddDomainIntegrator(new DomainLFIntegrator(this->one));
b.UseFastAssembly(true);
b.Assemble();
// MF setup ///////////////////////////////////////////////////
const auto dMFOperatorSetup = [&] (bool use_new_kernels,
bool use_kernels_specialization)
{
dbg("MF ∂fem {} kernels", use_new_kernels ? "NEW" : "STD");
std::vector<FieldDescriptor> in_fds = {{U, &pfes}, {Ξ, &mfes}};
std::vector<FieldDescriptor> out_fds = {{U, &pfes}};
dop = std::make_unique<DifferentiableOperator>(in_fds, out_fds, pmesh);
// dop->SetParameters({nodes});
if (use_kernels_specialization) { dop->UseKernelsSpecialization(); }
if (use_new_kernels) { dop->UseNewKernels(); }
// MFApply<DIM> mf_apply;
// dop->AddDomainIntegrator(mf_apply,
// tuple{Gradient<U>{}, Gradient<Ξ>{}, Weight{}}, // local API 🔥
// tuple{Gradient<U>{}},
// *ir, ess_bdr);
dop->FormLinearSystem(ess_tdof_list, x, b, A_ptr, X, B);
A.Reset(A_ptr);
};
// PA setup ///////////////////////////////////////////////////
const auto dPAOperatorSetup = [&] (bool use_new_kernels,
bool use_kernels_specialization)
{
#if 0
dbg("[PA ∂fem] Setup");
auto Iu = Identity<U> {};
auto = Gradient<Ξ> {};
auto W = Weight{};
tuple Iu_GΞ_W = {Iu, , W};
PASetup<DIM> pa_setup_qf;
DifferentiableOperator dSetup(u_sol, Ξ_q_params, pmesh);
if (use_kernels_specialization) { dSetup.UseKernelsSpecialization(); }
if (use_new_kernels) { dSetup.UseNewKernels(); }
dSetup.AddDomainIntegrator(pa_setup_qf, Iu_GΞ_W, tuple{Iq}, *ir, ess_bdr);
dSetup.SetParameters({nodes, &qdata});
X.SetSize(pfes.GetTrueVSize());
pfes.GetRestrictionMatrix()->Mult(x, X);
dSetup.Mult(X, qdata);
#else
dbg("[PA ∂fem] Setup (borrowing PA setup)");
{
ParBilinearForm bf(&pfes);
bf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
bf.AddDomainIntegrator(new StiffnessIntegrator(qdata));
bf.Assemble();
}
#endif
dbg("[PA ∂fem] Apply");
// auto Iq = Identity<Q> {};
// auto Gu = Gradient<U> {};
// tuple Gu_Iq = {Gu, Iq};
// PAApply<DIM> pa_apply_qf;
dop = std::make_unique<DifferentiableOperator>(u_sol, q_param, pmesh);
dop->SetMultLevel(DifferentiableOperator::MultLevel::LVECTOR);
if (use_kernels_specialization) { dop->UseKernelsSpecialization(); }
if (use_new_kernels) { dop->UseNewKernels(); }
else { dbg("[PA ∂fem] NOT using kernels specialization"); }
// dop->AddDomainIntegrator(pa_apply_qf, Gu_Iq, tuple{Gu}, *ir, ess_bdr); // local API 🔥
assert(qdata*qdata > 0.0);
// dop->SetParameters({ &qdata });
dop->FormLinearSystem(ess_tdof_list, x, b, A_ptr, X, B);
A.Reset(A_ptr);
dbg("[PA ∂fem] done");
};
if (version <= 3) // std, reg, low & mma
{
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
if (version == 0) { a.AddDomainIntegrator(new DiffusionIntegrator(ir)); }
if (version == 1) { a.AddDomainIntegrator(new StiffnessIntegrator(qdata)); }
if (version == 2) { a.AddDomainIntegrator(new PADiffLowIntegrator()); }
if (version == 3) { a.AddDomainIntegrator(new PADiffMmaIntegrator()); }
a.Assemble();
a.FormLinearSystem(ess_tdof_list, x, b, A, X, B);
if (version == 0)
{
BilinearFormIntegrator *bfi = a.GetDBFI()->operator[](0);
auto *di = dynamic_cast<DiffusionIntegrator*>(bfi);
assert(di);
const int d1d = di->dofs1D, q1d = di->quad1D;
// dbg("\x1b[33md1d:{} q1d:{}", d1d, q1d);
MFEM_VERIFY(d1d == gD1D, "D1D mismatch: " << d1d << " != " << gD1D);
MFEM_VERIFY(q1d == gQ1D, "Q1D mismatch: " << q1d << " != " << gQ1D);
}
}
else if (version == 4) // PA ∂fem new kernels, not specialized
{
dPAOperatorSetup(true, false);
}
else if (version == 5) // PA ∂fem new kernels, specialized
{
dPAOperatorSetup(true, true);
}
else if (version == 6) // PA ∂fem std
{
dPAOperatorSetup(false, false);
}
else if (version == 7) // MF ∂fem std
{
dMFOperatorSetup(false, false);
}
else if (version == 8) // MF ∂fem new kernels
{
MFEM_ABORT("MF ∂fem new kernels not implemented");
// dMFOperatorSetup(true, true);
}
else { MFEM_ABORT("Invalid version"); }
cg.SetOperator(*A);
cg.iterative_mode = false;
cg.SetAbsTol(0.0);
if (dofs < 128 * 1024) // check
{
cg.SetPrintLevel(3/*-1*/);
cg.SetMaxIter(2000);
cg.SetRelTol(1e-8);
cg.Mult(B, X);
MFEM_VERIFY(cg.GetConverged(), "❌ CG solver did not converge.");
// mfem::out << (cg.GetConverged() ? "✅" : "❌") << std::endl;
// mfem::out << "✅" << std::endl;
}
cg.SetPrintLevel(print_lvl);
cg.SetMaxIter(max_it);
cg.SetRelTol(rtol);
Benchmark();
mdofs = 0.0;
}
void Benchmark() override
{
NVTX_MARK_FUNCTION;
cg.Mult(B, X);
MFEM_DEVICE_SYNC;
mdofs += this->MDofs() * cg.GetNumIterations();
}
};
///////////////////////////////////////////////////////////////////////////////
#define BakeOff_Problem(i, Problem) \
static void BP##i(bm::State &state) \
{ \
const auto version = static_cast<int>(state.range(0)); \
const auto order = static_cast<int>(state.range(1)); \
const auto side = static_cast<int>(state.range(2)); \
Problem ker(version, order, side); \
while (state.KeepRunning()) { ker.Benchmark(); } \
bm::Counter::Flags flags = bm::Counter::kIsRate; \
state.counters["MDof/s"] = bm::Counter(ker.SumMdofs(), flags); \
state.counters["Dofs"] = bm::Counter(ker.dofs); \
state.counters["p"] = bm::Counter(order); \
state.counters["version"] = bm::Counter(version); \
} \
BENCHMARK(BP##i) \
->Apply(CustomArguments) \
->Unit(bm::kMillisecond)
BakeOff_Problem(3, Diffusion);
/// main //////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[])
{
dbg();
DumpVersionInfo();
AddBasicKernelSpecializations();
static mfem::MPI_Session mpi(argc, argv);
bm::ConsoleReporter CR;
bm::Initialize(&argc, argv);
// Device setup, cpu by default
std::string device_context = "cpu",
kernels_context = "std",
kernels_specialization = "yes";
const auto global_context = bmi::GetGlobalContext();
if (global_context != nullptr)
{
const auto device = global_context->find("device");
if (device != global_context->end())
{
mfem::out << device->first << " : "
<< device->second << std::endl;
device_context = device->second;
}
}
dbg("device_config: {}", device_context);
Device device(device_context.c_str());
device_ptr = &device;
device.Print();
if (bm::ReportUnrecognizedArguments(argc, argv)) { return EXIT_FAILURE; }
bm::RunSpecifiedBenchmarks(&CR);
return EXIT_SUCCESS;
}
#endif // MFEM_USE_BENCHMARK
+841
View File
@@ -0,0 +1,841 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "fem/bilininteg.hpp"
#include <fem/quadinterpolator.hpp>
#include "general/forall.hpp"
#include "linalg/dtensor.hpp"
#include "linalg/kernels.hpp"
using namespace mfem;
/// MMA ///////////////////////////////////////////////////////////////////////
namespace mma
{
MFEM_HOST_DEVICE inline int getThreadIdx()
{
#ifdef __CUDA_ARCH__
return threadIdx.x + blockDim.x * (threadIdx.y + blockDim.y * threadIdx.z);
#else
return 0;
#endif
}
MFEM_HOST_DEVICE inline int getWarpId(int thread)
{
return thread / 32;
}
MFEM_HOST_DEVICE inline int getLaneId(int thread)
{
return thread % 32;
}
MFEM_HOST_DEVICE inline int getGroupId(int laneId)
{
return laneId / 4;
}
MFEM_HOST_DEVICE inline int getThreadIdInGroup(int laneId)
{
return laneId % 4;
}
/// Load B1d & G1d matrices into shared memory
template<int MD1, int MQ1>
MFEM_HOST_DEVICE inline void LoadBG(const int D1D, const int Q1D,
const ConstDeviceMatrix &b,
const ConstDeviceMatrix &g,
real_t (&sBG)[2][MQ1*MD1])
{
DeviceMatrix B(sBG[0], D1D, Q1D);
DeviceMatrix G(sBG[1], D1D, Q1D);
int tid = getThreadIdx();
if (tid < D1D * Q1D)
{
int q = tid / D1D;
int d = tid % D1D;
B(d,q) = b(q,d);
G(d,q) = g(q,d);
}
}
/// Load Bt1d & Gt1d matrices into shared memory
template<int MD1, int MQ1>
MFEM_HOST_DEVICE inline void LoadBtGt(const int D1D, const int Q1D,
// const ConstDeviceMatrix &bt,
// const ConstDeviceMatrix &gt,
const ConstDeviceMatrix &b,
const ConstDeviceMatrix &g,
real_t (&sBG)[2][MQ1*MD1])
{
DeviceMatrix Bt(sBG[0], Q1D, D1D);
DeviceMatrix Gt(sBG[1], Q1D, D1D);
int thread = getThreadIdx();
if (thread < D1D * Q1D)
{
int q = thread % Q1D;
int d = thread / Q1D;
// Bt(q,d) = bt(d,q);
// Gt(q,d) = gt(d,q);
Bt(q,d) = b(q,d);
Gt(q,d) = g(q,d);
}
}
/// Load 3D input vector into shared memory
template<int MQ1>
MFEM_HOST_DEVICE inline void LoadX(const int e, const int D1D,
const DeviceTensor<4, const real_t> &x,
real_t (&sm)[3][MQ1*MQ1*MQ1])
{
const int DDD = D1D * D1D * D1D;
DeviceCube X(sm[0], D1D,D1D,D1D);
int tid = getThreadIdx();
if (tid < DDD)
{
int dx = tid % D1D;
int div = tid / D1D;
int dy = div % D1D;
int dz = div / D1D;
X(dx,dy,dz) = x(dx,dy,dz,e);
}
}
// using the m8n8k4 DMMA instriction
constexpr int mmaM = 8;
[[maybe_unused]] constexpr int mmaN = 8;
constexpr int mmaK = 4;
MFEM_HOST_DEVICE inline void dmmaSync([[maybe_unused]] double aReg[1],
[[maybe_unused]] double bReg[1],
[[maybe_unused]] double cReg[2])
{
#ifdef __CUDA_ARCH__
asm volatile("mma.sync.aligned.m8n8k4.row.col.f64.f64.f64.f64 {%0,%1}, {%2}, {%3}, {%0,%1};"
: "+d"(cReg[0]), "+d"(cReg[1]) : "d"(aReg[0]), "d"(bReg[0]));
#endif
}
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void dmma_GradX(const int m, const int n, const int k,
const real_t (&BG)[2][MQ1*MD1],
const real_t (*A)[MDQ*MDQ*MDQ],
real_t (*C)[MDQ*MDQ*MDQ])
{
ConstDeviceMatrix B(BG[0], k, n);
ConstDeviceMatrix G(BG[1], k, n);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
int mPass = (m + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[4] = {};
for (int mK = 0; mK < (k + mmaK - 1) / mmaK; mK++)
{
double bReg[1];
double gReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < n && bRow < k)
{
bReg[0] = B(bRow, bColumn);
gReg[0] = G(bRow, bColumn);
}
else
{
bReg[0] = 0;
gReg[0] = 0;
}
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < m && aColumn < k)
{
ConstDeviceMatrix aA(A[0], k, m);
aReg[0] = aA(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, gReg, &cReg[0]);
dmmaSync(aReg, bReg, &cReg[2]);
}
for (int d = 0; d < 2; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < m && cColumn < n)
{
DeviceMatrix cC(C[d], m, n);
cC(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Gradient, 1/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradX(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sDDD)[MDQ*MDQ*MDQ],
real_t (*sDDQ)[MDQ*MDQ*MDQ])
{
dmma_GradX<MD1, MQ1>(D1D * D1D, Q1D, D1D, sBG, sDDD, sDDQ);
}
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void dmma_GradY(const int m, const int n,
const int k,
const real_t (&BG)[2][MQ1*MD1],
const real_t (*A)[MDQ*MDQ*MDQ],
real_t (*C)[MDQ*MDQ*MDQ])
{
ConstDeviceMatrix B(BG[0], k, n);
ConstDeviceMatrix G(BG[1], k, n);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
int mPass = (m + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[6] = {};
for (int mK = 0; mK < (k + mmaK - 1) / mmaK; mK++)
{
double bReg[1];
double gReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < n && bRow < k)
{
bReg[0] = B(bRow, bColumn);
gReg[0] = G(bRow, bColumn);
}
else
{
bReg[0] = 0;
gReg[0] = 0;
}
double agReg[1];
double abReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < m && aColumn < k)
{
ConstDeviceMatrix gA(A[0], k, m);
ConstDeviceMatrix bA(A[1], k, m);
agReg[0] = gA(aColumn, aRow);
abReg[0] = bA(aColumn, aRow);
}
else
{
agReg[0] = 0;
abReg[0] = 0;
}
dmmaSync(agReg, bReg, &cReg[0]);
dmmaSync(abReg, gReg, &cReg[2]);
dmmaSync(abReg, bReg, &cReg[4]);
}
for (int d = 0; d < 3; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < m && cColumn < n)
{
DeviceMatrix cC(C[d], m, n);
cC(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Gradient, 2/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradY(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sDDQ)[MDQ*MDQ*MDQ],
real_t (*sDQQ)[MDQ*MDQ*MDQ])
{
dmma_GradY<MD1, MQ1>(D1D * Q1D, Q1D, D1D, sBG, sDDQ, sDQQ);
}
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void dmma_GradZ(const int m, const int n,
const int k,
const real_t (&BG)[2][MQ1*MD1],
const real_t (*A)[MDQ*MDQ*MDQ],
real_t (*C)[MDQ*MDQ*MDQ],
int gIdx)
{
ConstDeviceMatrix B(BG[0], k, n);
ConstDeviceMatrix G(BG[1], k, n);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
int mPass = (m + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[6] = {};
for (int mK = 0; mK < (k + mmaK - 1) / mmaK; mK++)
{
double bReg[1];
double gReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < n && bRow < k)
{
bReg[0] = B(bRow, bColumn);
gReg[0] = G(bRow, bColumn);
}
else
{
bReg[0] = 0;
gReg[0] = 0;
}
for (int d = 0; d < 3; d++)
{
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < m && aColumn < k)
{
ConstDeviceMatrix aA(A[d], k, m);
aReg[0] = aA(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, d == gIdx ? gReg : bReg, &cReg[d * 2]);
}
}
for (int d = 0; d < 3; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < m && cColumn < n)
{
DeviceMatrix cC(C[d], m, n);
cC(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Gradient, 3/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradZ(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sDQQ)[MDQ*MDQ*MDQ],
real_t (*sQQQ)[MDQ*MDQ*MDQ])
{
dmma_GradZ<MD1, MQ1>(Q1D * Q1D, Q1D, D1D, sBG, sDQQ, sQQQ, 2);
}
/// 3D Transposed Gradient, 1/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradZt(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sQQQ)[MDQ*MDQ*MDQ],
real_t (*sDQQ)[MDQ*MDQ*MDQ])
{
ConstDeviceMatrix Bt(sBG[0], Q1D, D1D);
ConstDeviceMatrix Gt(sBG[1], Q1D, D1D);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
// qy (Q1D), qz (Q1D) === M, dx (D1D) === N, qx (Q1D) === K
int mPass = (Q1D * Q1D + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps to calculate the 3 directions.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[6] = {};
for (int mK = 0; mK < (Q1D + mmaK - 1) / mmaK; mK++)
{
double BtReg[1];
double GtReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < D1D && bRow < Q1D)
{
BtReg[0] = Bt(bRow, bColumn);
GtReg[0] = Gt(bRow, bColumn);
}
else
{
BtReg[0] = 0;
GtReg[0] = 0;
}
for (int d = 0; d < 3; d++)
{
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < Q1D * Q1D && aColumn < Q1D)
{
ConstDeviceMatrix XxBBG(sQQQ[d], Q1D, Q1D * Q1D);
aReg[0] = XxBBG(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, d == 0 ? GtReg : BtReg, &cReg[d * 2]);
}
}
for (int d = 0; d < 3; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < Q1D * Q1D && cColumn < D1D)
{
DeviceMatrix Xx(sDQQ[d], Q1D * Q1D, D1D); // qy, qz, dx
Xx(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Transposed Gradient, 2/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradYt(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sDQQ)[MDQ*MDQ*MDQ],
real_t (*sDDQ)[MDQ*MDQ*MDQ])
{
ConstDeviceMatrix Bt(sBG[0], Q1D, D1D);
ConstDeviceMatrix Gt(sBG[1], Q1D, D1D);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
// dx (D1D), qz (Q1D) === M, dy (D1D) === N, qy (Q1D) === K
int mPass = (D1D * Q1D + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[6] = {}; // initialized to zero
for (int mK = 0; mK < (Q1D + mmaK - 1) / mmaK; mK++)
{
double BtReg[1];
double GtReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < D1D && bRow < Q1D)
{
BtReg[0] = Bt(bRow, bColumn);
GtReg[0] = Gt(bRow, bColumn);
}
else
{
BtReg[0] = 0;
GtReg[0] = 0;
}
for (int d = 0; d < 3; d++)
{
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < D1D * Q1D && aColumn < Q1D)
{
ConstDeviceMatrix XxBB(sDQQ[d], Q1D, D1D * Q1D); // qy, qz, dx
aReg[0] = XxBB(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, d == 1 ? GtReg : BtReg, &cReg[d * 2]);
}
}
for (int d = 0; d < 3; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < D1D * Q1D && cColumn < D1D)
{
DeviceMatrix Xx(sDDQ[d], D1D * Q1D, D1D); // qz, dx, dy
Xx(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Transposed Gradient, 3/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradXt(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (&sDDQ)[3][MDQ*MDQ*MDQ],
const DeviceTensor<4> &Y, // output
const int e)
{
ConstDeviceMatrix Bt(sBG[0], Q1D, D1D);
ConstDeviceMatrix Gt(sBG[1], Q1D, D1D);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
// dx (D1D), dy (D1D) === M, dz (D1D) === N, qz (Q1D) === K
int mPass = (D1D * D1D + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps to calculate the 3 directions.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
{
double BtReg[1];
double GtReg[1];
double cReg[2] = {}; // initialized to zero
for (int mK = 0; mK < (Q1D + mmaK - 1) / mmaK; mK++)
{
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < D1D && bRow < Q1D)
{
BtReg[0] = Bt(bRow, bColumn);
GtReg[0] = Gt(bRow, bColumn);
}
else
{
BtReg[0] = 0;
GtReg[0] = 0;
}
for (int d = 0; d < 3; d++)
{
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < D1D * D1D && aColumn < Q1D)
{
ConstDeviceMatrix Xx(sDDQ[d], Q1D, D1D * D1D); // qz, dx, dy
aReg[0] = Xx(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, d == 2 ? GtReg : BtReg, cReg);
}
}
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < D1D * D1D && cColumn < D1D)
{
int dx = cRow % D1D;
int dy = cRow / D1D;
int dz = cColumn;
Y(dx,dy,dz,e) += cReg[i];
}
}
}
}
}
} // namespace mma
/// PADiffMmaIntegrator ///////////////////////////////////////////////////////
struct PADiffMmaIntegrator : public BilinearFormIntegrator
{
const FiniteElementSpace *fes;
const real_t *B, *G, *DX;
int ne, d1d, q1d;
Vector J0, dx;
public: // for nvcc
//////////////////////////////////////////////////////////////////
template <int T_D1D = 0, int T_Q1D = 0>
static void PADiffMmaMult(const int ne,
const real_t *b, const real_t *g,
const real_t *dx, const real_t *xe,
real_t *ye,
const int, const int)
{
constexpr int Q1D = T_Q1D, D1D = T_D1D;
const auto B = Reshape(b, Q1D, D1D);
const auto G = Reshape(g, Q1D, D1D);
const auto XE = Reshape(xe, D1D, D1D, D1D, ne);
const auto DX = Reshape(dx, 3, 3, Q1D, Q1D, Q1D, ne);
auto YE = Reshape(ye, D1D, D1D, D1D, ne);
mfem::forall_3D(ne, ((Q1D * Q1D * Q1D + 31) / 32) * 32, 1, 1,
[=] MFEM_HOST_DEVICE(int e)
{
constexpr int MQ1 = T_Q1D, MD1 = T_D1D;
MFEM_SHARED real_t sm0[3][MQ1*MQ1*MQ1];
MFEM_SHARED real_t sm1[3][MQ1*MQ1*MQ1];
MFEM_SHARED real_t BG[2][MD1*MQ1];
mma::LoadBG<MD1, MQ1>(D1D, Q1D, B, G, BG);
mma::LoadX<MQ1>(e, D1D, XE, sm0);
MFEM_SYNC_THREAD;
mma::GradX<MD1, MQ1>(D1D, Q1D, BG, sm0, sm1);
MFEM_SYNC_THREAD;
mma::GradY<MD1, MQ1>(D1D, Q1D, BG, sm1, sm0);
MFEM_SYNC_THREAD;
mma::GradZ<MD1, MQ1>(D1D, Q1D, BG, sm0, sm1);
MFEM_SYNC_THREAD;
int thread = mma::getThreadIdx();
if (thread < Q1D * Q1D * Q1D)
{
int qx = thread % Q1D;
int div = thread / Q1D;
int qy = div % Q1D;
int qz = div / Q1D;
{
// pull
real_t v[3], u[3] = { sm1[0][qz + qy*Q1D + qx*Q1D*Q1D],
sm1[1][qz + qy*Q1D + qx*Q1D*Q1D],
sm1[2][qz + qy*Q1D + qx*Q1D*Q1D]
};
// Q-function
const real_t *dx = &DX(0, 0, qx, qy, qz, e);
kernels::Mult(3, 3, dx, u, v);
// push
sm0[0][qz + qy*Q1D + qx*Q1D*Q1D] = v[0];
sm0[1][qz + qy*Q1D + qx*Q1D*Q1D] = v[1];
sm0[2][qz + qy*Q1D + qx*Q1D*Q1D] = v[2];
}
}
mma::LoadBtGt<MD1,MQ1>(D1D, Q1D, B, G, BG);
MFEM_SYNC_THREAD;
mma::GradZt<MD1, MQ1>(D1D, Q1D, BG, sm0, sm1);
MFEM_SYNC_THREAD;
mma::GradYt<MD1, MQ1>(D1D, Q1D, BG, sm1, sm0);
MFEM_SYNC_THREAD;
mma::GradXt<MD1,MQ1>(D1D, Q1D, BG, sm0, YE, e);
});
}
using PADiffMmaKernelType = decltype(&PADiffMmaMult<>);
MFEM_REGISTER_KERNELS(PADiffMmaKernels, PADiffMmaKernelType, (int, int));
public:
PADiffMmaIntegrator()
{
// PADiffMmaKernels::Specialization<2,3>::Add(); // 1 ❌
PADiffMmaKernels::Specialization<3,4>::Add(); // 2
PADiffMmaKernels::Specialization<4,5>::Add(); // 3
PADiffMmaKernels::Specialization<5,6>::Add(); // 4
PADiffMmaKernels::Specialization<6,7>::Add(); // 5
PADiffMmaKernels::Specialization<7,8>::Add(); // 6
}
void AssemblePA(const FiniteElementSpace &fespace) override
{
NVTX();
fes = &fespace;
auto *mesh = fes->GetMesh();
const int DIM = mesh->Dimension();
ne = mesh->GetNE();
const auto p = fes->GetFE(0)->GetOrder();
const auto q = 2 * p + mesh->GetElementTransformation(0)->OrderW();
const auto type = mesh->GetElementBaseGeometry(0);
const IntegrationRule &ir = IntRules.Get(type, q);
const int NQPT = ir.GetNPoints();
d1d = p + 1;
q1d = IntRules.Get(Geometry::SEGMENT, ir.GetOrder()).GetNPoints();
MFEM_VERIFY(NQPT == q1d * q1d * q1d, "");
const DofToQuad *maps =
&fes->GetFE(0)->GetDofToQuad(ir, DofToQuad::TENSOR);
const GridFunction *nodes = (mesh->EnsureNodes(), mesh->GetNodes());
const FiniteElementSpace *nfes = nodes->FESpace();
const int nVDIM = nfes->GetVDim();
dx.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
J0.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
dx.UseDevice(true), J0.UseDevice(true);
B = maps->B.Read(), G = maps->G.Read(), DX = dx.Read();
const Operator *NR =
nfes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
const QuadratureInterpolator *nqi = nfes->GetQuadratureInterpolator(ir);
nqi->SetOutputLayout(QVectorLayout::byVDIM);
const int nd = nfes->GetFE(0)->GetDof();
Vector xe(nVDIM * nd * ne, Device::GetDeviceMemoryType());
NR->Mult(*nodes, (xe.UseDevice(true), xe));
nqi->Derivatives(xe, J0);
const int Q1D = q1d;
const auto w_r = ir.GetWeights().Read();
const auto W = Reshape(w_r, q1d, q1d, q1d);
const auto J = Reshape(J0.Read(), 3, 3, q1d, q1d, q1d, ne);
auto DX_w = Reshape(dx.Write(), 3, 3, q1d, q1d, q1d, ne);
mfem::forall_3D(ne, Q1D, Q1D, Q1D,[=] MFEM_HOST_DEVICE(int e)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, Q1D)
{
const real_t w = W(qx, qy, qz);
const real_t *Jtr = &J(0, 0, qx, qy, qz, e);
const real_t detJ = kernels::Det<3>(Jtr);
const real_t wd = w * detJ;
const real_t D[9] = { wd, 0.0, 0.0,
0.0, wd, 0.0,
0.0, 0.0, wd
};
real_t Jrt[9], A[9];
kernels::CalcInverse<3>(Jtr, Jrt);
kernels::MultABt(3, 3, 3, D, Jrt, A);
kernels::Mult(3, 3, 3, A, Jrt, &DX_w(0, 0, qz, qy, qx, e));
}
}
}
MFEM_SYNC_THREAD;
});
}
void AddMultPA(const Vector &x, Vector &y) const override
{
db1("\x1b[32md1d:{} q1d:{}", d1d, q1d);
PADiffMmaKernels::Run(d1d, q1d,
ne, B, G, DX, x.Read(), y.ReadWrite(),
d1d, q1d);
}
};
template <int D1D, int Q1D>
PADiffMmaIntegrator::PADiffMmaKernelType
PADiffMmaIntegrator::PADiffMmaKernels::Kernel()
{
db1("D1D:{} Q1D:{}", D1D, Q1D);
return PADiffMmaMult<D1D, Q1D>;
}
PADiffMmaIntegrator::PADiffMmaKernelType
PADiffMmaIntegrator::PADiffMmaKernels::Fallback(int d1d, int q1d)
{
dbg("\x1b[33mFallback d1d:{} q1d:{}", d1d, q1d);
MFEM_ABORT("No kernel for q1d=" << q1d);
return nullptr;
// return PADiffMmaMult;
}
+2 -2
View File
@@ -20,8 +20,8 @@ CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
MFEM_LIB_FILE = mfem_is_not_built
-include $(CONFIG_MK)
SEQ_TESTS = bench_assembly_levels bench_ceed bench_dg_amr bench_elasticity \
bench_tmop bench_vector bench_virtuals
SEQ_TESTS = bench_assembly_levels bench_ceed bench_dfem bench_dg_amr \
bench_elasticity bench_tmop bench_vector bench_virtuals
PAR_TESTS =
ifeq ($(MFEM_USE_MPI),NO)
TESTS = $(SEQ_TESTS)
+9 -7
View File
@@ -25,8 +25,8 @@ add_custom_target(copy_test_data
# Add 'copy_test_data' as a prerequisite for test executables, if the source and the
# build directories are not the same.
if (NOT ("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}"))
add_dependencies(${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME} copy_test_data)
add_dependencies(${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME} copy_data)
add_dependencies(${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME} copy_test_data)
add_dependencies(${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME} copy_data)
endif()
# Include the source directory for the unit tests - catch.hpp is there.
@@ -35,10 +35,12 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
# The following list can be updated using (in bash):
# for d in dfem general linalg mesh fem enzyme; do ls -1 $d/*.cpp; done
set(UNIT_TESTS_SRCS
dfem/test_diffusion.cpp
dfem/test_divergence.cpp
dfem/test_lvector_interface.cpp
dfem/test_mass.cpp
# dfem/test_diffusion.cpp
# dfem/test_diffusion_q1d.cpp
# dfem/test_divergence.cpp
# dfem/test_lvector_interface.cpp
# dfem/test_mass.cpp
dfem/test_multiple_outputs.cpp
general/test_array.cpp
general/test_scan.cpp
general/test_arrays_by_name.cpp
@@ -256,7 +258,7 @@ function(add_serial_miniapp_test name test_uvm)
set_property(SOURCE ${${NAME}_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
endif(MFEM_USE_CUDA)
if (MFEM_USE_HIP)
set_property(SOURCE ${${NAME}_TESTS_SRCS} PROPERTY HIP_SOURCE_PROPERTY_FORMAT TRUE)
set_property(SOURCE ${${NAME}_TESTS_SRCS} PROPERTY HIP_SOURCE_PROPERTY_FORMAT TRUE)
endif(MFEM_USE_HIP)
mfem_add_executable(${name}_tests_cpu ${${NAME}_TESTS_SRCS})
+1 -1
View File
@@ -302,7 +302,7 @@ void diffusion(const char *filename, int p)
TEST_CASE("dFEM Diffusion", "[Parallel][dFEM][GPU]")
{
const bool all_tests = launch_all_non_regression_tests;
// const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
+338
View File
@@ -0,0 +1,338 @@
// 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.
#define NVTX_COLOR nvtx::kGold
#include "unit_tests.hpp"
#include "mfem.hpp"
// #include <type_traits>
#include "fem/dfem/doperator.hpp"
#include "fem/dfem/util.hpp"
#include <fem/integ/bilininteg_diffusion_kernels.hpp>
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
using mfem::future::dual;
using DOperator = future::DifferentiableOperator;
enum class MQ1Settings : int { kRuntime,
kCompileTime,
kDefault
};
namespace dfem_pa_kernels
{
///////////////////////////////////////////////////////////////////////////////
template <typename T, int DIM, int T_MQ1 = 0> struct Diffusion
{
using dvecd_t = tensor<T, DIM>;
using matd_t = tensor<real_t, DIM, DIM>;
struct MFApply
{
static constexpr int MQ1 = T_MQ1;
MFEM_HOST_DEVICE inline auto operator()(const dvecd_t &dudxi,
const real_t &rho,
const matd_t &J,
const real_t &w) const
{
const auto invJ = inv(J), TinJ = transpose(invJ);
return mfem::future::tuple{ (dudxi * invJ) * TinJ * det(J) * w * rho };
}
};
struct PASetup
{
MFEM_HOST_DEVICE inline auto operator()(const real_t u,
const real_t &rho,
const matd_t &J,
const real_t &w) const
{
return mfem::future::tuple{ inv(J) * transpose(inv(J)) * det(J) * w * rho };
}
};
struct PAApply
{
MFEM_HOST_DEVICE inline auto operator()(const dvecd_t &dudxi,
const matd_t &q) const
{
return mfem::future::tuple{ q * dudxi };
};
};
};
///////////////////////////////////////////////////////////////////////////////
template <typename T, int DIM, std::size_t... MQ1s>
struct MFDiffusionFactory
{
static auto All()
{
// could also use a map instead of a tuple
return mfem::future::make_tuple(typename Diffusion<T, DIM, MQ1s>::MFApply{}...);
}
};
template <typename T, int DIM>
using MFDiffusionFactory_1_4 = MFDiffusionFactory<T, DIM, 1, 2, 3, 4>;
template <typename T, int DIM>
class MFDiffusionQFs
{
using MFApplyTuple = decltype(MFDiffusionFactory_1_4<T, DIM>::All());
MFApplyTuple mf_qfs;
public:
MFDiffusionQFs(): mf_qfs(MFDiffusionFactory_1_4<T, DIM>::All()) {}
template <typename F>
void run(int i, F&& f)
{
MFEM_VERIFY(i >= 1, "Index must be >= 1");
const auto I = static_cast<size_t>(i - 1);
runtime_get_impl(I, std::forward<F>(f),
std::make_index_sequence<mfem::future::tuple_size<MFApplyTuple>::value>());
}
private:
template <typename F, size_t... I>
void runtime_get_impl(size_t index, F&& f, std::index_sequence<I...>)
{
using fun_ptr = std::function<void(F&&)>;
fun_ptr table[] = { [&](F&& f) { f(mfem::future::get<I>(mf_qfs)); } ... };
if (index < mfem::future::tuple_size<MFApplyTuple>::value)
{
table[index](std::forward<F>(f));
}
else
{
throw std::out_of_range("Index out of bounds");
}
}
};
///////////////////////////////////////////////////////////////////////////////
template <int DIM>
void DFemDiffusion(const char *filename, int p, const int r,
const MQ1Settings mq1_setting)
{
dbg("DIM:{}", DIM);
CAPTURE(filename, DIM, p, r);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace pfes(&pmesh, &fec);
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const int NE = pfes.GetNE(), d1d(p + 1), q = 2 * p + r;
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), q);
const int q1d(IntRules.Get(Geometry::SEGMENT, ir->GetOrder()).GetNPoints());
MFEM_VERIFY(d1d <= q1d, "q1d should be >= d1d");
ParGridFunction x(&pfes), y(&pfes), z(&pfes);
Vector X(pfes.GetTrueVSize()), Y(pfes.GetTrueVSize()), Z(pfes.GetTrueVSize());
X.Randomize(1);
x.SetFromTrueDofs(X);
auto rho = [](const Vector &xyz)
{
const real_t x = xyz(0), y = xyz(1), z = DIM == 3 ? xyz(2) : 0.0;
real_t r = M_PI * pow(x, 2);
if (DIM >= 2) { r += pow(y, 3); }
if (DIM >= 3) { r += pow(z, 4); }
return r;
};
FunctionCoefficient rho_coeff(rho);
ParBilinearForm blf_fa(&pfes);
blf_fa.AddDomainIntegrator(new DiffusionIntegrator(rho_coeff, ir));
blf_fa.Assemble();
blf_fa.Finalize();
QuadratureSpace qs(pmesh, *ir);
CoefficientVector rho_coeff_cv(rho_coeff, qs);
MFEM_VERIFY(rho_coeff_cv.GetVDim() == 1, "Coefficient should be scalar");
MFEM_VERIFY(rho_coeff_cv.Size() == q1d * q1d * (DIM == 3 ? q1d : 1) * NE, "");
UniformParameterSpace rho_ps(pmesh, *ir, 1);
static constexpr int U = 0, Coords = 1, Rho = 3;
const auto sol = std::vector{ FieldDescriptor{ U, &pfes } };
SECTION("DFEM Matrix free")
{
// fields = {solutions, parameters}
dbg("fields = {{solutions, parameters}} = {{{{U}}, {{Rho, Coords}}}}");
DOperator dop_mf(sol, {{Rho, &rho_ps}, {Coords, mfes}}, pmesh);
dbg("AddDomainIntegrator: {{∇U, Rho, ∇Coords, Weight}} -> {{∇U}}");
if (mq1_setting == MQ1Settings::kRuntime)
{
dbg("MQ1Settings::kRuntime");
MFEM_VERIFY(q1d == (int)floor(std::pow(ir->GetNPoints(), 1.0/DIM) + 0.5),
"q1d and ir->GetNPoints() have to match");
auto add_domain_integrator = [&](auto &qf)
{
dbg("q1d:{} MQ1:{}", q1d, qf.MQ1);
MFEM_VERIFY(q1d == qf.MQ1, "q1d and qf.MQ1 have to match");
dop_mf.AddDomainIntegrator(qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
};
// select the right qf from the factory
MFDiffusionQFs<real_t, DIM> {}.run(q1d, add_domain_integrator);
}
else if (mq1_setting == MQ1Settings::kCompileTime) // hardcoded, MQ1 = 2,3,4,5
{
dbg("MQ1Settings::kCompileTime");
dbg("q1d:{}", q1d);
if (q1d == 2)
{
typename Diffusion<real_t, DIM, 2>::MFApply mf_apply_qf;
MFEM_VERIFY(q1d == 2, "q1d and 2 have to match");
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
else if (q1d == 3)
{
typename Diffusion<real_t, DIM, 3>::MFApply mf_apply_qf;
MFEM_VERIFY(q1d == 3, "q1d and 3 have to match");
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
else if (q1d == 4)
{
typename Diffusion<real_t, DIM, 4>::MFApply mf_apply_qf;
MFEM_VERIFY(q1d == 4, "q1d and 4 have to match");
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
else if (q1d == 5)
{
typename Diffusion<real_t, DIM, 5>::MFApply mf_apply_qf;
MFEM_VERIFY(q1d == 5, "q1d and 5 have to match");
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
else { MFEM_ABORT("Not supported q1d:" << q1d); }
}
else // MQ1Settings::kDefault, MQ1 = 0
{
dbg("MQ1Settings::kDefault");
typename Diffusion<real_t, DIM>::MFApply mf_apply_qf;
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
dop_mf.SetParameters({ &rho_coeff_cv, nodes });
pfes.GetRestrictionMatrix()->Mult(x, X);
dop_mf.Mult(X, Z);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
real_t norm_global = 0.0;
real_t norm_local = Y.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
TEST_CASE("DFEM Diffusion Q1D", "[Parallel][DFEM][MQ1]")
{
// const bool all_tests = launch_all_non_regression_tests;
// const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
// const auto r = !all_tests ? 1 : GENERATE(0, 1, 2, 3);
const int p = 2, r = 1;
dbg("p:{} r:{}", p, r);
const auto mq1_setting = MQ1Settings::kCompileTime;
/*const auto mq1_setting = GENERATE(MQ1Settings::kRuntime,
MQ1Settings::kCompileTime,
MQ1Settings::kDefault);*/
DiffusionIntegrator::AddSpecialization<3,3,3>();
/*SECTION("2D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
const auto filename =
GENERATE("../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh");
DFemDiffusion<2>(filename, p, r);
}*/
// SECTION("3D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
#if 0
const auto filename =
GENERATE("../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh");
#else
const auto filename = "../../data/fichera.mesh";
#endif
dbg("DFemDiffusion");
DFemDiffusion<3>(filename, p, r, mq1_setting);
}
}
} // namespace dfem_pa_kernels
#endif
+1
View File
@@ -11,6 +11,7 @@
#include "../unit_tests.hpp"
#include "mfem.hpp"
#include <fem/dfem/doperator.hpp>
#ifdef MFEM_USE_MPI
+1
View File
@@ -12,6 +12,7 @@
#include "../unit_tests.hpp"
#include "../linalg/test_same_matrices.hpp"
#include "mfem.hpp"
#include <fem/dfem/doperator.hpp>
#ifdef MFEM_USE_MPI
+383
View File
@@ -0,0 +1,383 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#include "../fem/dfem/doperator.hpp"
#include "../fem/dfem/backends/local_qf/prelude.hpp"
#include "linalg/tensor_arrays.hpp"
#include <proteus/JitInterface.h>
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using mfem::future::dual;
using dscalar_t = dual<real_t, real_t>;
#endif
constexpr int DIM = 2;
class DummyParameterSpace : public ParameterSpace
{
public:
class Bimpl : public Operator
{
virtual void Mult(const Vector &x, Vector &y) const
{
for (int i = 0; i < y.Size(); i++)
{
y(i) = x(0);
}
}
};
class Btimpl : public Operator
{
virtual void Mult(const Vector &x, Vector &y) const
{
y(0) = x(0);
}
};
DummyParameterSpace() : ParameterSpace(1) {}
virtual int GetTrueVSize() const override
{
return 1;
}
virtual int GetVSize() const override
{
return 1;
}
virtual const Operator* GetB() const override
{
if (!B)
{
B.reset(new Bimpl());
}
return B.get();
}
virtual const Operator* GetBt() const override
{
if (!Bt)
{
Bt.reset(new Btimpl());
}
return Bt.get();
}
};
struct massqf
{
inline MFEM_HOST_DEVICE
void operator()(
tensor_array<const real_t> &u,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t> &w,
tensor_array<real_t> &out1,
tensor_array<real_t> &out2) const
{
for (size_t q = 0; q < u.size(); q++)
{
const auto v = u(q) * det(J(q)) * w(q);
out1(q) = v;
out2(q) = v;
}
}
};
struct mass_diffusion_qdata_qf
{
inline MFEM_HOST_DEVICE
void operator()(
tensor_array<const real_t> &u,
tensor_array<const real_t, DIM> &dudxi,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t, DIM, DIM> &qdata,
tensor_array<const real_t> &w,
tensor_array<const real_t> &dummy_parameter,
tensor_array<real_t> &out1,
tensor_array<real_t, DIM> &out2,
tensor_array<real_t, DIM, DIM> &out3) const
{
for (size_t q = 0; q < u.size(); q++)
{
const auto invJq = inv(J(q));
const auto detJq = det(J(q));
out1(q) = u(q) * detJq * w(q);
// out2(q) = (dudxi(q) * invJq) * transpose(invJq) * (detJq * w(q));
out3(q) = J(q);
}
jit_bounds(dudxi, J, w, out2, u.size());
}
// XXX: Attribute instrumentation does not work due to ABI differences that
// change the argument number.
//__attribute__((annotate("jit", 5)))
void jit_bounds(
tensor_array<const real_t, DIM> &dudxi,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t> &w,
tensor_array<real_t, DIM> &out,
size_t NQ) const
{
for (size_t q = 0; q < NQ; q++)
{
const auto invJq = inv(J(q));
const auto detJq = det(J(q));
out(q) = (dudxi(q) * invJq) * transpose(invJq) * (detJq * w(q));
}
}
};
TEST_CASE("dFEM Multiple Outputs", "[Parallel][dFEM]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
const char *filename = "../../data/inline-quad.mesh";
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
MFEM_ASSERT(smesh.Dimension() == DIM, "DIM and mesh dimension have to match");
ParMesh pmesh(MPI_COMM_WORLD, smesh);
pmesh.EnsureNodes();
auto* nodes = static_cast<ParGridFunction*>(pmesh.GetNodes());
smesh.Clear();
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
ParGridFunction x(&fes), y(&fes), z(&fes);
ConstantCoefficient one(1.0);
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
// {
// Array<int> inoffsets(3);
// inoffsets[0] = 0;
// inoffsets[1] = fes.GetTrueVSize();
// inoffsets[2] = nodes->ParFESpace()->GetTrueVSize();
// inoffsets.PartialSum();
// BlockVector X(inoffsets);
// X.GetBlock(0).Randomize(1);
// X.GetBlock(1) = *nodes;
// x.SetFromTrueDofs(X.GetBlock(0));
// Array<int> outoffsets(2);
// outoffsets[0] = 0;
// outoffsets[1] = fes.GetTrueVSize();
// outoffsets.PartialSum();
// BlockVector Z(outoffsets);
// ParBilinearForm blf(&fes);
// blf.AddDomainIntegrator(new MassIntegrator(one, ir));
// blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
// blf.Assemble();
// blf.Mult(x, y);
// Vector Y(fes.GetTrueVSize());
// fes.GetProlongationMatrix()->MultTranspose(y, Y);
// static constexpr int U = 0, COORDINATES = 1, V = 2;
// const std::vector<FieldDescriptor> in
// {
// {U, &fes},
// {COORDINATES, nodes->ParFESpace()}
// };
// const std::vector<FieldDescriptor> out // test spaces?
// {
// {V, &fes},
// };
// DifferentiableOperator dop(in, out, pmesh);
// auto derivatives = std::integer_sequence<size_t, U> {};
// auto mass_qfunc = massqf{};
// dop.AddDomainIntegrator(mass_qfunc,
// tuple{ Value<U>{}, Gradient<COORDINATES>{}, Weight{} },
// tuple{ Value<V>{}, Value<V>{} },
// *ir, all_domain_attr, derivatives);
// fes.GetRestrictionMatrix()->Mult(x, X.GetBlock(0));
// dop.Mult(X, Z);
// Vector Y0(Y);
// Y0 *= 2.0;
// Y0 -= Z.GetBlock(0);
// real_t norm_g, norm_l = Y0.Normlinf();
// MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
// REQUIRE(norm_g == MFEM_Approx(0.0));
// MPI_Barrier(MPI_COMM_WORLD);
// auto ddop = dop.GetDerivative(U, X);
// ddop->Mult(X.GetBlock(0), Z);
// Y0 = Y;
// Y0 *= 2.0;
// Y0 -= Z.GetBlock(0);
// norm_l = Y0.Normlinf();
// MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
// REQUIRE(norm_g == MFEM_Approx(0.0));
// MPI_Barrier(MPI_COMM_WORLD);
// }
{
QuadratureSpace qs(pmesh, *ir);
QuadratureFunction qdata(qs, DIM*DIM);
DummyParameterSpace dps;
ParameterFunction dpf(dps);
dpf = 9.12345;
auto coef_func = [](const Vector &coords)
{
return coords[0] * coords[1] * (DIM == 3 ? coords[2] : 1.0);
};
FunctionCoefficient coef(coef_func);
x.ProjectCoefficient(coef);
Vector xtvec, ytvec, ytvecmfem;
x.GetTrueDofs(xtvec);
ytvec.SetSize(xtvec.Size());
ytvecmfem.SetSize(xtvec.Size());
Vector nodestvec;
nodes->GetTrueDofs(nodestvec);
qdata = 123.0;
Vector yqdata(qdata.Size());
MultiVector X{xtvec, nodestvec, qdata, dpf};
MultiVector Z{ytvec, yqdata};
ParBilinearForm blf(&fes);
blf.AddDomainIntegrator(new MassIntegrator(ir));
blf.AddDomainIntegrator(new DiffusionIntegrator(ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
fes.GetProlongationMatrix()->MultTranspose(y, ytvecmfem);
std::cout << "mfem: ";
pretty_print(ytvecmfem);
static constexpr int U = 0, COORDINATES = 1, V = 2, S = 3, L = 4;
const std::vector<FieldDescriptor> in
{
{U, &fes},
{COORDINATES, nodes->ParFESpace()},
{S, &qdata},
{L, &dps}
};
const std::vector<FieldDescriptor> out
{
{V, &fes},
{S, &qdata}
};
{
DifferentiableOperator dop(in, out, pmesh);
dop.SetQLayouts({{Value<U>{}, {1, 0}}}, {});
auto derivatives = std::integer_sequence<size_t, U> {};
auto mass_diffusion_qfunc = mass_diffusion_qdata_qf{};
dop.AddDomainIntegrator(mass_diffusion_qfunc,
tuple{Value<U>{}, Gradient<U>{}, Gradient<COORDINATES>{}, Identity<S>{}, Weight{}, Value<L>{}},
tuple{Value<V>{}, Gradient<V>{}, Identity<S>{}},
*ir, all_domain_attr, derivatives);
fes.GetRestrictionMatrix()->Mult(x, xtvec);
dop.Mult(X, Z);
std::cout << "dfem: ";
pretty_print(Z[0]);
Vector Y0(ytvecmfem);
Y0 -= Z[0];
real_t norm_l = Y0.Normlinf();
real_t norm_g = norm_l;
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
auto ddop = dop.GetDerivative(U, X);
ddop->Mult(X[0], Z);
Y0 = ytvecmfem;
Y0 -= Z[0];
std::cout << "∂dfem: ";
pretty_print(Z[0]);
norm_l = Y0.Normlinf();
norm_g = norm_l;
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
{
DifferentiableOperator dop(in, out, pmesh);
dop.SetQLayouts({{Value<U>{}, {1, 0}}}, {});
auto mass_diffusion_qfunc = mass_diffusion_qdata_qf{};
dop.AddDomainIntegrator<LocalQFBackend>(
mass_diffusion_qfunc,
tuple{Value<U>{}, Gradient<U>{}, Gradient<COORDINATES>{}, Identity<S>{}, Weight{}, Value<L>{}},
tuple{Value<V>{}, Gradient<V>{}, Identity<S>{}},
*ir, all_domain_attr);
fes.GetRestrictionMatrix()->Mult(x, xtvec);
dop.Mult(X, Z);
std::cout << "dfem: ";
pretty_print(Z[0]);
Vector Y0(ytvecmfem);
Y0 -= Z[0];
real_t norm_l = Y0.Normlinf();
real_t norm_g = norm_l;
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
}
#endif // MFEM_USE_MPI
+2 -2
View File
@@ -962,9 +962,9 @@ TEST_CASE("Dispatch Map Specializations")
DiffusionIntegrator{};
REQUIRE_FALSE(
DiffusionIntegrator::ApplyPAKernels::GetDispatchTable().empty());
DiffusionIntegrator::DiffusionApplyPAKernel::GetDispatchTable().empty());
REQUIRE_FALSE(
DiffusionIntegrator::DiagonalPAKernels::GetDispatchTable().empty());
DiffusionIntegrator::DiffusionDiagonalPAKernel::GetDispatchTable().empty());
Mesh mesh = Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL);
H1_FECollection fec(1, mesh.Dimension());