Compare commits

...
630 Commits
Author SHA1 Message Date
molinari2 f7f751b6c4 derivative_args fix for enzyme build. 2026-06-30 17:40:12 -07:00
molinari2 6e8d5b7f90 Add verbose to functional tests, and small refactoring of RevDiff. 2026-06-30 15:22:19 -07:00
molinari2 0d06237944 Working dev of 2nd derivative with dual numbers. 2026-06-30 13:47:23 -07:00
molinari2 2a11d686ec style 2026-06-24 14:29:58 -07:00
molinari2 d56f424498 Added sample SED for simple fiber reinforced materials. 2026-06-24 14:29:28 -07:00
molinari2 a1713be18c Matched constitutive law from Hooke miniapp. 2026-06-24 11:47:43 -07:00
molinari2 b969b36dd2 astyle 2026-06-23 17:46:31 -07:00
molinari2 0faf119c52 Small fix to multiple_outputs unit test. 2026-06-23 17:45:36 -07:00
molinari2 28e6dee81a dfem-hyperelasticity unit test comparing energy (functional) and stress formulation. 2026-06-23 17:45:19 -07:00
molinari2 d7cfdb413b Comparison of energy and stress-based formulations. 2026-06-23 17:44:33 -07:00
molinari2 c9633746f4 Using tensor IdentityMatrix(), 2026-06-23 14:47:46 -07:00
molinari2 6a24ee1b5d dfem Hyperelasticity example using energy-based formulation. 2026-06-23 13:50:16 -07:00
molinari2 1d9a65f6b4 Changes to enable assembly for dfem functional. 2026-06-23 13:49:53 -07:00
Julian Andrej 32fd898fb9 comparison warning 2026-06-22 07:33:26 -07:00
Julian Andrej 08408bff1e reenable tests 2026-06-22 07:33:19 -07:00
Julian Andrej a97d4d2f6f cleanup 2026-06-20 10:06:32 -07:00
camierjs 724e414848 Merge branch 'dfem-dev' of github.com:mfem/mfem into dfem-dev 2026-06-19 13:41:50 -07:00
camierjs 4005340f30 Avoid Enzyme global w/ mfem::forall tests, cleanup global derivative setup 2026-06-19 13:41:49 -07:00
camierjs 795adb0b21 Avoid too much shared memory for derivative_assemble 2026-06-19 13:40:38 -07:00
camierjs 48cb7acd9e Add ∂FEM local QFunction action specializations for bench 2026-06-19 09:50:02 -07:00
camierjs da7d6b1276 Add ∂FEM 'tmop' skeleton tests, fix style 2026-06-19 09:21:02 -07:00
camierjs bdb1042f14 Guard ∂FEM functionals only with Enzyme 2026-06-19 08:40:36 -07:00
camierjs ce307e0081 Merge branch 'dfem-dev' of github.com:mfem/mfem into dfem-dev 2026-06-19 08:34:40 -07:00
camierjs 9973bc7f75 Fix and simplify ∂FEM bench 2026-06-19 08:34:18 -07:00
Julian Andrej d3a9c198fc Merge branch 'dfem-dev' of github.com:mfem/mfem into dfem-dev 2026-06-19 08:10:06 -07:00
Julian Andrej eeff282330 hacky fix 2026-06-19 08:09:55 -07:00
camierjs 3562c476bb Add Eval 2D/3D vector support, add vector mass tests 2026-06-19 08:03:50 -07:00
Julian Andrej 53200e682e more second derivatives 2026-06-18 18:35:59 -07:00
Julian Andrej e57d86b7b9 second derivative prototype 2026-06-18 08:30:56 -07:00
camierjs 6cf6db31a2 Merge branch 'master' into dfem-dev 2026-06-16 17:47:36 -07:00
camierjs 82e9af5750 Remove instantiated fallback kernels 2026-06-16 17:33:02 -07:00
Julian Andrej e03db70e0f Merge branch 'tuple-refactor' into dfem-multiple-outputs 2026-06-15 08:56:28 -07:00
Julian Andrej 8dcec41e68 resolve tensor issues 2026-06-15 08:56:03 -07:00
Julian Andrej e807fc5c99 Merge branch 'tensor-refactor' into dfem-multiple-outputs
# Conflicts:
#	linalg/tensor.hpp
2026-06-15 08:40:06 -07:00
Julian Andrej d0488784a3 Merge pull request #5352 from mfem/dfem-multiple-outputs-kernels
∂FEM multiple outputs kernels
2026-06-15 08:27:28 -07:00
camierjs 655c1483cf Use mfem::out instead of std::cout in unit tests 2026-06-13 12:56:53 -07:00
camierjs b41fcbf961 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-06-13 12:53:11 -07:00
Julian Andrej c8ca8ea5d8 Merge branch 'dfem-multiple-outputs' of github.com:mfem/mfem into dfem-multiple-outputs 2026-06-12 16:12:14 -07:00
Julian Andrej d676d119f0 functional stuff 2026-06-12 16:11:53 -07:00
camierjs cfcd07c29a Re-avoiding 'number of sections exceeded object file format limit' error in test_jvp_vjp 2026-06-12 13:16:51 -07:00
camierjs 71a3684d8a Avoiding 'number of sections exceeded object file format limit' error in test_jvp_vjp 2026-06-12 12:35:07 -07:00
camierjs 607d1f0b06 Re avoiding 'number of sections exceeded object file format limit' error 2026-06-12 10:30:48 -07:00
camierjs a2b52e8942 Avoid too many instantiations in fallbacks 2026-06-12 10:26:14 -07:00
camierjs aa11f6c0e8 Simplify dFEM ultiple inputs tests 2026-06-12 09:33:19 -07:00
camierjs 8313768ca1 Avoid DYNAMIC_SECTION try for MSVC C1128 'number of sections exceeded object file format limit' 2026-06-12 06:41:47 -07:00
camierjs 0a1f2b53d1 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-06-11 19:35:38 -07:00
camierjs a2253b7bca Style and cleanup local actions 2026-06-11 12:51:08 -07:00
camierjs f7ab2c8ac4 MSVC avoid bigobj, cleanup ∂FEM unit test tags 2026-06-11 11:58:36 -07:00
camierjs dd1d941813 Merge branch 'master' into dfem-multiple-outputs 2026-06-11 10:26:28 -07:00
camierjs c0710a7e0c Add a Dispatch Kernel By Q1D for the fallbacks 2026-06-11 10:24:50 -07:00
camierjs 6ab5ec543c Remove debug traces in dFEM benchmarks 2026-06-11 09:58:35 -07:00
camierjs c81d4e8d1f Remove unused headers and SharedMemoryInfo 2026-06-11 09:40:04 -07:00
camierjs a70daf6123 Fix MSVC not allowed character in an identifier and fops undeclared identifier 2026-06-11 09:16:04 -07:00
camierjs 0f6774eba2 Fix FP32 build 2026-06-11 08:17:15 -07:00
camierjs 1cf9cdc6d8 Add tests unit dfem multiple inputs with Values and Gradients 2026-06-10 19:17:42 -07:00
camierjs 052462e98e wip tests unit dfem multiple inputs 2026-06-10 18:44:16 -07:00
camierjs 77f74936ef Merge branch 'master' into dfem-multiple-outputs-kernels 2026-06-10 17:03:44 -07:00
John Camier 71e3120f78 Merge branch 'master' into tensor-refactor 2026-06-09 06:54:27 -07:00
John Camier dbb5fe2f0e Merge branch 'master' into tuple-refactor 2026-06-09 06:54:17 -07:00
camierjs 08acd54f51 Avoid MSVC error C2065: 'outputs': undeclared identifier 2026-06-06 18:08:56 -07:00
camierjs 65a476c7b7 Revert with codecov-action@v5 2026-06-06 17:03:41 -07:00
camierjs 4245965c46 Try with codecov-action@v5 2026-06-06 16:33:57 -07:00
camierjs 9c2e25041f Fix ∂FEM mtop miniapp include 2026-06-06 15:05:46 -07:00
camierjs 431685728e Fix dFEM MPI runs 2026-06-06 14:50:07 -07:00
camierjs 0d9552d9d5 Fix dFEM Operator Mult 2026-06-06 14:07:02 -07:00
camierjs 783677b95d wip dFEM prolongations 2026-06-06 12:57:17 -07:00
camierjs 72e05eadd1 Re-fix win32 function dllimport, address tensors older compiler errors, avoid failing JIT example 2026-06-06 08:27:06 -07:00
camierjs 8afa3a3bfc Fix win32 definition of dllimport function not allowed 2026-06-05 17:41:09 -07:00
Tzanio Kolev 94da954917 Merge branch 'master' into tuple-refactor 2026-06-05 16:01:05 -07:00
camierjs cd887460ae Update cmake lists and style 2026-06-05 11:45:55 -07:00
camierjs 07cd749016 Avoid JIT playground w/o Proteus 2026-06-04 13:09:40 -07:00
camierjs 92c72e929f Fix pedantic unused variables 2026-06-04 11:24:54 -07:00
camierjs 2e59af76eb Fix MPI guards for serial runs 2026-06-04 10:27:24 -07:00
camierjs d18985cfd5 Fix out-of-source build and mtop dFEM solver 2026-06-04 09:45:03 -07:00
camierjs e5ecfdb9fc Fix tensor arrays guards & unused 2026-06-04 09:08:32 -07:00
camierjs 9ed5391919 Rename test_jvp_vjp and variables naming convention 2026-06-04 08:55:50 -07:00
camierjs 617c5fb295 Fix doxygen documentation 2026-06-04 07:33:08 -07:00
camierjs 81c4ce6a0c Cleanup dbg traces and meld back 2026-06-04 06:08:28 -07:00
camierjs a3967f36ce Cleanup tests, bring HO local kernels, add local specialization utils 2026-06-03 17:42:57 -07:00
camierjs d90d870868 Re-introduced 'enzyme_dup' vs. 'enzyme_const' for test_multiple_outputs 2026-06-03 15:41:23 -07:00
camierjs a1f93f45d7 Revert enzyme_const/enzyme_dup w/ shadow work-around 2026-06-03 15:04:08 -07:00
camierjs 4407597c3e Add LO/HO instantiations 2026-06-03 12:03:52 -07:00
Tzanio Kolev 9e18c64f52 Merge branch 'master' into tensor-refactor 2026-06-02 10:08:03 -07:00
camierjs 0e50038e5f Add dFEM GPU multiple outputs tests 2026-06-01 20:33:08 -07:00
camierjs d169026296 Add dFEM GPU functional tests 2026-06-01 20:09:39 -07:00
camierjs 94cb8fb05c Fix dFEM GPU derivative transposed tests 2026-06-01 20:03:58 -07:00
camierjs 68c51adb5c dFEM GPU dual tests 2026-06-01 18:47:46 -07:00
camierjs 3be5ec1dfa Fix dFEM DIFFUSION GPU action tests 2026-06-01 18:17:05 -07:00
camierjs e8e5d0a322 Cleanup 2026-06-01 18:02:50 -07:00
camierjs 67148db340 Merge branch 'dfem-multiple-outputs-kernels' of github.com:mfem/mfem into dfem-multiple-outputs-kernels 2026-06-01 17:54:23 -07:00
camierjs e795976b44 Fix dFEM mixed MASS GPU tests 2026-06-01 17:54:20 -07:00
camierjs ca79d9ac43 Merge branch 'dfem-multiple-outputs-kernels' of github.com:mfem/mfem into dfem-multiple-outputs-kernels 2026-06-01 17:46:51 -07:00
camierjs a989a27f6b wip dFEM mixed MASS GPU tests 2026-06-01 17:40:07 -07:00
camierjs 2b8f4f15bc wip dFEM mass tests: Linearized, Diagonal 2026-06-01 17:12:01 -07:00
camierjs 6c46647b43 wip debug device pass 2026-06-01 16:24:10 -07:00
camierjs 5a4c6ffdb7 wip dFEM MASS GPU tests 2026-06-01 11:59:55 -07:00
camierjs eb8aa62c67 wip GPU mass 2026-06-01 11:10:28 -07:00
John Camier 85a0d18caa Merge branch 'master' into tensor-refactor 2026-06-01 08:08:02 -07:00
camierjs f95970b551 Fix parallel adjoint consistency test 2026-06-01 07:10:20 -07:00
camierjs ca77f8245a Avoid Enzyme forward-diff and mfem::forall 2026-05-31 18:39:53 -07:00
camierjs 366516b757 Simplify local derivative assembly 2026-05-31 17:24:01 -07:00
camierjs 6c05daab28 Cleanup local derivative assembly 2026-05-31 16:55:49 -07:00
camierjs 1d58190e65 Simplify derivative assembly 2026-05-31 13:55:20 -07:00
camierjs ecd95ce33f wip assembly 2026-05-31 12:55:18 -07:00
camierjs f0aebb96d3 Cleanup 2026-05-31 10:31:37 -07:00
camierjs 384f21655b Add Local Specializations for Low Order kernels 2026-05-31 10:16:45 -07:00
camierjs b73939af33 Add last stage extern templates for kernels 2026-05-31 08:24:25 -07:00
camierjs 9da29918d7 Re-enable dFEM mass global tests, rework derivative assembly 2026-05-30 18:24:08 -07:00
camierjs c485588bcb wip device 2026-05-30 17:26:27 -07:00
camierjs 202afb05ae Use Vector for native_dual_t 2026-05-30 17:08:11 -07:00
camierjs 7246c63e76 dFEM device wip 2026-05-30 16:37:00 -07:00
camierjs d2e9a25cde Add more adjoint tests and mass use both global and local backends 2026-05-30 14:45:29 -07:00
camierjs 5dabca7a9f dFEM globals apply with cache, cleanup 2026-05-30 12:43:50 -07:00
camierjs 9e736b2220 wip simplify derivative apply transpose 2026-05-30 08:57:06 -07:00
camierjs 04bca1b63a dFEM Global prelude cleanup 2026-05-29 17:30:13 -07:00
camierjs 584b8bd746 wip derivative assemble, even less shared memory 2026-05-29 17:08:18 -07:00
camierjs 2ffd8fe82a wip derivative assemble, less shared memory 2026-05-29 16:52:39 -07:00
camierjs 7fc1b24e17 wip derivative assemble 2026-05-29 15:55:50 -07:00
camierjs e81e43ed7a Derivative assemble diagonal LO/HO kernels 2026-05-29 13:25:12 -07:00
camierjs 1ce7e4e107 Fix adjoint Enzyme non-linear consistency tests, wip derivative apply transpose 2026-05-29 12:23:09 -07:00
camierjs ec12d3133d wip Derivative Setup/Apply 2026-05-29 08:20:47 -07:00
camierjs 0a4ddccf50 Using Inputs/Outputs/Derivatives for dFEM diffusion tests 2026-05-29 06:53:55 -07:00
camierjs 2eaea9f097 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-29 06:48:34 -07:00
camierjs 0219a408ff Re-enable LocalQFImpl for GlobalQFImpl 2026-05-29 06:47:09 -07:00
camierjs e8889dd69a Cleanup DerivativeAssemble 2026-05-28 12:03:15 -07:00
camierjs 17a628c202 wip DerivativeAssemble 2026-05-28 11:53:46 -07:00
camierjs 5837ac9ab2 Use new driver for derivative assemble diagonal, add mass diagonal tests 2026-05-28 10:14:47 -07:00
Julian Andrej bd85ce1cc6 syntax sugar 2026-05-28 09:21:11 -07:00
camierjs 68b11fcdc1 Cleanup 2026-05-28 09:12:59 -07:00
camierjs 96432772fb Remove now unused 'has_cached_derivative' 2026-05-28 09:04:20 -07:00
camierjs 90bd3527ca Consolidate util functions for local qf backend 2026-05-28 09:02:07 -07:00
camierjs 7e03baa836 Use new driver for derivative apply 2026-05-28 08:50:52 -07:00
camierjs 4679e335b5 Use MFEM_ABORT in derivative_assemble_diagonal 2026-05-28 07:55:43 -07:00
camierjs 937e7568fb Swap input_size_on_qp as array 2026-05-28 07:50:41 -07:00
camierjs f7a20ad557 Cleanup global_qf prelude 2026-05-27 17:39:24 -07:00
camierjs 18acad4ea2 Rename global QF derivative_action 2026-05-27 17:38:05 -07:00
camierjs c716177716 Add new driver in DerivativeSetup, enable mass, multiple outputs, divergence tests.
Enable duals in global Q-functions.
2026-05-27 17:36:07 -07:00
camierjs e920d87133 DerivativeApplyTranspose with new kernels driver 2026-05-27 16:28:34 -07:00
camierjs 5d74f8d098 Added dual transpose/cache path 2026-05-27 15:27:38 -07:00
camierjs df86e132b6 Add dFEM consistency unit test 2026-05-27 13:21:19 -07:00
Tzanio Kolev 6ce18b2005 Merge branch 'master' into tuple-refactor 2026-05-27 09:24:12 -07:00
Julian Andrej c09b6d8a1d make style 2026-05-26 19:49:07 -07:00
Julian AndrejandCopilot Autofix powered by AI 19d9175833 replace tuple implementation with generic sized
Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Add tuple include

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

use move instead of copy

properly do forwards
2026-05-26 17:41:13 -07:00
Julian Andrej 9fc8398f9a guard array storage size from being zero 2026-05-26 17:26:42 -07:00
Julian Andrej 37dc4c9b08 review changes 2026-05-26 17:05:45 -07:00
Tzanio Kolev 16665bbe4e Merge branch 'master' into tensor-refactor 2026-05-26 10:33:20 -07:00
camierjs 1d984ba63c Add 2D LO backend 2026-05-24 20:12:24 -07:00
camierjs 70eb97b315 Default DIM for LocalQFLOBackend 2026-05-24 17:00:43 -07:00
camierjs 6b5a79ca5f Cleanup 2026-05-24 13:03:41 -07:00
camierjs 654533ac95 Add dFEM headers 2026-05-24 12:47:14 -07:00
camierjs 8312fc66fd Fix Enzyme multiple outputs tests, cache per integrator, populate primal storage and sync cache layout 2026-05-24 12:39:03 -07:00
camierjs 6664566f63 Fix and re-use apply_qpdc for mixed tests 2026-05-24 08:42:06 -07:00
camierjs e976d6155c Cleanup 2026-05-24 07:21:12 -07:00
camierjs d60f9d1659 Rename, cleanup, Enzyme derivative for HO backend 2026-05-23 17:26:59 -07:00
camierjs 194a4f1b57 wip Enzyme derivative for LO backend 2026-05-23 16:45:50 -07:00
camierjs 5c12b17451 dFEM Derivative 2D/3D w/ dual numbers 2026-05-23 16:13:56 -07:00
camierjs a8ef8698ef wip derivative 2D 2026-05-23 15:50:09 -07:00
camierjs 549d8f34c5 Action Linearized 3D 2026-05-23 15:39:12 -07:00
camierjs 758e312404 Cleanup dFEM instantiation guards, GPU benchmark runs 2026-05-23 15:07:57 -07:00
camierjs aa655137f9 Fix refactored WriteGradient for the dFEM benchmarks 2026-05-23 14:02:37 -07:00
camierjs b2debca631 Update dFEM multiple_outputs tests 2026-05-23 13:39:05 -07:00
camierjs 32f4223960 Re-enable lvector_interface tests 2026-05-23 13:29:03 -07:00
camierjs 30b964c9e1 Enable, filter dFEM diffusion, mass, divergence, functional 2026-05-23 13:06:12 -07:00
camierjs c8656fb0c4 Simplify qf_local_action_ho 2026-05-23 12:51:10 -07:00
camierjs 2f192f735c wip dFEM 2D vector action 2026-05-23 10:51:34 -07:00
camierjs 3719d88d0b wip dFEM action 2D/3D 2026-05-23 08:29:24 -07:00
camierjs bd326bcf95 wip integrating local_qf action: output gradient rank 1 only 2026-05-22 18:22:41 -07:00
camierjs ad3854dce2 Filter dFEM diffusion test w/o Enzyme 2026-05-22 17:43:49 -07:00
camierjs 841584b3b0 Report qf_global_kernels changes to default backend 2026-05-22 17:21:41 -07:00
camierjs 0f163b42af Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-22 13:57:53 -07:00
camierjs 0f893525ce Merge branch 'master' into dfem-multiple-outputs-kernels 2026-05-22 13:12:14 -07:00
Julian Andrej 356028d825 Merge branch 'dfem-multiple-outputs' of github.com:mfem/mfem into dfem-multiple-outputs 2026-05-21 16:27:22 -07:00
Julian Andrej 5a289baaa3 reverse dtq tensor logic 2026-05-21 16:13:19 -07:00
Veselin Dobrev bdc9c046d1 Merge branch 'master' into dfem-multiple-outputs 2026-05-20 18:49:29 -07:00
Veselin Dobrev a501e773ad Fix include paths in dfem-minimal-surface.cpp 2026-05-20 18:48:13 -07:00
Julian Andrej 5d7e2b5cfe fix remaining tests in parallel 2026-05-20 10:45:20 -07:00
Julian Andrej 195ec594dc fix parallel for functional 2026-05-20 09:41:12 -07:00
Julian Andrej e2de37f8c3 Merge branch 'dfem-multiple-outputs' of github.com:mfem/mfem into dfem-multiple-outputs 2026-05-19 17:25:57 -07:00
Julian Andrej 338830466e fix segfault 2026-05-19 17:25:51 -07:00
Veselin Dobrev 99ea6dfb38 Use the shortcut VectorQuadratureSpace::GetVSize() in some places.
Fix header include paths.

Apply style to new files.

Temporarily disable dfem unit tests with `#if 0`:
* test_lvector_interface.cpp
* test_mass.cpp
2026-05-19 17:04:29 -07:00
Veselin Dobrev f0ea9d1232 Merge branch 'vector-quadrature-space-dev' into dfem-multiple-outputs 2026-05-19 15:15:31 -07:00
Veselin Dobrev 95f2e2c47a In class VectorQuadratureSpace, add shortcut method GetVSize(). 2026-05-18 21:09:04 -07:00
Julian Andrej a5a4623e00 change to VectorQuadratureSpace 2026-05-18 16:26:09 -07:00
Julian Andrej cf59d7e17b Merge branch 'vector-quadrature-space-dev' into dfem-multiple-outputs 2026-05-18 15:57:16 -07:00
Julian Andrej 0c488b5c4b adapted miniapp 2026-05-18 15:26:32 -07:00
Veselin Dobrev 60665cd77b Added class VectorQuadratureSpace that represents a vector (multi-component)
version of the scalar (single-component) class QuadratureSpaceBase.
2026-05-18 15:25:39 -07:00
Julian Andrej 27e3c00e51 fix vdim assemble 2026-05-18 14:18:22 -07:00
Julian Andrej 7aa3cbe267 add currently failing test for vdim sparsematrix 2026-05-18 09:43:09 -07:00
camierjs 7d5e8004fa dFEM LocalQF action linearized for mass and diffusion unit tests 2026-05-15 18:38:32 -07:00
camierjs a60e2f46be wip dFEM LocalQF Kernels derivatives 2026-05-15 16:46:26 -07:00
camierjs 35d26afed9 dFEM LocalQF LO fallback fix & dual support 2026-05-15 13:06:12 -07:00
camierjs 1f067707c5 dFEM 2D/3D GPU: benchmarks & unit tests 2026-05-15 12:42:00 -07:00
Julian Andrej 77bcbab7f3 fix issues in local qf derivative apply and add global derivative setup/apply 2026-05-15 09:37:41 -07:00
Julian Andrej f8c5d5821d bugfix 2026-05-15 09:35:44 -07:00
Julian Andrej 8d934ea6b4 bugfix 2026-05-15 09:35:31 -07:00
camierjs 6455e1c933 dFEM 2D local kernels 2026-05-15 09:04:15 -07:00
camierjs 4893c3ab60 wip dFEM test mass 2026-05-14 11:57:26 -07:00
camierjs 96498d748b Cleanup 2026-05-14 11:16:36 -07:00
camierjs af15063705 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-14 10:53:23 -07:00
camierjs e306968c3e dFEM HO benches 2026-05-14 10:24:52 -07:00
camierjs 53cdad536c Simplify is_identity_fop, all dFEM benches 2026-05-14 09:49:16 -07:00
camierjs 4dd41558a1 Simplify mass_qf, support all mass q-functions 2026-05-14 09:35:30 -07:00
camierjs 8bde57fe8b HO only for MQ1 > 8, bench dFEM for mass 2026-05-14 08:53:33 -07:00
camierjs 776fcaa0af fix value write, cleanup & simplify 2026-05-13 21:06:17 -07:00
camierjs 61a3fb3f63 wip value layout 2026-05-13 19:17:16 -07:00
Julian Andrej 1fa7888706 fixes for transpose functional computation 2026-05-13 10:48:57 -07:00
camierjs 65e92ff2a0 Cleanup 2026-05-13 07:33:28 -07:00
camierjs bf27be2fe5 Cleanup 2026-05-13 06:07:58 -07:00
camierjs 0db1959daa wip value tests 2026-05-12 18:00:46 -07:00
camierjs ef4f3f274b Remove unused code 2026-05-12 11:47:48 -07:00
camierjs 79343b4949 Rename & simplify 2026-05-12 11:25:04 -07:00
camierjs b8ddb61be3 Simplify metadata 2026-05-12 10:52:16 -07:00
camierjs ad488edf55 Simplify metadata 2026-05-12 10:41:11 -07:00
camierjs 1926bb5e17 Cleanup 2026-05-12 10:37:11 -07:00
camierjs c809c833f8 Simplify and add header copyright 2026-05-12 10:10:40 -07:00
camierjs f8d003bda5 Cleanup unused code from using reg tuple 2026-05-12 09:52:21 -07:00
camierjs f931e75579 Fix dfem backends global_qf kernels has_cached_derivative 2026-05-12 07:57:09 -07:00
camierjs 581b16f33c Add dFEM Diffusion punit tests w/ LO/HO 3D backends 2026-05-11 16:50:21 -07:00
camierjs fcb82b850a style 2026-05-11 15:25:49 -07:00
camierjs 6f616e6849 Merge remote-tracking branch 'origin/dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-11 15:22:23 -07:00
Julian Andrej 66e63180fc MultiVector const ref constructor 2026-05-11 13:31:18 -07:00
camierjs 3e3f9e3c88 Cleanup, rename & simplify 2026-05-10 17:17:58 -07:00
camierjs 416eae77c0 Cleanup GPU local QF, add p=8 for HO only 2026-05-10 16:58:52 -07:00
camierjs 5f1cd5e9f4 fix GPU local QF HO w/ backend reg traits 2026-05-10 16:27:16 -07:00
camierjs 6c93a724df wip GPU local QF HO 2026-05-10 15:51:31 -07:00
camierjs a26743936c wip GPU local QF HO 2026-05-10 15:03:42 -07:00
camierjs 2da32d4c10 wip GPU local QF 2026-05-10 13:56:32 -07:00
camierjs 51492fd1e1 rename local LO/HO kernel backend files 2026-05-10 10:50:39 -07:00
camierjs 2dd3b5339e wip local HO kernel backend 2026-05-10 10:45:49 -07:00
camierjs 0af2ec03c7 wip local LO kernel backend 2026-05-10 10:29:08 -07:00
camierjs d9ae476e6f Fix local_qf kernels HO 2026-05-10 09:37:57 -07:00
camierjs b41c0815bb Simplify & cleanup local_qf kernels HO 2026-05-10 09:35:16 -07:00
camierjs 3520fce371 Simplify & cleanup local_qf kernels 2026-05-10 08:33:11 -07:00
camierjs 53c390432d Simplify low order kernels with unique tuple register args 2026-05-10 06:57:17 -07:00
camierjs b1484d6471 cleanup 2026-05-10 05:37:56 -07:00
camierjs 5eaaa05ab7 Fix CUDA runs with regs3d_vd device wrapper and allow to optin for for shared mem 2026-05-09 17:36:18 -07:00
camierjs 6d6771c0ed fix use of specializations 2026-05-09 15:02:22 -07:00
camierjs e519e1946b wip high kernels 2026-05-09 14:53:03 -07:00
camierjs a0087b7152 wip low kernels 2026-05-09 14:50:14 -07:00
camierjs 205595aac2 wip low/high kernels 2026-05-09 14:32:11 -07:00
camierjs 7f5d5923a3 wip low/high kernels, cleanup 2026-05-09 12:52:00 -07:00
camierjs 301bd7a680 wip MFStiffnessIntegrator 2026-05-09 09:08:11 -07:00
camierjs c7f8bea215 wip low/high kernels 2026-05-09 07:14:31 -07:00
camierjs ba7e52d707 wip low/high orders 2026-05-08 20:47:00 -07:00
camierjs bea47b834f wip qf-function metadata 2026-05-08 17:10:35 -07:00
camierjs 46e7317ef1 Split low/high orders 2026-05-08 16:22:27 -07:00
camierjs b2c569925b Cleanup 2026-05-08 15:07:48 -07:00
camierjs 1b457f045e Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-08 14:57:24 -07:00
Julian Andrej 2dfdabcbcf cleanup 2026-05-08 14:39:54 -07:00
camierjs 4d63d4c7b6 meld back toward target 2026-05-08 13:28:22 -07:00
camierjs e59a84509c meld back toward target 2026-05-08 13:11:08 -07:00
camierjs 40dd411d42 Remove specific instruction files 2026-05-08 13:10:50 -07:00
camierjs 9511c087f8 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-08 12:40:59 -07:00
camierjs d80c254f35 Back to using future::tuple 2026-05-08 12:29:51 -07:00
Julian Andrej dc3d947d26 proper diagonal assemble 2026-05-08 10:53:54 -07:00
camierjs ea250a41c4 wip meld back 2026-05-08 10:26:39 -07:00
camierjs 73d2d51a60 Meld back with renaming and w/o mono outputs 2026-05-08 09:37:22 -07:00
camierjs 5a0d1eba4c Rename files 2026-05-08 09:36:33 -07:00
Julian Andrej fe31fbe9e0 diagonal assemble 2026-05-08 08:56:12 -07:00
camierjs d7d5d80eae Fix warnings and remove multi Mult/GetGradient virtuals from operators 2026-05-08 06:52:28 -07:00
camierjs 94701457f2 Propagate merge changes 2026-05-08 05:55:32 -07:00
camierjs e0c12f4008 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-08 05:54:28 -07:00
Julian Andrej 375dfdd51e transpose 2026-05-07 13:10:13 -07:00
Julian Andrej 768f3a567e naming 2026-05-07 07:21:02 -07:00
Julian Andrej fe27a78596 fix derivativeoperator behavior 2026-05-06 16:25:46 -07:00
Julian Andrej ac68a241b2 fix 2026-05-06 16:25:29 -07:00
Julian Andrej 4f57001be5 divergence derivative is buggy 2026-05-06 16:05:50 -07:00
Julian Andrej 0fdb336a3d revive mixed tests 2026-05-06 14:56:04 -07:00
Julian Andrej f7d874b8e2 vector tests 2026-05-06 14:43:46 -07:00
camierjs fd197d55e0 dFEM wip generic GPU runs 2026-05-06 12:52:18 -07:00
Julian Andrej a7e3b3d0c2 reorder lops 2026-05-06 09:57:14 -07:00
Julian Andrej 42b8c9ab4b caching and assemble 2026-05-06 09:43:53 -07:00
camierjs a6664e556d Back to using QuadratureFunction 2026-05-06 07:55:58 -07:00
camierjs 3e764bd06e Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-06 07:55:28 -07:00
Julian Andrej b4788ac05b caching 2026-05-06 07:23:31 -07:00
Julian Andrej ae58dfe3ff make QuadratureFunction work 2026-05-05 13:15:36 -07:00
Julian Andrej 8e88b832d4 derivatives for local qf 2026-05-05 08:19:19 -07:00
camierjs ff18f29993 wip generic qf local devices poly action cleanup 2026-05-04 20:37:01 -07:00
camierjs fd4d104eae wip eval generic qf local devices poly action 2026-05-04 20:18:03 -07:00
camierjs 65e0d8e197 wip generic qf local devices poly action 2026-05-04 17:49:32 -07:00
camierjs a941b0109a wip generic backends local_qf devices poly action 2026-05-04 15:24:22 -07:00
camierjs 767c3aaf39 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-04 12:54:54 -07:00
Julian Andrej fe49b19a50 tests 2026-05-04 10:50:37 -07:00
camierjs 5d954e8349 Cleanup LocalQFDevicesPolyImpl 2026-05-04 08:45:39 -07:00
Julian Andrej a6c2cd1c26 formatting 2026-05-04 08:22:46 -07:00
Julian Andrej 2813bbf566 update test 2026-05-04 08:20:36 -07:00
Julian Andrej d94bebb31f fix multiple outputs for local qf and add test 2026-05-04 08:14:59 -07:00
camierjs 28abd02668 Melding back 2026-05-03 16:16:32 -07:00
camierjs 631a0d2d7b Cleanup 2026-05-03 16:05:58 -07:00
camierjs 954c962306 Cleanup 2026-05-03 15:52:28 -07:00
camierjs bc4a48efd6 dFEM outputs kernels GPU runs 2026-05-03 14:32:39 -07:00
camierjs 554485e349 dFEM PA/MF global/local mono/poly 2026-05-03 10:20:45 -07:00
camierjs 1c7fa2839d Moved dFEM backends utils to util_qf 2026-05-03 10:20:16 -07:00
camierjs a59003ea7f dFEM MF local, devices, poly operator wip 2026-05-02 18:01:21 -07:00
camierjs a7a4ade3f0 dFEM MF local, devices, poly operator setup 2026-05-02 17:04:40 -07:00
camierjs 39ac074486 dFEM devices mono/poly backends init 2026-05-02 16:51:00 -07:00
camierjs 3ec411df2f dFEM move devices mono backends 2026-05-02 16:50:55 -07:00
camierjs 3a677eccc1 dFEM local devices backend simplify 2026-05-02 14:47:56 -07:00
camierjs c67156a1ab dFEM simplify action_callback_new signature 2026-05-02 11:46:24 -07:00
camierjs 548a7fa21b Rename dFEM local device backend action 2026-05-02 08:21:23 -07:00
camierjs 6665423e34 dFEM bench with global/local default MF and devices PA 2026-05-02 06:42:33 -07:00
camierjs 19bfb888d2 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-kernels 2026-05-02 04:42:17 -07:00
camierjs 692befefcc dFEM local globals runs with backends 2026-05-01 20:44:19 -07:00
camierjs 810eba0618 wip dFEM local devices backend 2026-05-01 18:40:02 -07:00
Julian Andrej c99e4d1280 full shmem local action 2026-05-01 18:10:19 -07:00
Julian Andrej a13ea95d27 crude local qf impl 2026-05-01 17:40:50 -07:00
camierjs f9cf2dd860 dFEM global benchmarks runs 2026-05-01 15:49:00 -07:00
Julian Andrej 1ddef79954 Merge branch 'dfem-multiple-outputs' of github.com:mfem/mfem into dfem-multiple-outputs 2026-05-01 14:51:42 -07:00
Julian Andrej ebfd6bfcba stage 2026-05-01 14:51:32 -07:00
camierjs 9ea6a591e1 Add extra subdir level in qf backends 2026-05-01 13:20:43 -07:00
camierjs abb2c75c88 dFEM global benchmarks 2026-05-01 10:57:33 -07:00
camierjs bce6adc1f0 Merge branch 'dfem-multiple-outputs' into dfem-bench-global 2026-05-01 09:36:22 -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 cd761775b6 Merge branch 'master' into camierjs-dfem-bench-global 2026-04-14 08:18:20 -07:00
camierjs 109cc7aa03 Cleanup 2026-04-02 09:06:47 -07:00
Julian Andrej faba224c26 jit playground 2026-04-02 08:34:33 -07:00
camierjs ab0836db42 Cleanup dFEM bench orders 2026-04-02 08:16:28 -07:00
camierjs eb1d0b5031 Use NewMemoryAndSize to avoid D2D copies 2026-04-02 07:20:58 -07:00
camierjs f0573545b3 wip Q blocks 2026-04-01 20:40:06 -07:00
camierjs a0747133ff wip prolongation extra copy 2026-04-01 15:03:49 -07:00
camierjs dfe66e08e4 Adding NVTX traces 2026-04-01 13:40:07 -07:00
camierjs 1f0d68c679 CUDA runs 2026-04-01 13:03:17 -07:00
camierjs 4300e71ae2 Factorize dOperatorSetup 2026-04-01 10:26:13 -07:00
camierjs ab5eea2a20 Fix create_descriptors_to_fields_map usage 2026-04-01 10:09:37 -07:00
camierjs ba3c328fd1 Remove warnings 2026-04-01 09:58:28 -07:00
camierjs 89b576b993 Revert default backend, cleanup tuple usage to std one, add devices backend 2026-04-01 09:42:27 -07:00
camierjs 4cf92a8e51 Cleanup kernel dispatch 2026-04-01 06:36:09 -07:00
camierjs 31b4d1e5bf Merge branch 'master' into camierjs-dfem-bench-global 2026-04-01 06:23:28 -07:00
camierjs 6837cb591c Please clangd AddKernelSpecializations 2026-03-31 12:38:25 -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
camierjs 66708c83aa dFEM profiling 2026-03-27 18:00:03 -07:00
camierjs 513d0669d9 H100 dFEM global runs 2026-03-27 14:25:11 -07:00
camierjs 070b9b530f dFEM global bench on GPU 2026-03-26 11:15:58 -07:00
camierjs 8861b809e6 Remove remaining NVTX_FMT_HPP 2026-03-25 16:53:51 -07:00
camierjs 1771bcca40 Merge branch 'master' into camierjs-dfem-bench-global 2026-03-25 16:45:14 -07:00
camierjs 3ab0d8357f dFEM util FieldBasisFromWeight HostReadWrite 2026-03-25 16:44:59 -07:00
camierjs 463df47610 Runs 2026-03-25 16:25:41 -07:00
camierjs b40d6efe6f Cleanup 2026-03-25 16:17:13 -07:00
camierjs cbff5ea532 Pre cleanup full WrapOpArg1 2026-03-25 16:11:31 -07:00
camierjs 601db29d9f wip WrapOpArg1 2026-03-25 16:01:46 -07:00
camierjs 4479afdf5c dFEM PA CG running 2026-03-25 12:57:01 -07:00
camierjs 62d3e4994e Cleanup 2026-03-25 09:46:24 -07:00
camierjs 76f9014fd6 Remove nvtx link file 2026-03-25 08:09:34 -07:00
camierjs 75cf276e41 Cleanup traces 2026-03-24 22:27:54 -07:00
camierjs 76d9ae4428 dFEM CG multi outputs and blocks 2026-03-24 22:21:03 -07:00
camierjs 2138b21771 Wip CG solver 2026-03-24 18:22:31 -07:00
camierjs 307cdf279a Wip dop in CG with BlockVector 2026-03-24 16:12:32 -07:00
camierjs 4ebd435a2b Merge remote-tracking branch 'origin/dfem-multiple-outputs-laghos' into camierjs-global 2026-03-24 10:54:45 -07:00
camierjs 05c5e98a90 Update debug traces 2026-03-24 10:49:48 -07:00
camierjs ee0d1fa0b7 Merge branch 'master' 2026-03-24 10:33:06 -07:00
camierjs bf3a40f73e Init mdofs before benchmarks 2026-03-24 10:27:28 -07:00
Julian Andrej 41aed0e916 cosmetic changes 2026-03-12 08:42:35 -07:00
camierjs fd27a338e4 Cleanup 2026-03-10 09:56:03 -07:00
camierjs a7ef657395 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-laghos 2026-03-10 09:20:36 -07:00
camierjs f7cf475d59 dbg traces 2026-03-10 09:20:02 -07:00
Julian Andrej 9bf156adf2 bugfix 2026-03-10 09:18:45 -07:00
camierjs 4baf621cca Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-laghos 2026-03-10 08:36:50 -07:00
Julian Andrej 9f0fcd6b10 custom layouts 2026-03-10 08:10:38 -07:00
camierjs 8d31ada017 wip laghos multi vectors 2026-03-09 20:08:10 -07:00
camierjs 33998bdc23 Merge branch 'dfem-multiple-outputs' into dfem-multiple-outputs-laghos 2026-03-09 17:52:32 -07:00
camierjs 69094094fa tensor ndarrays mutable strides to swap inputs 2026-03-09 17:51:24 -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 91c775eb58 refactor tensor for generic size 2026-02-11 16:08:35 -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 856d13e9ff Roctx init 2025-07-04 08:00:50 -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 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
Tzanio Kolev d3f1379dc8 Merge branch 'master' into dfem-phase1-dev 2025-05-03 13:46:43 -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 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
93 changed files with 23660 additions and 6023 deletions
+3
View File
@@ -38,6 +38,9 @@ Discretization improvements
- Added methods to estimate function extremum using piecewise linear bounds +
recursive subdivision.
- Added class VectorQuadratureSpace that represents a vector (multi-component)
version of the scalar (single-component) class QuadratureSpaceBase.
Meshing improvements
--------------------
- Added PA support for TMOP's adaptive limiting functionality. Multiple
+18 -1
View File
@@ -601,6 +601,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)
@@ -737,6 +744,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()
@@ -773,7 +790,7 @@ set_target_properties(mfem PROPERTIES SOVERSION "${mfem_VERSION}")
# If building out-of-source, define MFEM_CONFIG_FILE to point to the config file
# inside the build directory.
if (NOT ("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}"))
target_compile_definitions(mfem PRIVATE
target_compile_definitions(mfem PUBLIC
"MFEM_CONFIG_FILE=\"${PROJECT_BINARY_DIR}/config/_config.hpp\"")
endif()
+2 -2
View File
@@ -18,9 +18,9 @@
#define MFEM_CONFIG_HPP
#ifdef MFEM_CONFIG_FILE
#include MFEM_CONFIG_FILE
#include MFEM_CONFIG_FILE // IWYU pragma: export
#else
#include "_config.hpp"
#include "_config.hpp" // IWYU pragma: export
#endif
#include <cstdint>
+1
View File
@@ -47,6 +47,7 @@ list(APPEND ALL_EXE_SRCS
ex39.cpp
ex40.cpp
ex41.cpp
# jitplayground.cpp
)
if (MFEM_USE_MPI)
+548
View File
@@ -0,0 +1,548 @@
#include <mfem.hpp>
#ifdef MFEM_USE_PROTEUS
#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;
}
#else
int main()
{
std::cerr << "Proteus is required to run this example.\n";
return EXIT_FAILURE;
}
#endif // MFEM_USE_PROTEUS
+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);
}
+42 -16
View File
@@ -65,6 +65,7 @@ set(SRCS
dgmassinv.cpp
doftrans.cpp
dfem/doperator.cpp
dfem/backends/local_qf/kernels.cpp
eltrans.cpp
batchitrans.cpp
estimators.cpp
@@ -121,6 +122,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 +139,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
@@ -211,13 +217,31 @@ set(HDRS
dgmassinv_kernels.hpp
doftrans.hpp
dfem/doperator.hpp
dfem/fielddescriptor.hpp
dfem/fieldoperator.hpp
dfem/integrate.hpp
dfem/integrator_ctx.hpp
dfem/parameterspace.hpp
dfem/qfunction_apply.hpp
dfem/qfunction_transform.hpp
dfem/tuple.hpp
dfem/util.hpp
dfem/backends/util.hpp
dfem/backends/global_qf/action.hpp
dfem/backends/global_qf/derivative_action.hpp
dfem/backends/global_qf/derivative_apply.hpp
dfem/backends/global_qf/derivative_apply_transpose.hpp
dfem/backends/global_qf/derivative_setup.hpp
dfem/backends/global_qf/prelude.hpp
dfem/backends/local_qf/action.hpp
dfem/backends/local_qf/derivative_action.hpp
dfem/backends/local_qf/derivative_apply.hpp
dfem/backends/local_qf/derivative_apply_transpose.hpp
dfem/backends/local_qf/derivative_assemble.hpp
dfem/backends/local_qf/derivative_assemble_diagonal.hpp
dfem/backends/local_qf/derivative_setup.hpp
dfem/backends/local_qf/kernels.hpp
dfem/backends/local_qf/kernels_ho.hpp
dfem/backends/local_qf/kernels_lo.hpp
dfem/backends/local_qf/prelude.hpp
dfem/backends/local_qf/util.hpp
eltrans.hpp
estimators.hpp
fe.hpp
@@ -282,8 +306,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
@@ -317,36 +343,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
-403
View File
@@ -1,403 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "util.hpp"
namespace mfem::future
{
/// @brief Assemble element matrix for three dimensional data.
///
/// Note: In the below layouts, total_trial_op_dim is > 1 if
/// there are more than one inputs dependent on the derivative variable.
///
/// @param A Memory for one element matrix with layout
/// [test_ndof, test_vdim, trial_ndof, trial_vdim].
/// @param fhat Memory to hold the residual computation with layout
/// [test_vdim, test_op_dim, nqp].
/// @param qpdc The quadrature point data cache with data layout
/// [test_vdim, test_op_dim, trial_vdim, total_trial_op_dim, nqp].
/// @param itod Input Trial Operator Dimension array. If the trial
/// operator is not dependent, the dimension is 0 to indicate that.
/// @param inputs The input field operator types.
/// @param output The output field operator types.
/// @param input_dtqmaps The input DofToQuad maps.
/// @param output_dtqmap The output DofToQuad maps.
/// @param scratch_shmem Scratch shared memory for computations.
/// @param q1d The number of quadrature points in one dimension.
/// @param td1d The number of trial dofs in one dimension.
template <typename input_fop_ts, size_t num_inputs, typename output_fop_t>
MFEM_HOST_DEVICE void assemble_element_mat_t3d(
const DeviceTensor<4, real_t>& A,
const DeviceTensor<3, real_t>& fhat,
const DeviceTensor<5, const real_t>& qpdc,
const DeviceTensor<1, const real_t>& itod,
const input_fop_ts& inputs,
const output_fop_t& output,
const std::array<DofToQuadMap, num_inputs>& input_dtqmaps,
const DofToQuadMap& output_dtqmap,
std::array<DeviceTensor<1>, 6>& scratch_shmem,
const int& q1d,
const int& td1d)
{
constexpr int dimension = 3;
// [test_vdim, test_op_dim, trial_vdim, total_trial_op_dim, num_qp]
const int test_vdim = qpdc.GetShape()[0];
const int test_op_dim = qpdc.GetShape()[1];
const int trial_vdim = qpdc.GetShape()[2];
// [num_test_dof, ...]
const auto num_test_dof = A.GetShape()[0];
for (int Jx = 0; Jx < td1d; Jx++)
{
for (int Jy = 0; Jy < td1d; Jy++)
{
for (int Jz = 0; Jz < td1d; Jz++)
{
const int J = Jx + td1d * (Jy + td1d * Jz);
for (int j = 0; j < trial_vdim; j++)
{
for (int tv = 0; tv < test_vdim; tv++)
{
for (int tod = 0; tod < test_op_dim; tod++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
fhat(tv, tod, q) = 0.0;
}
}
}
}
}
// MSVC lambda capture workaround
[[maybe_unused]] const auto& inputs_ref = inputs;
int m_offset = 0;
for_constexpr<num_inputs>([&](auto s)
{
using fop_t = std::decay_t<decltype(get<s>(inputs_ref))>;
const int trial_op_dim = static_cast<int>(itod(static_cast<int>(s)));
if (trial_op_dim == 0)
{
// This is inside a lambda so we have to return
// instead of idiomatic 'continue'.
return;
}
auto& B = input_dtqmaps[s].B;
auto& G = input_dtqmaps[s].G;
if constexpr (is_value_fop<fop_t>::value)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
for (int m = 0; m < trial_op_dim; m++)
{
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
const real_t f = qpdc(i, k, j, m + m_offset, q);
fhat(i, k, q) += f * B(qx, 0, Jx) * B(qy, 0, Jy) * B(qz, 0, Jz);
}
}
}
}
}
}
}
else if constexpr (is_gradient_fop<fop_t>::value)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
for (int m = 0; m < trial_op_dim; m++)
{
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
const real_t f = qpdc(i, k, j, m + m_offset, q);
if (m == 0)
{
fhat(i, k, q) += f * G(qx, 0, Jx) * B(qy, 0, Jy) * B(qz, 0, Jz);
}
else if (m == 1)
{
fhat(i, k, q) += f * B(qx, 0, Jx) * G(qy, 0, Jy) * B(qz, 0, Jz);
}
else if (m == 2)
{
fhat(i, k, q) += f * B(qx, 0, Jx) * B(qy, 0, Jy) * G(qz, 0, Jz);
}
}
}
}
}
}
}
}
else
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ABORT("sum factorized sparse matrix assemble routine "
"not implemented for field operator");
#endif
}
MFEM_SYNC_THREAD;
m_offset += trial_op_dim;
});
auto bvtfhat = Reshape(&A(0, 0, J, j), num_test_dof, test_vdim);
map_quadrature_data_to_fields(bvtfhat, fhat, output, output_dtqmap,
scratch_shmem, dimension, true);
}
}
}
}
}
/// @brief Assemble element matrix for two dimensional data.
///
/// Note: In the below layouts, total_trial_op_dim is > 1 if
/// there are more than one inputs dependent on the derivative variable.
///
/// @param A Memory for one element matrix with layout
/// [test_ndof, test_vdim, trial_ndof, trial_vdim].
/// @param fhat Memory to hold the residual computation with layout
/// [test_vdim, test_op_dim, nqp].
/// @param qpdc The quadrature point data cache with data layout
/// [test_vdim, test_op_dim, trial_vdim, total_trial_op_dim, nqp].
/// @param itod Input Trial Operator Dimension array. If the trial
/// operator is not dependent, the dimension is 0 to indicate that.
/// @param inputs The input field operator types.
/// @param output The output field operator types.
/// @param input_dtqmaps The input DofToQuad maps.
/// @param output_dtqmap The output DofToQuad maps.
/// @param scratch_shmem Scratch shared memory for computations.
/// @param q1d The number of quadrature points in one dimension.
/// @param td1d The number of trial dofs in one dimension.
template <typename input_fop_ts, size_t num_inputs, typename output_fop_t>
MFEM_HOST_DEVICE void assemble_element_mat_t2d(
const DeviceTensor<4, real_t>& A,
const DeviceTensor<3, real_t>& fhat,
const DeviceTensor<5, const real_t>& qpdc,
const DeviceTensor<1, const real_t>& itod,
const input_fop_ts& inputs,
const output_fop_t& output,
const std::array<DofToQuadMap, num_inputs>& input_dtqmaps,
const DofToQuadMap& output_dtqmap,
std::array<DeviceTensor<1>, 6>& scratch_shmem,
const int& q1d,
const int& td1d)
{
constexpr int dimension = 2;
// [test_vdim, test_op_dim, trial_vdim, total_trial_op_dim, num_qp]
const int test_vdim = qpdc.GetShape()[0];
const int test_op_dim = qpdc.GetShape()[1];
const int trial_vdim = qpdc.GetShape()[2];
// [num_test_dof, ...]
const auto num_test_dof = A.GetShape()[0];
for (int Jx = 0; Jx < td1d; Jx++)
{
for (int Jy = 0; Jy < td1d; Jy++)
{
const int J = Jy + Jx * td1d;
for (int j = 0; j < trial_vdim; j++)
{
for (int tv = 0; tv < test_vdim; tv++)
{
for (int tod = 0; tod < test_op_dim; tod++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
const int q = qy + qx * q1d;
fhat(tv, tod, q) = 0.0;
}
}
}
}
// MSVC lambda capture workaround
[[maybe_unused]] const auto& inputs_ref = inputs;
int m_offset = 0;
for_constexpr<num_inputs>([&](auto s)
{
using fop_t = std::decay_t<decltype(get<s>(inputs_ref))>;
const int trial_op_dim = static_cast<int>(itod(static_cast<int>(s)));
if (trial_op_dim == 0)
{
// This is inside a lambda so we have to return
// instead of idiomatic 'continue'.
return;
}
auto& B = input_dtqmaps[s].B;
auto& G = input_dtqmaps[s].G;
if constexpr (is_value_fop<fop_t>::value)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
const int q = qy + qx * q1d;
for (int m = 0; m < trial_op_dim; m++)
{
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
const real_t f = qpdc(i, k, j, m + m_offset, q);
fhat(i, k, q) += f * B(qx, 0, Jx) * B(qy, 0, Jy);
}
}
}
}
}
}
else if constexpr (is_gradient_fop<fop_t>::value)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
const int q = qy + qx * q1d;
for (int m = 0; m < trial_op_dim; m++)
{
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
const real_t f = qpdc(i, k, j, m + m_offset, q);
if (m == 0)
{
fhat(i, k, q) += f * B(qx, 0, Jx) * G(qy, 0, Jy);
}
else
{
fhat(i, k, q) += f * G(qx, 0, Jx) * B(qy, 0, Jy);
}
}
}
}
}
}
}
else
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ABORT("sum factorized sparse matrix assemble routine "
"not implemented for field operator");
#endif
}
MFEM_SYNC_THREAD;
m_offset += trial_op_dim;
});
auto bvtfhat = Reshape(&A(0, 0, J, j), num_test_dof, test_vdim);
map_quadrature_data_to_fields(bvtfhat, fhat, output, output_dtqmap,
scratch_shmem, dimension, true);
}
}
}
}
/// @brief Assemble element matrix for two or three dimensional data.
///
/// Note: In the below layouts, total_trial_op_dim is > 1 if
/// there are more than one inputs dependent on the derivative variable.
///
/// @param A Memory for one element matrix with layout
/// [test_ndof, test_vdim, trial_ndof, trial_vdim].
/// @param fhat Memory to hold the residual computation with layout
/// [test_vdim, test_op_dim, nqp].
/// @param qpdc The quadrature point data cache with data layout
/// [test_vdim, test_op_dim, trial_vdim, total_trial_op_dim, nqp].
/// @param itod Input Trial Operator Dimension array. If the trial
/// operator is not dependent, the dimension is 0 to indicate that.
/// @param inputs The input field operator types.
/// @param output The output field operator types.
/// @param input_dtqmaps The input DofToQuad maps.
/// @param output_dtqmap The output DofToQuad maps.
/// @param scratch_shmem Scratch shared memory for computations.
/// @param dimension The spatial dimension.
/// @param q1d The number of quadrature points in one dimension.
/// @param td1d The number of trial dofs in one dimension.
/// @param use_sum_factorization Indicator if sum factorization is used.
template <typename input_fop_ts, size_t num_inputs, typename output_fop_t>
MFEM_HOST_DEVICE void assemble_element_mat_naive(
const DeviceTensor<4, real_t>& A,
const DeviceTensor<3, real_t>& fhat,
const DeviceTensor<5, const real_t>& qpdc,
const DeviceTensor<1, const real_t>& itod,
const input_fop_ts& inputs,
const output_fop_t& output,
const std::array<DofToQuadMap, num_inputs>& input_dtqmaps,
const DofToQuadMap& output_dtqmap,
std::array<DeviceTensor<1>, 6>& scratch_shmem,
const int& dimension,
const int& q1d,
const int& td1d,
const bool& use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 2)
{
assemble_element_mat_t2d(A, fhat, qpdc, itod, inputs, output,
input_dtqmaps, output_dtqmap, scratch_shmem, q1d, td1d);
}
else if (dimension == 3)
{
assemble_element_mat_t3d(A, fhat, qpdc, itod, inputs, output,
input_dtqmaps, output_dtqmap, scratch_shmem, q1d, td1d);
}
}
else
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ABORT("element matrix assemble not implemented for non tensor "
"product basis");
#endif
}
}
} // namespace mfem::future
+122
View File
@@ -0,0 +1,122 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "../util.hpp"
#include "../../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;
// prepare xq and yq BlockVectors
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();
InitBlockVector(xq, 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();
InitBlockVector(yq, 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,160 @@
// 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 "../../integrator_ctx.hpp"
#include "../util.hpp"
#include <utility>
namespace mfem::future::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 DerivativeAction
{
DerivativeAction(
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();
InitBlockVector(xq, 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();
InitBlockVector(yq, yq_offsets);
// Shadow blocks use the same offsets as xq so tensor_array views
shadow_xq_offsets.SetSize(xq_offsets.Size());
shadow_xq_offsets = xq_offsets;
InitBlockVector(shadow_xq, shadow_xq_offsets);
dof_ordering = ElementDofOrdering::LEXICOGRAPHIC;
for (size_t i = 0; i < ctx.infds.size(); i++)
{
if (static_cast<int>(ctx.infds[i].id) == derivative_id)
{
direction_fd = ctx.infds[i];
break;
}
}
MFEM_ASSERT(direction_fd.id != SIZE_MAX,
"derivative direction field not found in infds");
}
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);
constexpr auto input_active =
detail::make_activity_map<derivative_id>(inputs_t{});
MFEM_ASSERT(de != nullptr, "derivative direction vector is null");
restriction<Entity::Element>(
direction_fd, *de, direction_e, dof_ordering);
shadow_xq = 0.0;
constexpr_for<0, ninputs>([&](auto i)
{
if (!input_active[i]) { return; }
input_bases[i].forward(direction_e, shadow_xq.GetBlock(i));
});
static_assert(detail::supports_tensor_array_qfunc<qfunc_t,
inputs_t,
outputs_t>::value,
"qfunc signature not supported by default backend Action");
// Q -> Q
yq = 0.0;
detail::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;
FieldDescriptor direction_fd;
ElementDofOrdering dof_ordering = ElementDofOrdering::LEXICOGRAPHIC;
mutable Vector direction_e;
};
} // namespace mfem::future::GlobalQFImpl
@@ -0,0 +1,231 @@
// 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 "../../integrator_ctx.hpp"
#include "../util.hpp"
#include <array>
#include <utility>
#include <vector>
namespace mfem::future::GlobalQFImpl
{
// Q-function-shape-agnostic cached forward apply (J·v)
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
struct DerivativeApply
{
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
DerivativeApply(
IntegratorContext ctx,
qfunc_t /*qfunc*/,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache):
ctx(ctx), inputs(std::move(inputs)), outputs(std::move(outputs)),
qp_cache(qp_cache)
{
create_fop_to_fd(this->inputs, ctx.infds, input_to_infd);
create_fop_to_fd(this->outputs, ctx.outfds, output_to_outfd);
check_consistency(this->inputs, input_to_infd, ctx.infds);
check_consistency(this->outputs, output_to_outfd, ctx.outfds);
create_fieldbases(
this->inputs, input_to_infd, ctx.infds, ctx.ir, input_bases);
create_fieldbases(
this->outputs, output_to_outfd, ctx.outfds, ctx.ir, output_bases);
const int nqp = ctx.ir.GetNPoints();
const int ne = ctx.nentities;
gnqp = nqp * ne;
// Precompute Q-space BlockVector layouts
dir_q_offsets.SetSize(n_inputs + 1);
dir_q_offsets[0] = 0;
constexpr_for<0, n_inputs>([&](auto i)
{
dir_q_offsets[i + 1] =
dir_q_offsets[i] + get<i>(this->inputs).size_on_qp * nqp * ne;
});
InitBlockVector(dir_q_local, dir_q_offsets);
result_q_offsets.SetSize(n_outputs + 1);
result_q_offsets[0] = 0;
constexpr_for<0, n_outputs>([&](auto i)
{
result_q_offsets[i + 1] =
result_q_offsets[i] + get<i>(this->outputs).size_on_qp * nqp * ne;
});
InitBlockVector(result_q_local, result_q_offsets);
// Cache layout metadata (must match DerivativeSetup)
residual_size_on_qp = 0;
trial_vdim = 0;
total_trial_op_dim = 0;
constexpr auto activity =
detail::make_activity_map<derivative_id>(inputs_t{});
constexpr_for<0, n_inputs>([&](auto i)
{
if (!activity[i]) { return; }
const auto &fop = get<i>(this->inputs);
trial_vdim = fop.vdim;
total_trial_op_dim += fop.size_on_qp / fop.vdim;
});
constexpr_for<0, n_outputs>([&](auto i)
{ residual_size_on_qp += get<i>(this->outputs).size_on_qp; });
residual_size_on_qp *= trial_vdim * total_trial_op_dim;
}
void operator()(
const std::vector<Vector *> & /*xe*/,
const Vector *direction_l,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
MFEM_ASSERT(direction_l != nullptr,
"Global DerivativeApply: direction vector is null");
// Re-zero pre-allocated Q temporaries
dir_q_local = 0.0;
result_q_local = 0.0;
// Restrict trial direction from the derivative field
size_t in_fd = SIZE_MAX;
constexpr_for<0, n_inputs>([&](auto i)
{
if (get<i>(inputs).GetFieldId() == derivative_id)
{
in_fd = input_to_infd[i.value];
}
});
MFEM_ASSERT(in_fd != SIZE_MAX,
"DerivativeApply: derivative field not found among inputs");
const auto &fd = ctx.infds[in_fd];
Vector dir_e;
restriction<Entity::Element>(
fd, *direction_l, dir_e, ElementDofOrdering::LEXICOGRAPHIC);
// Forward the trial direction into active input Q block
constexpr_for<0, n_inputs>([&](auto s)
{
if (get<s>(inputs).GetFieldId() != derivative_id) { return; }
input_bases[s.value].forward(dir_e, dir_q_local.GetBlock(s.value));
});
dir_q_local.SyncFromBlocks();
const real_t *dir_mono = dir_q_local.HostRead();
real_t *res_mono = result_q_local.HostReadWrite();
const real_t *cache_ptr = qp_cache.HostRead();
const int res_sz = residual_size_on_qp;
constexpr_for<0, n_outputs>([&](auto o)
{
const int tv_o = get<o>(outputs).vdim;
const int to_o = get<o>(outputs).size_on_qp / tv_o;
const int out_base = [&]
{
int off = 0;
constexpr_for<0, o.value>([&](auto prev)
{ off += get<prev>(outputs).size_on_qp; });
return off;
}();
real_t *res_o = res_mono + result_q_offsets[o.value];
int m_offset = 0;
constexpr_for<0, n_inputs>([&](auto s)
{
if (get<s>(inputs).GetFieldId() != derivative_id) { return; }
const int tv = get<s>(inputs).vdim;
const int to = get<s>(inputs).size_on_qp / tv;
const real_t *dir_s = dir_mono + dir_q_offsets[s.value];
for (int gq = 0; gq < gnqp; ++gq)
{
for (int j = 0; j < tv; ++j)
{
for (int m = 0; m < to; ++m)
{
const real_t v = dir_s[(j * to + m) + (tv * to) * gq];
const int m_global = m + m_offset;
for (int i = 0; i < tv_o; ++i)
{
for (int k = 0; k < to_o; ++k)
{
const int out_comp = out_base + i * to_o + k;
const int cache_idx =
out_comp * trial_vdim * total_trial_op_dim +
j * total_trial_op_dim + m_global;
const real_t c = cache_ptr[cache_idx + res_sz * gq];
res_o[(i * to_o + k) + (tv_o * to_o) * gq] += c * v;
}
}
}
}
}
m_offset += to;
});
});
result_q_local.SyncToBlocks();
// Map result Q back to output fields
constexpr_for<0, n_outputs>([&](auto o)
{
const size_t out_fd = output_to_outfd[o.value];
output_bases[o.value].transpose(result_q_local.GetBlock(o.value),
*ye[out_fd]);
});
}
private:
IntegratorContext ctx;
inputs_t inputs;
outputs_t outputs;
const Vector &qp_cache;
std::array<size_t, n_inputs> input_to_infd;
std::array<size_t, n_outputs> output_to_outfd;
std::array<FieldBasis, n_inputs> input_bases;
std::array<FieldBasis, n_outputs> output_bases;
int gnqp = 0;
Array<int> dir_q_offsets;
Array<int> result_q_offsets;
mutable BlockVector dir_q_local;
mutable BlockVector result_q_local;
int residual_size_on_qp = 0;
int trial_vdim = 0;
int total_trial_op_dim = 0;
};
} // namespace mfem::future::GlobalQFImpl
@@ -0,0 +1,244 @@
// 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 "../../integrator_ctx.hpp"
#include "../util.hpp"
#include <array>
#include <utility>
#include <vector>
namespace mfem::future::GlobalQFImpl
{
// Q-function-shape-agnostic cached transpose apply (Jᵀ·w)
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
struct DerivativeApplyTranspose
{
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
DerivativeApplyTranspose(
IntegratorContext ctx,
qfunc_t /*qfunc*/,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache):
ctx(ctx), inputs(std::move(inputs)), outputs(std::move(outputs)),
qp_cache(qp_cache)
{
create_fop_to_fd(this->inputs, ctx.infds, input_to_infd);
create_fop_to_fd(this->outputs, ctx.outfds, output_to_outfd);
check_consistency(this->inputs, input_to_infd, ctx.infds);
check_consistency(this->outputs, output_to_outfd, ctx.outfds);
create_fieldbases(
this->inputs, input_to_infd, ctx.infds, ctx.ir, input_bases);
create_fieldbases(
this->outputs, output_to_outfd, ctx.outfds, ctx.ir, output_bases);
const int nqp = ctx.ir.GetNPoints();
const int ne = ctx.nentities;
gnqp = nqp * ne;
// Precompute Q-space BlockVector layouts
dir_q_offsets.SetSize(n_outputs + 1);
dir_q_offsets[0] = 0;
constexpr_for<0, n_outputs>([&](auto i)
{
dir_q_offsets[i + 1] =
dir_q_offsets[i] + get<i>(this->outputs).size_on_qp * nqp * ne;
});
InitBlockVector(dir_q_local, dir_q_offsets);
result_q_offsets.SetSize(n_inputs + 1);
result_q_offsets[0] = 0;
constexpr_for<0, n_inputs>([&](auto i)
{
result_q_offsets[i + 1] =
result_q_offsets[i] + get<i>(this->inputs).size_on_qp * nqp * ne;
});
InitBlockVector(result_q_local, result_q_offsets);
// Cache layout metadata
residual_size_on_qp = 0;
trial_vdim = 0;
total_trial_op_dim = 0;
constexpr auto activity =
detail::make_activity_map<derivative_id>(inputs_t{});
constexpr_for<0, n_inputs>([&](auto i)
{
if (!activity[i]) { return; }
const auto &fop = get<i>(this->inputs);
trial_vdim = fop.vdim;
total_trial_op_dim += fop.size_on_qp / fop.vdim;
});
constexpr_for<0, n_outputs>([&](auto i)
{ residual_size_on_qp += get<i>(this->outputs).size_on_qp; });
residual_size_on_qp *= trial_vdim * total_trial_op_dim;
}
void operator()(
const std::vector<Vector *> & /*xe*/,
const Vector *direction_l,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
MFEM_ASSERT(direction_l != nullptr,
"Global DerivativeApplyTranspose: direction vector is null");
// Re-zero the pre-allocated Q temporaries
dir_q_local = 0.0;
result_q_local = 0.0;
dir_q_local.SyncToBlocks();
result_q_local.SyncToBlocks();
// Bring test cotangent to quadrature points
pull_output_cotangents_to_q(direction_l, dir_q_local);
// Contract qp_cache with test directions at quadrature points
const real_t *cache_ptr = qp_cache.HostRead();
const int res_sz = residual_size_on_qp;
constexpr_for<0, n_outputs>([&](auto o)
{
const int tv_o = get<o>(outputs).vdim;
const int to_o = get<o>(outputs).size_on_qp / tv_o;
const int out_base = [&]
{
int off = 0;
constexpr_for<0, o.value>([&](auto prev)
{ off += get<prev>(outputs).size_on_qp; });
return off;
}();
const real_t *dir_o = dir_q_local.GetBlock(o.value).HostRead();
constexpr_for<0, n_inputs>([&](auto s)
{
if (get<s>(inputs).GetFieldId() != derivative_id) { return; }
real_t *res_s = result_q_local.GetBlock(s.value).HostReadWrite();
for (int gq = 0; gq < gnqp; ++gq)
{
for (int i = 0; i < tv_o; ++i)
{
for (int k = 0; k < to_o; ++k)
{
const int out_comp = out_base + i * to_o + k;
const int size_o = get<o>(outputs).size_on_qp;
const real_t w = dir_o[(i * to_o + k) + size_o * gq];
for (int j = 0; j < trial_vdim; ++j)
{
for (int m = 0; m < total_trial_op_dim; ++m)
{
const int cache_idx =
out_comp * trial_vdim * total_trial_op_dim +
j * total_trial_op_dim + m;
const real_t c = cache_ptr[cache_idx + res_sz * gq];
const int size_s = get<s>(inputs).size_on_qp;
res_s[(j * total_trial_op_dim + m) + size_s * gq] +=
c * w;
}
}
}
}
}
});
});
// Map result Q back to the trial (input) fields
constexpr_for<0, n_inputs>([&](auto s)
{
if (get<s>(inputs).GetFieldId() != derivative_id) { return; }
const size_t in_fd = input_to_infd[s.value];
input_bases[s.value].transpose(
result_q_local.GetBlock(s.value), *ye[in_fd]);
});
}
private:
IntegratorContext ctx;
inputs_t inputs;
outputs_t outputs;
const Vector &qp_cache;
std::array<size_t, n_inputs> input_to_infd;
std::array<size_t, n_outputs> output_to_outfd;
std::array<FieldBasis, n_inputs> input_bases;
std::array<FieldBasis, n_outputs> output_bases;
int gnqp = 0;
// Pre-allocated Q-space temporaries
Array<int> dir_q_offsets;
Array<int> result_q_offsets;
mutable BlockVector dir_q_local;
mutable BlockVector result_q_local;
// Pre-allocated owning storage for output cotangent temporaries
mutable std::array<Vector, n_outputs> dir_out_l_owned;
mutable std::array<Vector, n_outputs> dir_out_e_owned;
int residual_size_on_qp = 0;
int trial_vdim = 0;
int total_trial_op_dim = 0;
/// Pull output cotangents from L-space into the pre-allocated Q BlockVector
void pull_output_cotangents_to_q(const Vector *direction_l,
BlockVector &dir_q) const
{
std::vector<Vector *> dir_out_l(n_outputs);
std::vector<Vector *> dir_out_e(n_outputs);
int l_offset = 0;
constexpr_for<0, n_outputs>([&](auto i)
{
const size_t outfd = output_to_outfd[i];
const auto &fd = ctx.outfds[outfd];
const int l_size = GetVSize(fd);
dir_out_l_owned[i] =
Vector(*const_cast<Vector *>(direction_l), l_offset, l_size);
dir_out_e_owned[i].SetSize(0);
dir_out_e_owned[i].UseDevice(true);
dir_out_l[i] = &dir_out_l_owned[i];
dir_out_e[i] = &dir_out_e_owned[i];
l_offset += l_size;
});
restriction<Entity::Element>(ctx.outfds, dir_out_l, dir_out_e);
constexpr_for<0, n_outputs>([&](auto i)
{
output_bases[i.value].forward(*dir_out_e[i], dir_q.GetBlock(i.value));
});
dir_q.SyncToBlocks();
}
};
} // namespace mfem::future::GlobalQFImpl
@@ -0,0 +1,226 @@
// 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 "../../integrator_ctx.hpp"
#include "../util.hpp"
#include <utility>
namespace mfem::future::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 DerivativeSetup
{
DerivativeSetup(
IntegratorContext ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs,
Vector &qp_cache) :
ctx(ctx),
qfunc(std::move(qfunc)),
inputs(inputs),
outputs(outputs),
qp_cache(qp_cache)
{
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();
num_qp = nqp;
nentities = ctx.nentities;
gnqp = nqp * nentities;
xq_offsets.SetSize(ninputs + 1);
xq_offsets[0] = 0;
constexpr_for<0, ninputs>([&](auto i)
{
xq_offsets[i + 1] = nqp * get<i>(inputs).size_on_qp * nentities;
});
xq_offsets.PartialSum();
InitBlockVector(xq, xq_offsets);
shadow_xq_offsets.SetSize(xq_offsets.Size());
shadow_xq_offsets = xq_offsets;
InitBlockVector(shadow_xq, shadow_xq_offsets);
yq_offsets.SetSize(noutputs + 1);
yq_offsets[0] = 0;
constexpr_for<0, noutputs>([&](auto o)
{
yq_offsets[o + 1] = nqp * get<o>(outputs).size_on_qp * nentities;
});
yq_offsets.PartialSum();
InitBlockVector(yq, yq_offsets);
total_out_size_on_qp = 0;
constexpr_for<0, noutputs>([&](auto o)
{
total_out_size_on_qp += get<o>(outputs).size_on_qp;
out_vdim[o] = get<o>(outputs).vdim;
out_op_dim[o] = get<o>(outputs).size_on_qp / get<o>(outputs).vdim;
});
activity_map = detail::make_activity_map<derivative_id>(inputs_t {});
trial_vdim = 0;
total_trial_op_dim = 0;
constexpr_for<0, ninputs>([&](auto i)
{
if (!activity_map[i]) { return; }
const auto inp = get<i>(inputs);
trial_vdim = inp.vdim;
total_trial_op_dim += inp.size_on_qp / inp.vdim;
});
constexpr_for<0, ninputs>([&](auto i)
{
input_size_on_qp_arr[i] = get<i>(inputs).size_on_qp;
});
residual_size_on_qp = total_out_size_on_qp * trial_vdim * total_trial_op_dim;
qp_cache.SetSize(residual_size_on_qp * num_qp * nentities);
qp_cache.UseDevice(true);
}
void operator()(const std::vector<Vector *> &xe) const
{
if (ctx.attr.Size() == 0) { return; }
qp_cache = 0.0;
interpolate(input_to_infd, input_bases, xe, xq);
const int gnqp_local = gnqp;
const int trial_vdim_local = trial_vdim;
const int total_trial_op_dim_local = total_trial_op_dim;
const int residual_size_local = residual_size_on_qp;
for (int j = 0; j < trial_vdim; j++)
{
int m_offset = 0;
constexpr_for<0, ninputs>([&](auto s)
{
if (!activity_map[s]) { return; }
const int input_vdim_s = get<s>(inputs).vdim;
const int input_size_s = input_size_on_qp_arr[s];
const int trial_op_dim_s = input_size_s / input_vdim_s;
for (int m = 0; m < trial_op_dim_s; m++)
{
shadow_xq = 0.0;
// Set component (j + input_vdim_s * m) to 1 at all QPs
const int c_shadow = j + input_vdim_s * m;
real_t *shadow_ptr = shadow_xq.GetBlock(s.value).HostReadWrite();
for (int gq = 0; gq < gnqp_local; gq++)
{
shadow_ptr[c_shadow + input_size_s * gq] = 1.0;
}
yq = 0.0;
detail::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> {});
yq.SyncToBlocks();
real_t *cache_ptr = qp_cache.HostReadWrite();
// Write yq into the cache column
const int m_global = m + m_offset;
const int j_cur = j;
int out_offset = 0;
constexpr_for<0, noutputs>([&](auto o)
{
const int test_vdim_o = out_vdim[o];
const int test_op_dim_o = out_op_dim[o];
const int yq_out_size = test_vdim_o * test_op_dim_o;
const int out_offset_o = out_offset;
const real_t *yq_ptr = yq.GetBlock(o.value).HostRead();
for (int gq = 0; gq < gnqp_local; gq++)
{
for (int i = 0; i < test_vdim_o; i++)
{
for (int k = 0; k < test_op_dim_o; k++)
{
const int c_out = i * test_op_dim_o + k;
const int out_comp = out_offset_o + c_out;
const int cache_idx =
out_comp * trial_vdim_local * total_trial_op_dim_local +
j_cur * total_trial_op_dim_local +
m_global;
cache_ptr[cache_idx + residual_size_local * gq] =
yq_ptr[c_out + yq_out_size * gq];
}
}
}
out_offset += yq_out_size;
});
}
m_offset += trial_op_dim_s;
});
}
}
IntegratorContext ctx;
qfunc_t qfunc;
inputs_t inputs;
outputs_t outputs;
Vector &qp_cache;
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;
int num_qp = 0;
int nentities = 0;
Array<int> xq_offsets, shadow_xq_offsets, yq_offsets;
mutable BlockVector xq, shadow_xq, yq;
int total_out_size_on_qp = 0;
int trial_vdim = 0;
int total_trial_op_dim = 0;
int residual_size_on_qp = 0;
std::array<int, noutputs> out_vdim {};
std::array<int, noutputs> out_op_dim {};
std::array<int, ninputs> input_size_on_qp_arr {};
std::array<bool, ninputs> activity_map {};
};
} // namespace mfem::future::GlobalQFImpl
+166
View File
@@ -0,0 +1,166 @@
// 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 "action.hpp"
#include "derivative_action.hpp"
#include "derivative_apply.hpp"
#include "derivative_setup.hpp"
#include "derivative_apply_transpose.hpp"
#include "../local_qf/derivative_assemble.hpp"
#include "../local_qf/derivative_assemble_diagonal.hpp"
namespace mfem::future
{
struct GlobalQFBackend
{
/**
* @brief Make an action for a global Q-function.
*
* @param ctx The integrator context.
* @param args The arguments to the action.
* @return The action.
*/
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);
}
/**
* @brief Make a derivative action for a global Q-function.
*
* @tparam derivative_id The id of the derivative.
* @param ctx The integrator context.
* @param args The arguments to the derivative action.
* @return The derivative action.
*/
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::DerivativeAction<
derivative_id, qfunc_t, inputs_t, outputs_t>(
ctx, qfunc, inputs, outputs);
}
/**
* @brief Make a derivative setup for a global Q-function.
*
* @tparam derivative_id The id of the derivative.
* @param ctx The integrator context.
* @param args The arguments to the derivative setup.
* @return The derivative setup.
*/
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeSetup(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs,
Vector &qp_cache)
{
return GlobalQFImpl::DerivativeSetup<
derivative_id, qfunc_t, inputs_t, outputs_t>(
ctx, qfunc, inputs, outputs, qp_cache);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeApply(
const IntegratorContext &ctx,
qfunc_t /*unused*/,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache)
{
return GlobalQFImpl::DerivativeApply<
derivative_id, qfunc_t, inputs_t, outputs_t>(
ctx, qfunc_t{}, inputs, outputs, qp_cache);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeApplyTranspose(
const IntegratorContext &ctx,
qfunc_t /*unused*/,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache)
{
return GlobalQFImpl::DerivativeApplyTranspose<
derivative_id, qfunc_t, inputs_t, outputs_t>(
ctx, qfunc_t{}, inputs, outputs, qp_cache);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeAssemble(
const IntegratorContext &ctx,
qfunc_t /*unused*/,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache)
{
return LocalQFImpl::DerivativeAssemble<
derivative_id, qfunc_t, inputs_t, outputs_t>(
ctx, qfunc_t{}, inputs, outputs, qp_cache);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeAssembleDiagonal(
const IntegratorContext &ctx,
qfunc_t /*unused*/,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache)
{
return LocalQFImpl::DerivativeAssembleDiagonal<
derivative_id, qfunc_t, inputs_t, outputs_t>(
ctx, qfunc_t{}, inputs, outputs, qp_cache);
}
};
} // namespace mfem::future
+509
View File
@@ -0,0 +1,509 @@
// 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 "kernels.hpp"
#include "util.hpp"
#include "../../integrator_ctx.hpp"
#include "../util.hpp"
namespace mfem::future::LocalQFImpl
{
template<typename qfunc_t, typename inputs_t, typename outputs_t>
class Action
{
static constexpr auto inout_tuple =
merge_mfem_tuples_as_empty_std_tuple(inputs_t {}, outputs_t{});
static constexpr auto filtered_inout_tuple = filter_fields(inout_tuple);
static constexpr size_t nfields =
count_unique_field_ids(filtered_inout_tuple);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
using args_tuple_t = decay_tuple<qf_param_ts>;
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
static_assert(n_inputs + n_outputs == tuple_size<qf_param_ts>::value,
"LocalQF: q-function arity must match inputs + outputs");
const qfunc_t qfunc;
const inputs_t inputs;
const outputs_t outputs;
const IntegratorContext ctx;
const std::vector<const DofToQuad *> dtqs;
// inputs: dtq, idx, B, G, d1d, q1d, vdim
const std::array<DofToQuadMap, n_inputs> input_dtq;
const std::array<size_t, n_inputs> input_idx; // input to field
const std::array<const real_t *, n_inputs> input_B, input_G;
const std::array<int, n_inputs> input_d1d, input_q1d, input_vdim;
// outputs: dtq, idx, B, G, d1d, q1d, vdim
const std::array<DofToQuadMap, n_outputs> output_dtq;
const std::array<size_t, n_outputs> output_idx; // output to field
const std::array<const real_t *, n_outputs> output_B, output_G;
const std::array<int, n_outputs> output_d1d, output_q1d, output_vdim;
// other constants
const int dim, ne, nq, q1d;
public:
////////////////////////////////////////////////////////
Action() = delete;
Action(IntegratorContext ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs):
qfunc(std::move(qfunc)), inputs(inputs), outputs(outputs), ctx(ctx),
dtqs(make_dtqs(ctx)),
// inputs: dtq, idx, B, G, d1d, q1d, vdim
input_dtq(create_dtq_maps<Entity::Element>(
inputs,
dtqs,
create_union_field_map_for_dtq(ctx, inputs),
ctx.unionfds,
ctx.ir)),
input_idx(create_input_vector_map(ctx, inputs)),
input_B(get_B(input_dtq)), input_G(get_G(input_dtq)),
input_d1d(get_D1D(input_dtq)), input_q1d(get_Q1D(input_dtq)),
input_vdim(get_vdim(inputs)),
// outputs: dtq, idx, B, G, d1d, q1d, vdim
output_dtq(create_dtq_maps<Entity::Element>(
outputs,
dtqs,
create_union_field_map_for_dtq(ctx, outputs),
ctx.unionfds,
ctx.ir)),
output_idx(create_output_vector_map(ctx, outputs)),
output_B(get_B(output_dtq)), output_G(get_G(output_dtq)),
output_d1d(get_D1D(output_dtq)), output_q1d(get_Q1D(output_dtq)),
output_vdim(get_vdim(outputs)),
// other constants
dim(ctx.mesh.Dimension()), ne(ctx.nentities), nq(ctx.ir.GetNPoints()),
q1d(tensor_1d_size(nq, dim))
{
MFEM_ASSERT(ctx.unionfds.size() == nfields,
"LocalQFBackend: unionfds size mismatch");
}
template<typename Backend>
void run_kernels(const std::vector<Vector *> &xe,
std::vector<Vector *> &ye) const
{
Backend::Run(dim,
q1d,
// arguments
ctx,
qfunc,
// inputs
input_idx,
input_B,
input_G,
input_vdim,
input_d1d,
input_q1d,
// outputs
output_idx,
output_B,
output_G,
output_vdim,
output_d1d,
output_q1d,
// input and output vectors
xe,
ye,
// fallback arguments
dim,
q1d);
}
void operator()(const std::vector<Vector *> &xe,
std::vector<Vector *> &ye) const
{
if (q1d <= LocalQFLOBackendMQ1())
{
run_kernels<ActionLO>(xe, ye);
}
else if (q1d <= LocalQFHOBackendMQ1())
{
run_kernels<ActionHO>(xe, ye);
}
else
{
MFEM_ABORT("Unsupported quadrature order for LocalQF backend");
}
}
////////////////////////////////////////////////////////
template<typename backend_t = LocalQFLOBackend<3>, int T_Q1D = 0>
static void
action_callback(const IntegratorContext &ctx,
const qfunc_t &qfunc,
// inputs: idx, B, G, vdim, d1d, q1d
const std::array<size_t, n_inputs> &in_idx,
const std::array<const real_t *, n_inputs> in_B,
const std::array<const real_t *, n_inputs> in_G,
const std::array<int, n_inputs> &in_vdim,
const std::array<int, n_inputs> &in_d1d,
const std::array<int, n_inputs> &in_q1d,
// outputs: idx, B, G, vdim, d1d, q1d
const std::array<size_t, n_outputs> &out_idx,
const std::array<const real_t *, n_outputs> out_B,
const std::array<const real_t *, n_outputs> out_G,
const std::array<int, n_outputs> &out_vdim,
const std::array<int, n_outputs> &out_d1d,
const std::array<int, n_outputs> &out_q1d,
const std::vector<Vector *> &xe,
std::vector<Vector *> &ye,
// fallback arguments
const int dim,
const int q1d)
{
if (ctx.attr.Size() == 0) { return; }
MFEM_CONTRACT_VAR(dim);
MFEM_ASSERT(dim == ctx.mesh.Dimension(), "Dimension mismatch");
static constexpr auto B2D = backend_t::DIM == 2;
static constexpr auto MQ1 = T_Q1D ? T_Q1D : backend_t::Q1D;
static constexpr auto MTPB = backend_t::MAX_THREADS_PER_BLOCK();
const int ne = ctx.nentities;
constexpr auto k_dim = [](const int k) { return k * k * (B2D ? 1 : k); };
// --------------------------------------------------
// INPUTS: XE, 3(max DIM) + 1(VDIM) + 1(number of elements)
// --------------------------------------------------
std::array<DeviceTensor<3 + 1 + 1, const real_t>, n_inputs> in_XE;
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const size_t k = in_idx[i];
const int d = in_d1d[i], q = in_q1d[i], v = in_vdim[i];
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
MFEM_VERIFY(xe[k]->Size() == k_dim(d) * v * ne, "Size mismatch");
in_XE[i] = Reshape(xe[k]->Read(), d, d, B2D ? 1 : d, v, ne);
}
else if constexpr (is_identity_fop_v<FOP>)
{
MFEM_VERIFY(xe[k]->Size() == k_dim(q) * v * ne, "Size mismatch");
in_XE[i] = Reshape(xe[k]->Read(), v, q, q, B2D ? 1 : q, ne);
}
else if constexpr (is_weight_fop_v<FOP>)
{
MFEM_VERIFY(ctx.ir.GetNPoints() == k_dim(q1d),
"tensor-product IR expected");
in_XE[i] = Reshape(
ctx.ir.GetWeights().Read(), q1d, q1d, B2D ? 1 : q1d, 1, 1);
}
else
{
static_assert(false, "Unsupported");
}
});
// --------------------------------------------------
// OUTPUTS: YE, 3(max DIM) + 1(VDIM) + 1(number of elements)
// --------------------------------------------------
std::array<DeviceTensor<3 + 1 + 1, real_t>, n_outputs> out_YE;
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const size_t k = out_idx[i];
const int d = out_d1d[i], q = out_q1d[i], v = out_vdim[i];
using FOP = tuple_element_t<i, outputs_t>;
if constexpr (is_gradient_fop_v<FOP> || is_value_fop_v<FOP>)
{
MFEM_ASSERT(ye[k]->Size() == k_dim(d) * v * ne, "Size mismatch");
out_YE[i] = Reshape(ye[k]->ReadWrite(), d, d, B2D ? 1 : d, v, ne);
}
else if constexpr (is_identity_fop_v<FOP>)
{
MFEM_ASSERT(ye[k]->Size() == k_dim(q) * v * ne, "Size mismatch");
out_YE[i] = Reshape(ye[k]->ReadWrite(), v, q, q, B2D ? 1 : q, ne);
}
else
{
static_assert(false, "Unsupported FieldOperator");
}
});
const auto d_attr = ctx.attr.Read();
const bool has_attr = ctx.attr.Size() > 0;
const auto d_elem_attr = ctx.elem_attr->Read();
dfem::forall<MTPB>(
[=] MFEM_HOST_DEVICE(const int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
// -----------------------------------------------
// Inputs and outputs argument registers
// -----------------------------------------------
args_reg_t<backend_t, qfunc_t, inputs_t, outputs_t, MQ1> rargs;
// -----------------------------------------------
// Shared memory
// -----------------------------------------------
MFEM_SHARED typename backend_t::Shared smem;
// -----------------------------------------------
// Load inputs
// -----------------------------------------------
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const auto &XE = in_XE[i];
const int d = in_d1d[i], q = in_q1d[i], Q1D = q1d;
;
const real_t *B = in_B[i], *G = in_G[i];
auto &rarg = get<i>(rargs);
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop<FOP>::value)
{
backend_t::LoadValue(smem, e, d, q, Q1D, B, XE, rarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
using XE_t = decltype(XE);
using rarg_t = decltype(rarg);
using qf_param_t =
typename qf_param_slot<qfunc_t, i>::qf_decay_param_t;
constexpr auto RNK = qf_param_slot<qfunc_t, i>::extents.size();
backend_t::template LoadGradient<RNK, rarg_t, XE_t, qf_param_t>(
smem, e, d, q, q1d, B, G, XE, rarg);
}
else if constexpr (is_weight_fop_v<FOP> || is_identity_fop_v<FOP>)
{
// qp values are read directly from in_XE / IR
}
else
{
static_assert(false, "Unsupported");
}
});
// -----------------------------------------------
// Evaluate the quadrature function
// Warning: no 'DIRECT' on the 'Z' direction,
// as one backend may need to iterate over it.
// -----------------------------------------------
MFEM_FOREACH_THREAD(qz, z, (B2D ? 1 : q1d))
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
args_tuple_t qargs;
// --------------------------------------
// Pulling arguments from registers to qargs tuple
// --------------------------------------
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
auto &qarg = get<i>(qargs);
const auto &XE = in_XE[i];
using FOP = tuple_element_t<i, inputs_t>;
using ARG =
typename qf_param_slot<qfunc_t, i>::qf_reg_param_t;
if constexpr (is_identity_fop_v<FOP>)
{
using DT =
typename qf_param_slot<qfunc_t, i>::qf_decay_param_t;
if constexpr (qf_param_uses_dual_v<DT>)
{
qarg = backend_t::template identity_qp_pull_dual<DT>(
false, XE, XE, qx, qy, qz, e);
}
else
{
qarg = as_tensor<ARG>(&XE(0, qx, qy, qz, e));
}
}
else if constexpr (is_weight_fop_v<FOP>)
{
qarg = XE(qx, qy, qz, 0, 0);
}
else if constexpr (is_value_fop_v<FOP> ||
is_gradient_fop_v<FOP>)
{
qarg = backend_t::template qp_pull<ARG>(
get<i>(rargs), qx, qy, qz);
}
else
{
static_assert(false, "Unsupported");
}
});
// --------------------------------------
// Call the quadrature function
// --------------------------------------
call_qfunc_no_move(qfunc, qargs);
// --------------------------------------
// Pushing arguments from qargs tuple to registers
// --------------------------------------
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value, o = n_inputs + i;
const auto &qarg = get<o>(qargs);
const auto &YE = out_YE[i];
using FOP = tuple_element_t<i, outputs_t>;
using ARG =
typename qf_param_slot<qfunc_t, o>::qf_reg_param_t;
if constexpr (is_identity_fop_v<FOP>)
{
using DT =
typename qf_param_slot<qfunc_t, o>::qf_decay_param_t;
if constexpr (qf_param_uses_dual_v<DT>)
{
backend_t::identity_qp_write_value(
YE, qx, qy, qz, e, qarg);
}
else
{
as_tensor<ARG>(&YE(0, qx, qy, qz, e)) = qarg;
}
}
else if constexpr (is_value_fop_v<FOP> ||
is_gradient_fop_v<FOP>)
{
auto &rarg = get<o>(rargs);
backend_t::template qp_push<ARG>(
rarg, qx, qy, qz, qarg);
}
else
{
static_assert(false, "Unsupported");
}
});
}
}
}
MFEM_SYNC_THREAD;
// -----------------------------------------------
// Integrate outputs
// -----------------------------------------------
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value, o = n_inputs + i;
const int d = out_d1d[i], q = out_q1d[i], Q1D = q1d;
const auto B = out_B[i], G = out_G[i];
const auto &YE = out_YE[i];
auto &rarg = get<o>(rargs);
using FOP = tuple_element_t<i, outputs_t>;
if constexpr (is_value_fop_v<FOP>)
{
backend_t::WriteValue(smem, e, d, q, q1d, B, YE, rarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
using YE_t = decltype(YE);
using rarg_t = decltype(rarg);
using qf_param_t =
typename qf_param_slot<qfunc_t, i>::qf_decay_param_t;
constexpr auto RNK = qf_param_slot<qfunc_t, o>::extents.size();
backend_t::template WriteGradient<RNK, rarg_t, YE_t, qf_param_t>(
smem, e, d, q, Q1D, B, G, YE, rarg);
}
else if constexpr (is_identity_fop_v<FOP>)
{
// nothing to do
}
else
{
static_assert(false, "Unsupported");
}
});
},
ne,
backend_t::thread_blocks(compute_kernel_thread_1d<inputs_t, outputs_t>(
q1d, in_d1d, out_d1d)),
0,
nullptr);
}
using KernelType = decltype(&Action::action_callback<>);
MFEM_REGISTER_KERNELS_HEADER_ONLY(ActionLO, KernelType, (int, int) );
MFEM_REGISTER_KERNELS_HEADER_ONLY(ActionHO, KernelType, (int, int) );
};
// Low Order kernels
template<typename qfunc_t, typename inputs_t, typename outputs_t>
template<int DIM, int Q1D>
inline typename Action<qfunc_t, inputs_t, outputs_t>::KernelType
Action<qfunc_t, inputs_t, outputs_t>::ActionLO::Kernel()
{
static_assert(Q1D <= LocalQFLOBackend<DIM>::MQ1);
using action_t = Action<qfunc_t, inputs_t, outputs_t>;
return action_t::template action_callback<LocalQFLOBackend<DIM, Q1D>>;
}
// Low Order fallback
template<typename qfunc_t, typename inputs_t, typename outputs_t>
inline typename Action<qfunc_t, inputs_t, outputs_t>::KernelType
Action<qfunc_t, inputs_t, outputs_t>::ActionLO::Fallback(int dim, int q1d)
{
using action_t = Action<qfunc_t, inputs_t, outputs_t>;
using ActionLO = typename action_t::ActionLO;
if (dim == 2)
{
return DispatchLOKernelByQ1D<ActionLO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchLOKernelByQ1D<ActionLO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
// High Order kernels
template<typename qfunc_t, typename inputs_t, typename outputs_t>
template<int DIM, int Q1D>
inline typename Action<qfunc_t, inputs_t, outputs_t>::KernelType
Action<qfunc_t, inputs_t, outputs_t>::ActionHO::Kernel()
{
using action_t = Action<qfunc_t, inputs_t, outputs_t>;
return action_t::template action_callback<LocalQFHOBackend<DIM>, Q1D>;
}
// High Order fallback
template<typename qfunc_t, typename inputs_t, typename outputs_t>
inline typename Action<qfunc_t, inputs_t, outputs_t>::KernelType
Action<qfunc_t, inputs_t, outputs_t>::ActionHO::Fallback(int dim, int q1d)
{
using action_t = Action<qfunc_t, inputs_t, outputs_t>;
using ActionHO = typename action_t::ActionHO;
if (dim == 2)
{
return DispatchHOKernelByQ1D<ActionHO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchHOKernelByQ1D<ActionHO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
} // namespace mfem::future::LocalQFImpl
@@ -0,0 +1,761 @@
// 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 "../../integrator_ctx.hpp"
#include "kernels.hpp"
#include "util.hpp"
namespace mfem::future::LocalQFImpl
{
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
class DerivativeAction
{
static constexpr auto inout_tuple =
merge_mfem_tuples_as_empty_std_tuple(inputs_t {}, outputs_t{});
static constexpr auto filtered_inout_tuple = filter_fields(inout_tuple);
static constexpr size_t nfields =
count_unique_field_ids(filtered_inout_tuple);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
using args_tuple_t = decay_tuple<qf_param_ts>;
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
static_assert(n_inputs + n_outputs == tuple_size<qf_param_ts>::value,
"LocalQF: q-function arity must match inputs + outputs");
const qfunc_t qfunc;
const inputs_t inputs;
const outputs_t outputs;
const IntegratorContext ctx;
const std::vector<const DofToQuad *> dtqs;
// inputs: dtq, idx, B, G, d1d, q1d, vdim
const std::array<DofToQuadMap, n_inputs> input_dtq;
const std::array<size_t, n_inputs> input_idx;
const std::array<const real_t *, n_inputs> input_B, input_G;
const std::array<int, n_inputs> input_d1d, input_q1d, input_vdim;
// outputs: dtq, idx, B, G, d1d, q1d, vdim
const std::array<DofToQuadMap, n_outputs> output_dtq;
const std::array<size_t, n_outputs> output_idx;
const std::array<const real_t *, n_outputs> output_B, output_G;
const std::array<int, n_outputs> output_d1d, output_q1d, output_vdim;
// other constants
const int dim, ne, nq, q1d;
std::array<bool, n_inputs> input_is_dependent;
FieldDescriptor direction_fd;
mutable Vector direction_e;
public:
//////////////////////////////////////////////////////////////////
DerivativeAction() = delete;
DerivativeAction(IntegratorContext ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs):
qfunc(std::move(qfunc)), inputs(inputs), outputs(outputs), ctx(ctx),
dtqs(make_dtqs(ctx)),
// inputs: dtq, idx, B, G, d1d, q1d, vdim
input_dtq(create_dtq_maps<Entity::Element>(
inputs,
dtqs,
create_union_field_map_for_dtq(ctx, inputs),
ctx.unionfds,
ctx.ir)),
input_idx(create_input_vector_map(ctx, inputs)),
input_B(get_B(input_dtq)), input_G(get_G(input_dtq)),
input_d1d(get_D1D(input_dtq)), input_q1d(get_Q1D(input_dtq)),
input_vdim(get_vdim(inputs)),
// outputs: dtq, idx, B, G, d1d, q1d, vdim
output_dtq(create_dtq_maps<Entity::Element>(
outputs,
dtqs,
create_union_field_map_for_dtq(ctx, outputs),
ctx.unionfds,
ctx.ir)),
output_idx(create_output_vector_map(ctx, outputs)),
output_B(get_B(output_dtq)), output_G(get_G(output_dtq)),
output_d1d(get_D1D(output_dtq)), output_q1d(get_Q1D(output_dtq)),
output_vdim(get_vdim(outputs)),
// other constants
dim(ctx.mesh.Dimension()), ne(ctx.nentities), nq(ctx.ir.GetNPoints()),
q1d(tensor_1d_size(nq, dim))
{
MFEM_ASSERT(ctx.unionfds.size() == nfields,
"LocalQFBackend: unionfds size mismatch");
// Determine which inputs are dependent on the derivative direction
auto dependency_map = make_dependency_map(inputs);
auto it = dependency_map.find(derivative_id);
MFEM_ASSERT(it != dependency_map.end(),
"Derivative ID not found in dependency map");
input_is_dependent = it->second;
// Find direction field index
int direction_field_idx = -1;
for (size_t uf = 0; uf < nfields; uf++)
{
if (static_cast<int>(ctx.unionfds[uf].id) == derivative_id)
{
direction_field_idx = static_cast<int>(uf);
break;
}
}
MFEM_ASSERT(
direction_field_idx != -1,
"LocalQFBackend: derivative direction field not found in unionfds");
direction_fd = ctx.unionfds[static_cast<size_t>(direction_field_idx)];
}
//////////////////////////////////////////////////////////////////
template<typename Backend>
void run_kernels(const std::vector<Vector *> &xe,
std::vector<Vector *> &ye) const
{
Backend::Run(dim,
q1d,
// arguments
ctx,
qfunc,
// inputs
input_idx,
input_B,
input_G,
input_vdim,
input_d1d,
input_q1d,
// outputs
output_idx,
output_B,
output_G,
output_vdim,
output_d1d,
output_q1d,
// input and output vectors
xe,
ye,
input_is_dependent,
direction_e,
// fallback arguments
dim,
q1d);
}
//////////////////////////////////////////////////////////////////
void operator()(const std::vector<Vector *> &xe,
const Vector *direction_l,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
MFEM_ASSERT(direction_l != nullptr,
"LocalQF DerivativeAction: direction vector is null");
restriction<Entity::Element>(direction_fd,
*direction_l,
direction_e,
ElementDofOrdering::LEXICOGRAPHIC);
if (q1d <= LocalQFLOBackendMQ1())
{
run_kernels<DerivativeActionLO>(xe, ye);
}
else if (q1d <= LocalQFHOBackendMQ1())
{
run_kernels<DerivativeActionHO>(xe, ye);
}
else
{
MFEM_ABORT("Unsupported quadrature order for LocalQF backend");
}
}
//////////////////////////////////////////////////////////////////
template<typename backend_t = LocalQFLOBackend<3>, int T_Q1D = 0>
static void
derivative_action_callback(const IntegratorContext &ctx,
const qfunc_t &qfunc,
// inputs: idx, B, G, vdim, d1d, q1d
const std::array<size_t, n_inputs> &in_idx,
const std::array<const real_t *, n_inputs> in_B,
const std::array<const real_t *, n_inputs> in_G,
const std::array<int, n_inputs> &in_vdim,
const std::array<int, n_inputs> &in_d1d,
const std::array<int, n_inputs> &in_q1d,
// outputs: idx, B, G, vdim, d1d, q1d
const std::array<size_t, n_outputs> &out_idx,
const std::array<const real_t *, n_outputs> out_B,
const std::array<const real_t *, n_outputs> out_G,
const std::array<int, n_outputs> &out_vdim,
const std::array<int, n_outputs> &out_d1d,
const std::array<int, n_outputs> &out_q1d,
const std::vector<Vector *> &xe,
std::vector<Vector *> &ye,
const std::array<bool, n_inputs> &input_dep,
const Vector &direction_e,
// fallback arguments
const int dim,
const int q1d)
{
MFEM_VERIFY(dim == ctx.mesh.Dimension(), "Dimension mismatch");
if (ctx.attr.Size() == 0) { return; }
static constexpr auto B2D = backend_t::DIM == 2;
static constexpr auto MQ1 = T_Q1D ? T_Q1D : backend_t::MQ1;
static constexpr auto MTPB = backend_t::MAX_THREADS_PER_BLOCK();
const int ne = ctx.nentities;
constexpr auto k_dim = [](const int k) { return k * k * (B2D ? 1 : k); };
// --------------------------------------------------
// INPUTS: XE, 3(max DIM) + 1(VDIM) + 1(number of elements)
// --------------------------------------------------
std::array<DeviceTensor<3 + 1 + 1, const real_t>, n_inputs> in_XE;
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const size_t k = in_idx[i];
const int d = in_d1d[i], q = in_q1d[i], v = in_vdim[i];
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
MFEM_VERIFY(xe[k]->Size() == k_dim(d) * v * ne, "Size mismatch");
in_XE[i] = Reshape(xe[k]->Read(), d, d, B2D ? 1 : d, v, ne);
}
else if constexpr (is_identity_fop_v<FOP>)
{
MFEM_VERIFY(xe[k]->Size() == k_dim(q) * v * ne, "Size mismatch");
in_XE[i] = Reshape(xe[k]->Read(), v, q, q, B2D ? 1 : q, ne);
}
else if constexpr (is_weight_fop_v<FOP>)
{
MFEM_VERIFY(ctx.ir.GetNPoints() == k_dim(q1d),
"tensor-product IR expected");
in_XE[i] = Reshape(
ctx.ir.GetWeights().Read(), q1d, q1d, B2D ? 1 : q1d, 1, 1);
}
else
{
static_assert(false, "Unsupported");
}
});
const auto d_direction = direction_e.Read();
std::array<DeviceTensor<3 + 1 + 1, const real_t>, n_inputs> in_XE_dir;
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const size_t k = in_idx[i];
const int d = in_d1d[i], q = in_q1d[i], v = in_vdim[i];
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
if (input_dep[i])
{
MFEM_ASSERT(direction_e.Size() == xe[k]->Size(),
"direction E-vector size mismatch for input " << i);
in_XE_dir[i] = Reshape(d_direction, d, d, B2D ? 1 : d, v, ne);
}
else
{
in_XE_dir[i] = in_XE[i];
}
}
else if constexpr (is_identity_fop_v<FOP>)
{
if (input_dep[i])
{
MFEM_VERIFY(direction_e.Size() == xe[k]->Size(),
"direction E-vector size mismatch (identity input) "
<< i);
in_XE_dir[i] = Reshape(d_direction, v, q, q, B2D ? 1 : q, ne);
}
else
{
in_XE_dir[i] = in_XE[i];
}
}
else if constexpr (is_weight_fop_v<FOP>) { in_XE_dir[i] = in_XE[i]; }
else
{
static_assert(false, "Unsupported");
}
});
// --------------------------------------------------
// OUTPUTS: YE, 3(max DIM) + 1(VDIM) + 1(number of elements)
// --------------------------------------------------
std::array<DeviceTensor<3 + 1 + 1, real_t>, n_outputs> out_YE;
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const size_t k = out_idx[i];
const int d = out_d1d[i], q = out_q1d[i], v = out_vdim[i];
using FOP = tuple_element_t<i, outputs_t>;
if constexpr (is_gradient_fop_v<FOP> || is_value_fop_v<FOP>)
{
MFEM_ASSERT(ye[k]->Size() == k_dim(d) * v * ne, "Size mismatch");
out_YE[i] = Reshape(ye[k]->ReadWrite(), d, d, B2D ? 1 : d, v, ne);
}
else if constexpr (is_identity_fop_v<FOP>)
{
MFEM_ASSERT(ye[k]->Size() == k_dim(q) * v * ne, "Size mismatch");
out_YE[i] = Reshape(ye[k]->ReadWrite(), v, q, q, B2D ? 1 : q, ne);
}
else
{
static_assert(false, "Unsupported FieldOperator");
}
});
const auto d_attr = ctx.attr.Read();
const bool has_attr = ctx.attr.Size() > 0;
const auto d_elem_attr = ctx.elem_attr->Read();
dfem::forall<MTPB>(
[=] MFEM_HOST_DEVICE(const int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
// -----------------------------------------------
// Inputs and outputs argument registers
// -----------------------------------------------
args_reg_t<backend_t, qfunc_t, inputs_t, outputs_t, MQ1> rargs;
input_args_reg_t<backend_t, qfunc_t, inputs_t, outputs_t, MQ1>
sargs; // shadow
// -----------------------------------------------
// Shared memory
// -----------------------------------------------
MFEM_SHARED typename backend_t::Shared smem;
// -----------------------------------------------
// Load primal inputs (rargs)
// -----------------------------------------------
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const auto &XE = in_XE[i];
const int d = in_d1d[i], q = in_q1d[i], Q1D = q1d;
const real_t *B = in_B[i], *G = in_G[i];
auto &rarg = get<i>(rargs);
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop<FOP>::value)
{
backend_t::LoadValue(smem, e, d, q, Q1D, B, XE, rarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
constexpr auto RNK = qf_param_slot<qfunc_t, i>::extents.size();
using FieldParamT =
typename qf_param_slot<qfunc_t, i>::qf_decay_param_t;
backend_t::template LoadGradient<RNK,
decltype(rarg),
decltype(XE),
FieldParamT>(
smem, e, d, q, Q1D, B, G, XE, rarg);
}
else if constexpr (is_weight_fop_v<FOP> || is_identity_fop_v<FOP>)
{
// qp values are read directly from in_XE / IR
}
else
{
static_assert(false, "Unsupported");
}
});
// -----------------------------------------------
// Load tangent directions (sargs)
// -----------------------------------------------
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
if (!input_dep[i]) { return; }
const auto &XE = in_XE_dir[i];
const int d = in_d1d[i], q = in_q1d[i], Q1D = q1d;
const real_t *B = in_B[i], *G = in_G[i];
auto &sarg = get<i>(sargs); // shadow argument register
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop<FOP>::value)
{
backend_t::LoadValue(smem, e, d, q, Q1D, B, XE, sarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
constexpr auto RNK = qf_param_slot<qfunc_t, i>::extents.size();
using FieldParamT =
typename qf_param_slot<qfunc_t, i>::qf_decay_param_t;
backend_t::template LoadGradient<RNK,
decltype(sarg),
decltype(XE),
FieldParamT>(
smem, e, d, q, Q1D, B, G, XE, sarg);
}
else if constexpr (is_weight_fop_v<FOP> || is_identity_fop_v<FOP>)
{
}
else
{
static_assert(false, "Unsupported");
}
});
// -----------------------------------------------
// Evaluate the quadrature function
// Warning: no 'DIRECT' on the 'Z' direction,
// as one backend may need to iterate over it.
// -----------------------------------------------
MFEM_FOREACH_THREAD(qz, z, (B2D ? 1 : q1d))
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
#ifdef MFEM_USE_ENZYME
args_tuple_t primal_args {}, shadow_args {};
// --------------------------------------
// Pulling arguments from registers to primal and shadow
// tuples
// --------------------------------------
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
auto &parg = get<i>(primal_args);
auto &targ = get<i>(shadow_args);
const auto &XE = in_XE[i];
const auto &XEd = in_XE_dir[i];
using FOP = tuple_element_t<i, inputs_t>;
using ARG =
typename qf_param_slot<qfunc_t, i>::qf_reg_param_t;
if constexpr (is_identity_fop_v<FOP>)
{
parg = as_tensor<ARG>(&XE(0, qx, qy, qz, e));
if (input_dep[i])
{
targ = as_tensor<ARG>(&XEd(0, qx, qy, qz, e));
}
else
{
targ = ARG{};
}
}
else if constexpr (is_weight_fop_v<FOP>)
{
parg = XE(qx, qy, qz, 0, 0);
targ = real_t(0.0);
}
else if constexpr (is_value_fop_v<FOP> ||
is_gradient_fop_v<FOP>)
{
parg = backend_t::template qp_pull<ARG>(
get<i>(rargs), qx, qy, qz);
if (input_dep[i])
{
targ = backend_t::template qp_pull<ARG>(
get<i>(sargs), qx, qy, qz);
}
else
{
targ = ARG{};
}
}
else
{
static_assert(false, "Unsupported");
}
});
// --------------------------------------
// Call the quadrature function
// --------------------------------------
call_enzyme_fwddiff(qfunc, primal_args, shadow_args);
// --------------------------------------
// Pushing arguments from enzyme_shadow tuple to registers
// --------------------------------------
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value, o = n_inputs + i;
const auto &qout = get<o>(shadow_args);
auto &YE = out_YE[i];
using FOP = tuple_element_t<i, outputs_t>;
using ARG =
typename qf_param_slot<qfunc_t, o>::qf_reg_param_t;
if constexpr (is_identity_fop_v<FOP>)
{
as_tensor<ARG>(&YE(0, qx, qy, qz, e)) = qout;
}
else if constexpr (is_value_fop_v<FOP> ||
is_gradient_fop_v<FOP>)
{
auto &rarg = get<o>(rargs);
backend_t::template qp_push_tangent<ARG>(
rarg, qx, qy, qz, qout);
}
else
{
static_assert(false, "Unsupported");
}
});
#else // MFEM_USE_ENZYME
args_tuple_t qargs;
// --------------------------------------
// Pulling arguments from registers to qargs tuple
// --------------------------------------
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
auto &qarg = get<i>(qargs);
const auto &XE = in_XE[i];
const auto &XEd = in_XE_dir[i];
using FOP = tuple_element_t<i, inputs_t>;
using ARG =
typename qf_param_slot<qfunc_t, i>::qf_reg_param_t;
if constexpr (is_identity_fop_v<FOP>)
{
using DT =
typename qf_param_slot<qfunc_t, i>::qf_decay_param_t;
if constexpr (qf_param_uses_dual_v<DT>)
{
qarg = backend_t::template identity_qp_pull_dual<DT>(
input_dep[i], XE, XEd, qx, qy, qz, e);
}
else
{
qarg = as_tensor<ARG>(&XE(0, qx, qy, qz, e));
}
}
else if constexpr (is_weight_fop_v<FOP>)
{
qarg = XE(qx, qy, qz, 0, 0);
}
else if constexpr (is_value_fop_v<FOP> ||
is_gradient_fop_v<FOP>)
{
qarg = backend_t::template qp_pull_directional<ARG>(
get<i>(rargs),
get<i>(sargs),
qx,
qy,
qz,
input_dep[i]);
}
else
{
static_assert(false, "Unsupported");
}
});
// --------------------------------------
// Call the quadrature function
// --------------------------------------
call_qfunc_no_move(qfunc, qargs);
// --------------------------------------
// Pushing arguments from qargs tuple to registers
// --------------------------------------
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value, o = n_inputs + i;
const auto &qarg = get<o>(qargs);
const auto &YE = out_YE[i];
using FOP = tuple_element_t<i, outputs_t>;
using ARG =
typename qf_param_slot<qfunc_t, o>::qf_reg_param_t;
if constexpr (is_identity_fop_v<FOP>)
{
using DT =
typename qf_param_slot<qfunc_t, o>::qf_decay_param_t;
if constexpr (qf_param_uses_dual_v<DT>)
{
backend_t::identity_qp_write_tangent(
YE, qx, qy, qz, e, qarg);
}
else
{
as_tensor<ARG>(&YE(0, qx, qy, qz, e)) = qarg;
}
}
else if constexpr (is_value_fop_v<FOP> ||
is_gradient_fop_v<FOP>)
{
auto &rarg = get<o>(rargs);
backend_t::template qp_push_tangent<ARG>(
rarg, qx, qy, qz, qarg);
}
else
{
static_assert(false, "Unsupported");
}
});
#endif // MFEM_USE_ENZYME
}
}
}
MFEM_SYNC_THREAD;
// -----------------------------------------------
// Integrate outputs
// -----------------------------------------------
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value, o = n_inputs + i;
const int d = out_d1d[i], q = out_q1d[i];
const auto B = out_B[i], G = out_G[i];
auto &YE = out_YE[i];
auto &rarg = get<o>(rargs);
using FOP = tuple_element_t<i, outputs_t>;
if constexpr (is_value_fop_v<FOP>)
{
backend_t::WriteValue(smem, e, d, q, q1d, B, YE, rarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
using YE_t = decltype(YE);
using rarg_t = decltype(rarg);
using qf_param_t =
typename qf_param_slot<qfunc_t, o>::qf_decay_param_t;
constexpr auto RNK = qf_param_slot<qfunc_t, o>::extents.size();
backend_t::template WriteGradient<RNK, rarg_t, YE_t, qf_param_t>(
smem, e, d, q, q1d, B, G, YE, rarg);
}
else if constexpr (is_identity_fop_v<FOP>)
{
// nothing to do
}
else
{
static_assert(false, "Unsupported");
}
});
},
ne,
backend_t::thread_blocks(compute_kernel_thread_1d<inputs_t, outputs_t>(
q1d, in_d1d, out_d1d)),
0,
nullptr);
}
using DerivativeKernelType =
decltype(&DerivativeAction::derivative_action_callback<>);
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeActionLO,
DerivativeKernelType,
(int, int) );
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeActionHO,
DerivativeKernelType,
(int, int) );
};
// Low Order kernels
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeKernelType
DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeActionLO::Kernel()
{
static_assert((DIM == 2 || DIM == 3) && Q1D <= 8);
using derivative_action_t =
DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>;
return derivative_action_t::template derivative_action_callback<
LocalQFLOBackend<DIM, Q1D>>;
}
// Low Order fallback
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeKernelType
DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeActionLO::Fallback(int dim, int q1d)
{
using derivative_action_t =
DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeActionLO =
typename derivative_action_t::DerivativeActionLO;
if (dim == 2)
{
return DispatchLOKernelByQ1D<DerivativeActionLO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchLOKernelByQ1D<DerivativeActionLO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
// High Order kernels
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeKernelType
DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeActionHO::Kernel()
{
using derivative_action_t =
DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>;
return derivative_action_t::
template derivative_action_callback<LocalQFHOBackend<DIM>, Q1D>;
}
// High Order fallback
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeKernelType
DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeActionHO::Fallback(int dim, int q1d)
{
using derivative_action_t =
DerivativeAction<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeActionHO = typename derivative_action_t::DerivativeActionHO;
if (dim == 2)
{
return DispatchHOKernelByQ1D<DerivativeActionHO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchHOKernelByQ1D<DerivativeActionHO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
} // namespace mfem::future::LocalQFImpl
@@ -0,0 +1,576 @@
// 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 "../../integrator_ctx.hpp"
#include "kernels.hpp"
#include "util.hpp"
#include <array>
namespace mfem::future::LocalQFImpl
{
// Cached Jacobian apply: J·v from qp_cache filled by DerivativeSetup
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
class DerivativeApply
{
static constexpr auto inout_tuple =
merge_mfem_tuples_as_empty_std_tuple(inputs_t {}, outputs_t{});
static constexpr auto filtered_inout_tuple = filter_fields(inout_tuple);
static constexpr size_t nfields =
count_unique_field_ids(filtered_inout_tuple);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
static_assert(n_inputs + n_outputs == tuple_size<qf_param_ts>::value,
"LocalQF: q-function arity must match inputs + outputs");
const inputs_t inputs;
const outputs_t outputs;
const IntegratorContext ctx;
const Vector &qp_cache;
const std::vector<const DofToQuad *> dtqs;
// inputs: dtq, idx, B, G, d1d, q1d, vdim
const std::array<DofToQuadMap, n_inputs> input_dtq;
const std::array<size_t, n_inputs> input_idx;
const std::array<const real_t *, n_inputs> input_B, input_G;
const std::array<int, n_inputs> input_d1d, input_q1d, input_vdim;
// outputs: dtq, idx, B, G, d1d, q1d, vdim
const std::array<DofToQuadMap, n_outputs> output_dtq;
const std::array<size_t, n_outputs> output_idx;
const std::array<const real_t *, n_outputs> output_B, output_G;
const std::array<int, n_outputs> output_d1d, output_q1d, output_vdim;
// Jacobian cache metadata
const std::array<bool, n_inputs> input_is_dependent;
const std::array<int, n_inputs> input_size_on_qp;
const std::array<int, n_outputs> out_op_dim;
const std::array<int, n_outputs> out_offsets;
const int output_size_on_qp;
const int trial_vdim;
const int total_trial_op_dim;
const int residual_size_on_qp;
// other constants
const int dim, ne, nq, q1d;
FieldDescriptor direction_fd;
mutable Vector direction_e;
public:
DerivativeApply() = delete;
DerivativeApply(IntegratorContext ctx,
qfunc_t /*qfunc*/,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache_in):
inputs(inputs), outputs(outputs), ctx(ctx), qp_cache(qp_cache_in),
dtqs(make_dtqs(ctx)), input_dtq(create_dtq_maps<Entity::Element>(
inputs,
dtqs,
create_union_field_map_for_dtq(ctx, inputs),
ctx.unionfds,
ctx.ir)),
input_idx(create_input_vector_map(ctx, inputs)),
input_B(get_B(input_dtq)), input_G(get_G(input_dtq)),
input_d1d(get_D1D(input_dtq)), input_q1d(get_Q1D(input_dtq)),
input_vdim(get_vdim(inputs)),
output_dtq(create_dtq_maps<Entity::Element>(
outputs,
dtqs,
create_union_field_map_for_dtq(ctx, outputs),
ctx.unionfds,
ctx.ir)),
output_idx(create_output_vector_map(ctx, outputs)),
output_B(get_B(output_dtq)), output_G(get_G(output_dtq)),
output_d1d(get_D1D(output_dtq)), output_q1d(get_Q1D(output_dtq)),
output_vdim(get_vdim(outputs)),
input_is_dependent(compute_input_is_dependent(inputs, derivative_id)),
input_size_on_qp(
get_input_size_on_qp(inputs, std::make_index_sequence<n_inputs> {})),
out_op_dim(compute_out_op_dim(outputs)),
out_offsets(compute_out_offsets(output_vdim, out_op_dim)),
output_size_on_qp(
[&]
{
int s = 0;
for_constexpr<n_outputs>([&](auto o)
{ s += get<o>(outputs).size_on_qp; });
return s;
}()),
trial_vdim(compute_trial_vdim(inputs, derivative_id)),
total_trial_op_dim(compute_total_trial_op_dim(
inputs, input_is_dependent, input_size_on_qp)),
residual_size_on_qp(output_size_on_qp * trial_vdim * total_trial_op_dim),
dim(ctx.mesh.Dimension()), ne(ctx.nentities), nq(ctx.ir.GetNPoints()),
q1d(tensor_1d_size(nq, dim))
{
MFEM_ASSERT(ctx.unionfds.size() == nfields,
"LocalQFBackend: unionfds size mismatch");
int direction_field_idx = -1;
for (size_t uf = 0; uf < nfields; uf++)
{
if (static_cast<int>(ctx.unionfds[uf].id) == derivative_id)
{
direction_field_idx = static_cast<int>(uf);
break;
}
}
MFEM_ASSERT(direction_field_idx != -1,
"DerivativeApply: derivative direction field not found");
direction_fd = ctx.unionfds[static_cast<size_t>(direction_field_idx)];
}
//////////////////////////////////////////////////////////////////
template<typename Backend>
void run_kernels(std::vector<Vector *> &ye) const
{
Backend::Run(dim,
q1d,
ctx,
qp_cache,
// inputs
input_idx,
input_B,
input_G,
input_vdim,
input_d1d,
input_q1d,
input_size_on_qp,
input_is_dependent,
// outputs
output_idx,
output_B,
output_G,
output_vdim,
output_d1d,
output_q1d,
out_op_dim,
out_offsets,
trial_vdim,
total_trial_op_dim,
residual_size_on_qp,
output_size_on_qp,
direction_e,
ye,
// fallback arguments
dim,
q1d);
}
//////////////////////////////////////////////////////////////////
void operator()(const std::vector<Vector *> &,
const Vector *direction_l,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
MFEM_ASSERT(direction_l != nullptr,
"LocalQF DerivativeApply: direction vector is null");
restriction<Entity::Element>(direction_fd,
*direction_l,
direction_e,
ElementDofOrdering::LEXICOGRAPHIC);
if (q1d <= LocalQFLOBackendMQ1())
{
run_kernels<DerivativeApplyLO>(ye);
}
else if (q1d <= LocalQFHOBackendMQ1())
{
run_kernels<DerivativeApplyHO>(ye);
}
else
{
MFEM_ABORT("Unsupported quadrature order for LocalQF backend");
}
}
//////////////////////////////////////////////////////////////////
template<typename backend_t = LocalQFLOBackend<3>, int T_Q1D = 0>
static void
derivative_apply_callback(const IntegratorContext &ctx,
const Vector &qp_cache,
// inputs: idx, B, G, vdim, d1d, q1d
const std::array<size_t, n_inputs> & /*in_idx*/,
const std::array<const real_t *, n_inputs> in_B,
const std::array<const real_t *, n_inputs> in_G,
const std::array<int, n_inputs> &in_vdim,
const std::array<int, n_inputs> &in_d1d,
const std::array<int, n_inputs> &in_q1d,
const std::array<int, n_inputs> &in_size_on_qp,
const std::array<bool, n_inputs> &input_dep,
// outputs: idx, B, G, vdim, d1d, q1d
const std::array<size_t, n_outputs> &out_idx,
const std::array<const real_t *, n_outputs> out_B,
const std::array<const real_t *, n_outputs> out_G,
const std::array<int, n_outputs> &out_vdim,
const std::array<int, n_outputs> &out_d1d,
const std::array<int, n_outputs> &out_q1d,
const std::array<int, n_outputs> &out_op_dim,
const std::array<int, n_outputs> &out_offsets,
const int trial_vdim,
const int total_trial_op_dim,
const int residual_size_on_qp,
const int output_size_on_qp,
const Vector &direction_e,
std::vector<Vector *> &ye,
// fallback arguments
const int dim,
const int q1d)
{
MFEM_VERIFY(dim == ctx.mesh.Dimension(), "Dimension mismatch");
if (ctx.attr.Size() == 0) { return; }
static constexpr auto B2D = backend_t::DIM == 2;
static constexpr auto MQ1 = T_Q1D ? T_Q1D : backend_t::MQ1;
static constexpr auto MTPB = backend_t::MAX_THREADS_PER_BLOCK();
const int ne = ctx.nentities;
const int nq = ctx.ir.GetNPoints();
MFEM_CONTRACT_VAR(output_size_on_qp);
constexpr auto k_dim = [](const int k) { return k * k * (B2D ? 1 : k); };
// --------------------------------------------------
// DIRECTION (trial): XE_dir for the dependent inputs
// --------------------------------------------------
const auto d_direction = direction_e.Read();
std::array<DeviceTensor<3 + 1 + 1, const real_t>, n_inputs> in_XE_dir;
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const int d = in_d1d[i], q = in_q1d[i], v = in_vdim[i];
using FOP = tuple_element_t<i, inputs_t>;
if (!input_dep[i]) { return; }
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
in_XE_dir[i] = Reshape(d_direction, d, d, B2D ? 1 : d, v, ne);
}
else if constexpr (is_identity_fop_v<FOP>)
{
in_XE_dir[i] = Reshape(d_direction, v, q, q, B2D ? 1 : q, ne);
}
else if constexpr (is_weight_fop_v<FOP>) { /* never a direction */ }
else
{
static_assert(false, "Unsupported");
}
});
// --------------------------------------------------
// OUTPUTS: YE, 3(max DIM) + 1(VDIM) + 1(number of elements)
// --------------------------------------------------
std::array<DeviceTensor<3 + 1 + 1, real_t>, n_outputs> out_YE;
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const size_t k = out_idx[i];
const int d = out_d1d[i], q = out_q1d[i], v = out_vdim[i];
using FOP = tuple_element_t<i, outputs_t>;
if constexpr (is_gradient_fop_v<FOP> || is_value_fop_v<FOP>)
{
MFEM_VERIFY(ye[k]->Size() == k_dim(d) * v * ne, "Size mismatch");
out_YE[i] = Reshape(ye[k]->ReadWrite(), d, d, B2D ? 1 : d, v, ne);
}
else if constexpr (is_identity_fop_v<FOP>)
{
MFEM_VERIFY(ye[k]->Size() == k_dim(q) * v * ne, "Size mismatch");
out_YE[i] = Reshape(ye[k]->ReadWrite(), v, q, q, B2D ? 1 : q, ne);
}
else
{
static_assert(false, "Unsupported FieldOperator");
}
});
auto cache_tensor = DeviceTensor<3, const real_t>(
qp_cache.Read(), residual_size_on_qp, nq, ne);
const auto d_attr = ctx.attr.Read();
const bool has_attr = ctx.attr.Size() > 0;
const auto d_elem_attr = ctx.elem_attr->Read();
dfem::forall<MTPB>(
[=] MFEM_HOST_DEVICE(const int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
// -----------------------------------------------
// Output integration registers, trial direction (shadow) registers
// and shared memory.
// -----------------------------------------------
args_reg_t<backend_t, qfunc_t, inputs_t, outputs_t, MQ1> rargs;
input_args_reg_t<backend_t, qfunc_t, inputs_t, outputs_t, MQ1> sargs;
MFEM_SHARED typename backend_t::Shared smem;
// -----------------------------------------------
// Load trial direction (sargs) for the dependent inputs
// -----------------------------------------------
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
if (!input_dep[i]) { return; }
const auto &XE = in_XE_dir[i];
const int d = in_d1d[i], q = in_q1d[i], Q1D = q1d;
const real_t *B = in_B[i], *G = in_G[i];
auto &sarg = get<i>(sargs);
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop<FOP>::value)
{
backend_t::LoadValue(smem, e, d, q, Q1D, B, XE, sarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
constexpr auto RNK = qf_param_slot<qfunc_t, i>::extents.size();
using FieldParamT =
typename qf_param_slot<qfunc_t, i>::qf_decay_param_t;
backend_t::template LoadGradient<RNK,
decltype(sarg),
decltype(XE),
FieldParamT>(
smem, e, d, q, Q1D, B, G, XE, sarg);
}
else if constexpr (is_identity_fop_v<FOP> || is_weight_fop_v<FOP>)
{
// identity read at qp; weight is never a trial direction
}
else
{
static_assert(false, "Unsupported");
}
});
MFEM_SYNC_THREAD;
// -----------------------------------------------
// Contract the cached Jacobian with the trial direction at each
// quadrature point and push the result into the test registers.
// -----------------------------------------------
MFEM_FOREACH_THREAD(qz, z, (B2D ? 1 : q1d))
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
for_constexpr<n_outputs>([&](auto oc)
{
constexpr size_t o = oc.value, ao = n_inputs + o;
using FOP = tuple_element_t<o, outputs_t>;
using ARG =
typename qf_param_slot<qfunc_t, ao>::qf_reg_param_t;
const int tv = out_vdim[o], to = out_op_dim[o];
ARG fhat{};
for (int i = 0; i < tv; i++)
{
for (int k = 0; k < to; k++)
{
const int row = out_offsets[o] + i * to + k;
const int cache_row =
row * trial_vdim * total_trial_op_dim;
real_t sum = 0.0;
int m_offset = 0;
for_constexpr<n_inputs>([&](auto sc)
{
constexpr size_t s = sc.value;
if (!input_dep[s]) { return; }
using SARG =
typename qf_param_slot<qfunc_t,
s>::qf_reg_param_t;
const int vdim_s = in_vdim[s];
const int op_dim_s = in_size_on_qp[s] / vdim_s;
const auto dvec =
backend_t::template qp_pull<SARG>(
get<s>(sargs), qx, qy, qz);
for (int j = 0; j < trial_vdim; j++)
{
for (int m = 0; m < op_dim_s; m++)
{
const int cache_idx =
cache_row + j * total_trial_op_dim +
(m + m_offset);
sum += cache_tensor(cache_idx, q, e) *
qf_flat_value(dvec, j + vdim_s * m);
}
}
m_offset += op_dim_s;
});
qf_set_flat_value(fhat, i + tv * k, sum);
}
}
auto &YE = out_YE[o];
if constexpr (is_identity_fop_v<FOP>)
{
for (int i = 0; i < tv; i++)
{
for (int k = 0; k < to; k++)
{
YE(i + tv * k, qx, qy, qz, e) =
qf_flat_value(fhat, i + tv * k);
}
}
}
else
{
backend_t::template qp_push<ARG>(
get<ao>(rargs), qx, qy, qz, fhat);
}
});
}
}
}
MFEM_SYNC_THREAD;
// -----------------------------------------------
// Integrate value / gradient outputs to the test dofs
// -----------------------------------------------
for_constexpr<n_outputs>([&](auto ic)
{
constexpr size_t i = ic.value, o = n_inputs + i;
const int d = out_d1d[i], q = out_q1d[i], Q1D = q1d;
const auto B = out_B[i], G = out_G[i];
auto &YE = out_YE[i];
auto &rarg = get<o>(rargs);
using FOP = tuple_element_t<i, outputs_t>;
if constexpr (is_value_fop_v<FOP>)
{
backend_t::WriteValue(smem, e, d, q, Q1D, B, YE, rarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
using YE_t = decltype(YE);
using rarg_t = decltype(rarg);
using qf_param_t =
typename qf_param_slot<qfunc_t, o>::qf_decay_param_t;
constexpr auto RNK = qf_param_slot<qfunc_t, o>::extents.size();
backend_t::template WriteGradient<RNK, rarg_t, YE_t, qf_param_t>(
smem, e, d, q, Q1D, B, G, YE, rarg);
}
else if constexpr (is_identity_fop_v<FOP>) { /* written at qp */ }
else
{
static_assert(false, "Unsupported");
}
});
},
ne,
backend_t::thread_blocks(compute_kernel_thread_1d<inputs_t, outputs_t>(
q1d, in_d1d, out_d1d)),
0,
nullptr);
}
using ApplyKernelType =
decltype(&DerivativeApply::derivative_apply_callback<>);
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeApplyLO,
ApplyKernelType,
(int, int) );
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeApplyHO,
ApplyKernelType,
(int, int) );
};
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>::
ApplyKernelType
DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeApplyLO::Kernel()
{
static_assert((DIM == 2 || DIM == 3) && Q1D <= 8);
using apply_t = DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>;
return apply_t::template derivative_apply_callback<
LocalQFLOBackend<DIM, Q1D>>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>::
ApplyKernelType
DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeApplyLO::Fallback(int dim, int q1d)
{
using apply_t = DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeApplyLO = typename apply_t::DerivativeApplyLO;
if (dim == 2)
{
return DispatchLOKernelByQ1D<DerivativeApplyLO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchLOKernelByQ1D<DerivativeApplyLO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>::
ApplyKernelType
DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeApplyHO::Kernel()
{
using apply_t = DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>;
return apply_t::template derivative_apply_callback<LocalQFHOBackend<DIM>,
Q1D>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>::
ApplyKernelType
DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeApplyHO::Fallback(int dim, int q1d)
{
using apply_t = DerivativeApply<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeApplyHO = typename apply_t::DerivativeApplyHO;
if (dim == 2)
{
return DispatchHOKernelByQ1D<DerivativeApplyHO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchHOKernelByQ1D<DerivativeApplyHO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
} // namespace mfem::future::LocalQFImpl
@@ -0,0 +1,617 @@
// 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 "../../integrator_ctx.hpp"
#include "kernels.hpp"
#include "util.hpp"
#include <array>
namespace mfem::future::LocalQFImpl
{
// Cached transposed Jacobian apply: Jᵀ·w from the qp_cache
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
class DerivativeApplyTranspose
{
static constexpr auto inout_tuple =
merge_mfem_tuples_as_empty_std_tuple(inputs_t {}, outputs_t{});
static constexpr auto filtered_inout_tuple = filter_fields(inout_tuple);
static constexpr size_t nfields =
count_unique_field_ids(filtered_inout_tuple);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
static_assert(n_inputs + n_outputs == tuple_size<qf_param_ts>::value,
"LocalQF: q-function arity must match inputs + outputs");
// Input tuple slot referencing the derivative field (compile-time)
static constexpr size_t deriv_input_idx_ct = []() constexpr
{
size_t idx = SIZE_MAX;
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
using FOP = tuple_element_t<i, inputs_t>;
if (FOP::GetFieldId() == derivative_id) { idx = i; }
});
return idx;
}();
static_assert(deriv_input_idx_ct < n_inputs,
"DerivativeApplyTranspose: derivative input slot not found");
const inputs_t inputs;
const outputs_t outputs;
const IntegratorContext ctx;
const Vector &qp_cache; // Jacobian cache from DerivativeSetup
const std::vector<const DofToQuad *> dtqs;
// inputs: dtq, B, G, d1d, q1d, vdim (trial / derivative fields)
const std::array<DofToQuadMap, n_inputs> input_dtq;
const std::array<const real_t *, n_inputs> input_B, input_G;
const std::array<int, n_inputs> input_d1d, input_q1d, input_vdim;
// outputs: dtq, idx, B, G, d1d, q1d, vdim (test / cotangent fields)
const std::array<DofToQuadMap, n_outputs> output_dtq;
const std::array<size_t, n_outputs> output_idx;
const std::array<const real_t *, n_outputs> output_B, output_G;
const std::array<int, n_outputs> output_d1d, output_q1d, output_vdim;
// Jacobian cache metadata
const std::array<bool, n_inputs> input_is_dependent;
const std::array<int, n_inputs> input_size_on_qp;
const std::array<int, n_outputs> out_op_dim;
const std::array<int, n_outputs> out_offsets;
const int output_size_on_qp;
const int trial_vdim;
const int total_trial_op_dim;
const int residual_size_on_qp;
// other constants
const int dim, ne, nq, q1d;
const size_t deriv_infd_idx; // index of the derivative field in ye
// output cotangent restriction workspace (blocked by element)
std::array<int, n_outputs> out_elem_dof_size;
mutable Vector dir_out_e;
public:
//////////////////////////////////////////////////////////////////
DerivativeApplyTranspose() = delete;
DerivativeApplyTranspose(IntegratorContext ctx,
qfunc_t /*qfunc*/,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache):
inputs(inputs), outputs(outputs), ctx(ctx), qp_cache(qp_cache),
dtqs(make_dtqs(ctx)), input_dtq(create_dtq_maps<Entity::Element>(
inputs,
dtqs,
create_union_field_map_for_dtq(ctx, inputs),
ctx.unionfds,
ctx.ir)),
input_B(get_B(input_dtq)), input_G(get_G(input_dtq)),
input_d1d(get_D1D(input_dtq)), input_q1d(get_Q1D(input_dtq)),
input_vdim(get_vdim(inputs)),
output_dtq(create_dtq_maps<Entity::Element>(
outputs,
dtqs,
create_union_field_map_for_dtq(ctx, outputs),
ctx.unionfds,
ctx.ir)),
output_idx(create_output_vector_map(ctx, outputs)),
output_B(get_B(output_dtq)), output_G(get_G(output_dtq)),
output_d1d(get_D1D(output_dtq)), output_q1d(get_Q1D(output_dtq)),
output_vdim(get_vdim(outputs)),
input_is_dependent(compute_input_is_dependent(inputs, derivative_id)),
input_size_on_qp(
get_input_size_on_qp(inputs, std::make_index_sequence<n_inputs> {})),
out_op_dim(compute_out_op_dim(outputs)),
out_offsets(compute_out_offsets(output_vdim, out_op_dim)),
output_size_on_qp(
[&]
{
int s = 0;
for_constexpr<n_outputs>([&](auto o)
{ s += get<o>(outputs).size_on_qp; });
return s;
}()),
trial_vdim(compute_trial_vdim(inputs, derivative_id)),
total_trial_op_dim(compute_total_trial_op_dim(
inputs, input_is_dependent, input_size_on_qp)),
residual_size_on_qp(output_size_on_qp * trial_vdim * total_trial_op_dim),
dim(ctx.mesh.Dimension()), ne(ctx.nentities), nq(ctx.ir.GetNPoints()),
q1d(tensor_1d_size(nq, dim)),
deriv_infd_idx(find_infd_index(ctx, derivative_id)), out_elem_dof_size{}
{
MFEM_ASSERT(ctx.unionfds.size() == nfields,
"LocalQFBackend: unionfds size mismatch");
MFEM_ASSERT(
deriv_infd_idx != SIZE_MAX,
"DerivativeApplyTranspose: derivative field not found in infds");
// Size the workspace that holds the output cotangent(s) in element
// layout.
int total_dir_e_size = 0;
for_constexpr<n_outputs>([&](auto o)
{
const int elem_sz = compute_element_dof_sz(
ctx.outfds[output_idx[o]], ne, ElementDofOrdering::LEXICOGRAPHIC);
out_elem_dof_size[o] = elem_sz;
total_dir_e_size += elem_sz;
});
dir_out_e.SetSize(total_dir_e_size * ne);
dir_out_e.UseDevice(true);
dir_out_e.Read();
}
//////////////////////////////////////////////////////////////////
template<typename Backend>
void run_kernels(std::vector<Vector *> &ye) const
{
Backend::Run(dim,
q1d,
ctx,
qp_cache,
dir_out_e,
// inputs (integration target metadata)
input_B,
input_G,
input_vdim,
input_d1d,
input_q1d,
input_size_on_qp,
input_is_dependent,
// outputs (direction interpolation metadata)
output_B,
output_G,
output_vdim,
output_d1d,
output_q1d,
out_op_dim,
out_offsets,
trial_vdim,
total_trial_op_dim,
residual_size_on_qp,
output_size_on_qp,
deriv_infd_idx,
ye,
// fallback arguments
dim,
q1d);
}
//////////////////////////////////////////////////////////////////
void operator()(const std::vector<Vector *> & /*xe*/,
const Vector *direction_l,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
MFEM_ASSERT(direction_l != nullptr,
"LocalQF DerivativeApplyTranspose: direction vector is null");
// Restrict output cotangent from L-vectors into element layout
// (dir_out_e).
int l_offset = 0;
int e_offset = 0;
for_constexpr<n_outputs>([&](auto o)
{
const size_t outfd = output_idx[o];
const auto &fd = ctx.outfds[outfd];
const int l_size = GetVSize(fd);
Vector dir_o_l(*const_cast<Vector *>(direction_l), l_offset, l_size);
dir_o_l.UseDevice(true);
const int elem_sz = out_elem_dof_size[o];
Vector dir_o_e(dir_out_e, e_offset, elem_sz * ne);
dir_o_e.UseDevice(true);
restriction<Entity::Element>(
fd, dir_o_l, dir_o_e, ElementDofOrdering::LEXICOGRAPHIC);
l_offset += l_size;
e_offset += elem_sz * ne;
});
if (q1d <= LocalQFLOBackendMQ1())
{
run_kernels<DerivativeApplyTransposeLO>(ye);
}
else if (q1d <= LocalQFHOBackendMQ1())
{
run_kernels<DerivativeApplyTransposeHO>(ye);
}
else
{
MFEM_ABORT("Unsupported quadrature order for LocalQF backend");
}
}
//////////////////////////////////////////////////////////////////
template<typename backend_t = LocalQFLOBackend<3>, int T_Q1D = 0>
static void derivative_apply_transpose_callback(
const IntegratorContext &ctx,
const Vector &qp_cache,
const Vector &dir_e, // restricted, concatenated output cotangents
// inputs (integration target metadata)
const std::array<const real_t *, n_inputs> in_B,
const std::array<const real_t *, n_inputs> in_G,
const std::array<int, n_inputs> &in_vdim,
const std::array<int, n_inputs> &in_d1d,
const std::array<int, n_inputs> &in_q1d,
const std::array<int, n_inputs> &in_size_on_qp,
const std::array<bool, n_inputs> &input_dep,
// outputs (direction interpolation metadata)
const std::array<const real_t *, n_outputs> out_B,
const std::array<const real_t *, n_outputs> out_G,
const std::array<int, n_outputs> &out_vdim,
const std::array<int, n_outputs> &out_d1d,
const std::array<int, n_outputs> &out_q1d,
const std::array<int, n_outputs> &out_op_dim,
const std::array<int, n_outputs> &out_offsets,
const int trial_vdim,
const int total_trial_op_dim,
const int residual_size_on_qp,
const int output_size_on_qp,
const size_t deriv_infd_idx,
std::vector<Vector *> &ye,
// fallback arguments
const int dim,
const int q1d)
{
MFEM_VERIFY(dim == ctx.mesh.Dimension(), "Dimension mismatch");
if (ctx.attr.Size() == 0) { return; }
static constexpr auto B2D = backend_t::DIM == 2;
static constexpr auto MQ1 = T_Q1D ? T_Q1D : backend_t::MQ1;
static constexpr auto MTPB = backend_t::MAX_THREADS_PER_BLOCK();
const int ne = ctx.nentities;
const int nq = ctx.ir.GetNPoints();
MFEM_CONTRACT_VAR(output_size_on_qp);
MFEM_CONTRACT_VAR(in_q1d);
constexpr auto k_dim = [](const int k) { return k * k * (B2D ? 1 : k); };
// --------------------------------------------------
// DIRECTION (test cotangent): out_XE_dir, concatenated per output
// --------------------------------------------------
const auto d_dir = dir_e.Read();
std::array<DeviceTensor<3 + 1 + 1, const real_t>, n_outputs> out_XE_dir;
int e_offset = 0;
for_constexpr<n_outputs>([&](auto oc)
{
constexpr size_t o = oc.value;
const int d = out_d1d[o], q = out_q1d[o], v = out_vdim[o];
using FOP = tuple_element_t<o, outputs_t>;
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
out_XE_dir[o] = Reshape(d_dir + e_offset, d, d, B2D ? 1 : d, v, ne);
e_offset += k_dim(d) * v * ne;
}
else if constexpr (is_identity_fop_v<FOP>)
{
out_XE_dir[o] = Reshape(d_dir + e_offset, v, q, q, B2D ? 1 : q, ne);
e_offset += k_dim(q) * v * ne;
}
else
{
static_assert(false, "Unsupported");
}
});
// --------------------------------------------------
// DERIVATIVE TRIAL FIELD: ye_XE (accumulates Jᵀ w)
// --------------------------------------------------
const int d_in = in_d1d[deriv_input_idx_ct];
const int v_in = in_vdim[deriv_input_idx_ct];
auto ye_XE = Reshape(
ye[deriv_infd_idx]->ReadWrite(), d_in, d_in, B2D ? 1 : d_in, v_in, ne);
auto cache_tensor = DeviceTensor<3, const real_t>(
qp_cache.Read(), residual_size_on_qp, nq, ne);
const auto d_attr = ctx.attr.Read();
const bool has_attr = ctx.attr.Size() > 0;
const auto d_elem_attr = ctx.elem_attr->Read();
dfem::forall<MTPB>(
[=] MFEM_HOST_DEVICE(const int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
// -----------------------------------------------
// Output cotangent (direction) registers live in the output slots;
// the trial integration data is pushed into the input slots.
// -----------------------------------------------
args_reg_t<backend_t, qfunc_t, inputs_t, outputs_t, MQ1> rargs;
MFEM_SHARED typename backend_t::Shared smem;
// -----------------------------------------------
// Interpolate the test cotangent to quadrature points (output slots)
// -----------------------------------------------
for_constexpr<n_outputs>([&](auto oc)
{
constexpr size_t o = oc.value, ao = n_inputs + o;
using FOP = tuple_element_t<o, outputs_t>;
const auto &XE = out_XE_dir[o];
const int d = out_d1d[o], q = out_q1d[o], Q1D = q1d;
const real_t *B = out_B[o], *G = out_G[o];
auto &oarg = get<ao>(rargs);
if constexpr (is_value_fop_v<FOP>)
{
backend_t::LoadValue(smem, e, d, q, Q1D, B, XE, oarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
constexpr auto RNK = qf_param_slot<qfunc_t, ao>::extents.size();
using FieldParamT =
typename qf_param_slot<qfunc_t, ao>::qf_decay_param_t;
backend_t::template LoadGradient<RNK,
decltype(oarg),
decltype(XE),
FieldParamT>(
smem, e, d, q, Q1D, B, G, XE, oarg);
}
else if constexpr (is_identity_fop_v<FOP>)
{
// identity cotangent is read directly at qp from out_XE_dir
}
else
{
static_assert(false, "Unsupported");
}
});
MFEM_SYNC_THREAD;
// -----------------------------------------------
// Contract the transposed cached Jacobian with the test cotangent at
// each quadrature point and push the trial result into the dependent
// input registers.
// -----------------------------------------------
MFEM_FOREACH_THREAD(qz, z, (B2D ? 1 : q1d))
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
int m_offset = 0;
for_constexpr<n_inputs>([&](auto sc)
{
constexpr size_t s = sc.value;
if (!input_dep[s]) { return; }
using SARG =
typename qf_param_slot<qfunc_t, s>::qf_reg_param_t;
const int vdim_s = in_vdim[s];
const int op_dim_s = in_size_on_qp[s] / vdim_s;
SARG fhat{};
for (int j = 0; j < trial_vdim; j++)
{
for (int m = 0; m < op_dim_s; m++)
{
const int col =
j * total_trial_op_dim + (m + m_offset);
real_t sum = 0.0;
for_constexpr<n_outputs>([&](auto oc)
{
constexpr size_t o = oc.value, ao = n_inputs + o;
using OFOP = tuple_element_t<o, outputs_t>;
using OARG =
typename qf_param_slot<qfunc_t,
ao>::qf_reg_param_t;
const int tv = out_vdim[o], to = out_op_dim[o];
const auto offset_o = out_offsets[o];
const auto &cache = cache_tensor;
if constexpr (is_value_fop_v<OFOP> ||
is_gradient_fop_v<OFOP>)
{
const auto wvec =
backend_t::template qp_pull<OARG>(
get<ao>(rargs), qx, qy, qz);
for (int i = 0; i < tv; i++)
{
for (int k = 0; k < to; k++)
{
const int row = offset_o + i * to + k;
const int cache_idx =
row * trial_vdim *
total_trial_op_dim +
col;
sum += cache(cache_idx, q, e) *
qf_flat_value(wvec, i + tv * k);
}
}
}
else if constexpr (is_identity_fop_v<OFOP>)
{
const auto &XEo = out_XE_dir[o];
for (int i = 0; i < tv; i++)
{
for (int k = 0; k < to; k++)
{
const int row = offset_o + i * to + k;
const int cache_idx =
row * trial_vdim *
total_trial_op_dim +
col;
sum += cache(cache_idx, q, e) *
XEo(i + tv * k, qx, qy, qz, e);
}
}
}
});
qf_set_flat_value(fhat, j + vdim_s * m, sum);
}
}
backend_t::template qp_push<SARG>(
get<s>(rargs), qx, qy, qz, fhat);
m_offset += op_dim_s;
});
}
}
}
MFEM_SYNC_THREAD;
// -----------------------------------------------
// Integrate the trial result into the derivative field dofs. Multiple
// dependent input slots (e.g. value and gradient of the same field)
// accumulate into ye_XE via the writers' '+=' semantics.
// -----------------------------------------------
for_constexpr<n_inputs>([&](auto sc)
{
constexpr size_t s = sc.value;
if (!input_dep[s]) { return; }
using FOP = tuple_element_t<s, inputs_t>;
const int d = in_d1d[s], q = in_q1d[s], Q1D = q1d;
const real_t *B = in_B[s], *G = in_G[s];
auto &sarg = get<s>(rargs);
auto &YE = ye_XE;
if constexpr (is_value_fop_v<FOP>)
{
backend_t::WriteValue(smem, e, d, q, Q1D, B, YE, sarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
using YE_t = decltype(YE);
using rarg_t = decltype(sarg);
using qf_param_t =
typename qf_param_slot<qfunc_t, s>::qf_decay_param_t;
constexpr auto RNK = qf_param_slot<qfunc_t, s>::extents.size();
backend_t::template WriteGradient<RNK, rarg_t, YE_t, qf_param_t>(
smem, e, d, q, Q1D, B, G, YE, sarg);
}
else
{
// identity / weight derivative targets are not produced here
}
});
},
ne,
backend_t::thread_blocks(compute_kernel_thread_1d<inputs_t, outputs_t>(
q1d, in_d1d, out_d1d)),
0,
nullptr);
}
using TransposeKernelType =
decltype(&DerivativeApplyTranspose::
derivative_apply_transpose_callback<>);
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeApplyTransposeLO,
TransposeKernelType,
(int, int) );
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeApplyTransposeHO,
TransposeKernelType,
(int, int) );
};
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeApplyTranspose<derivative_id,
qfunc_t,
inputs_t,
outputs_t>::TransposeKernelType
DerivativeApplyTranspose<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeApplyTransposeLO::Kernel()
{
static_assert((DIM == 2 || DIM == 3) && Q1D <= 8);
using transpose_t =
DerivativeApplyTranspose<derivative_id, qfunc_t, inputs_t, outputs_t>;
return transpose_t::template derivative_apply_transpose_callback<
LocalQFLOBackend<DIM, Q1D>>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeApplyTranspose<derivative_id,
qfunc_t,
inputs_t,
outputs_t>::TransposeKernelType
DerivativeApplyTranspose<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeApplyTransposeLO::Fallback(int dim, int q1d)
{
using transpose_t =
DerivativeApplyTranspose<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeApplyTransposeLO =
typename transpose_t::DerivativeApplyTransposeLO;
if (dim == 2)
{
return DispatchLOKernelByQ1D<DerivativeApplyTransposeLO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchLOKernelByQ1D<DerivativeApplyTransposeLO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeApplyTranspose<derivative_id,
qfunc_t,
inputs_t,
outputs_t>::TransposeKernelType
DerivativeApplyTranspose<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeApplyTransposeHO::Kernel()
{
using transpose_t =
DerivativeApplyTranspose<derivative_id, qfunc_t, inputs_t, outputs_t>;
return transpose_t::
template derivative_apply_transpose_callback<LocalQFHOBackend<DIM>, Q1D>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeApplyTranspose<derivative_id,
qfunc_t,
inputs_t,
outputs_t>::TransposeKernelType
DerivativeApplyTranspose<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeApplyTransposeHO::Fallback(int dim, int q1d)
{
using transpose_t =
DerivativeApplyTranspose<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeApplyTransposeHO =
typename transpose_t::DerivativeApplyTransposeHO;
if (dim == 2)
{
return DispatchHOKernelByQ1D<DerivativeApplyTransposeHO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchHOKernelByQ1D<DerivativeApplyTransposeHO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
} // namespace mfem::future::LocalQFImpl
@@ -0,0 +1,953 @@
// 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 "../../integrator_ctx.hpp"
#include "../../../kernels.hpp"
#include "kernels.hpp"
#include "util.hpp"
#include <array>
#include <type_traits>
namespace ker = mfem::kernels::internal;
namespace mfem::future::LocalQFImpl
{
namespace detail
{
template<int DIM>
MFEM_HOST_DEVICE inline int tensor_idx(int x, int y, int z, int N)
{
static_assert(DIM == 2 || DIM == 3);
if constexpr (DIM == 2) { assert(z == 0); }
return x + N * (y + N * z);
}
template<int DIM>
MFEM_HOST_DEVICE inline real_t
trial_basis_weight_value(const DeviceTensor<3, const real_t> &B,
const int qx,
const int qy,
const int qz,
const int Jx,
const int Jy,
const int Jz)
{
static_assert(DIM == 2 || DIM == 3);
return B(qx, 0, Jx) * B(qy, 0, Jy) * ((DIM == 3) ? B(qz, 0, Jz) : 1.0);
}
template<int DIM>
MFEM_HOST_DEVICE inline real_t
trial_basis_weight_gradient(const DeviceTensor<3, const real_t> &B,
const DeviceTensor<3, const real_t> &G,
const int m,
const int qx,
const int qy,
const int qz,
const int Jx,
const int Jy,
const int Jz)
{
const auto Gx = G(qx, 0, Jx), Gy = G(qy, 0, Jy);
const auto Bx = B(qx, 0, Jx), By = B(qy, 0, Jy);
if constexpr (DIM == 2)
{
MFEM_CONTRACT_VAR(qz & Jz);
return (m == 0) ? Gx * By : Bx * Gy;
}
else
{
const auto Bz = B(qz, 0, Jz), Gz = G(qz, 0, Jz);
return (m == 0) ? Gx * By * Bz
: (m == 1) ? Bx * Gy * Bz
: (m == 2) ? Bx * By * Gz
: (assert(false), 0.0);
}
}
template<int DIM, int MQ1, typename Shared, typename output_t>
MFEM_HOST_DEVICE void
map_quadrature_data_to_fields(DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq,
Shared &s,
const int tv_dof = -1)
{
using output_fop_t = std::decay_t<output_t>;
const auto B = dtq.B, G = dtq.G;
const bool f_slab = (tv_dof >= 0);
const int vdim = output.vdim;
const int vd_begin = f_slab ? tv_dof : 0;
const int vd_end = f_slab ? tv_dof + 1 : vdim;
if constexpr (is_value_fop_v<output_fop_t>)
{
const auto [q1d, unused, d1d] = B.GetShape();
MFEM_CONTRACT_VAR(unused);
const int test_dim = output.size_on_qp / vdim;
MFEM_CONTRACT_VAR(test_dim);
const int f_vdim = f_slab ? 1 : vdim;
if constexpr (DIM == 2)
{
const auto fqp = Reshape(&f(0, 0, 0), f_vdim, test_dim, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, vdim);
ker::LoadMatrix(d1d, q1d, B, s.B);
ker::s_regs2d_t<MQ1> r_qp, Y;
for (int vd = vd_begin; vd < vd_end; vd++)
{
const int fi = f_slab ? 0 : vd;
MFEM_FOREACH_THREAD(qy, y, q1d)
MFEM_FOREACH_THREAD(qx, x, q1d)
{ r_qp[qy][qx] = fqp(fi, 0, qx, qy); }
MFEM_SYNC_THREAD;
ker::Eval2d<MQ1, true>(d1d, q1d, s.M, s.B, r_qp, Y);
MFEM_FOREACH_THREAD(dy, y, d1d)
MFEM_FOREACH_THREAD(dx, x, d1d) { yd(dx, dy, vd) += Y[dy][dx]; }
MFEM_SYNC_THREAD;
}
}
else
{
const auto fqp = Reshape(&f(0, 0, 0), f_vdim, test_dim, q1d, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, d1d, vdim);
ker::LoadMatrix(d1d, q1d, B, s.B);
ker::s_regs3d_t<MQ1> f_qp, Y;
for (int vd = vd_begin; vd < vd_end; vd++)
{
const int fi = f_slab ? 0 : vd;
for (int qz = 0; qz < q1d; qz++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
MFEM_FOREACH_THREAD(qx, x, q1d)
{ f_qp[qz][qy][qx] = fqp(fi, 0, qx, qy, qz); }
}
MFEM_SYNC_THREAD;
ker::Eval3d<MQ1, true>(d1d, q1d, s.M, s.B, f_qp, Y);
for (int dz = 0; dz < d1d; dz++)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
MFEM_FOREACH_THREAD(dx, x, d1d)
{ yd(dx, dy, dz, vd) += Y[dz][dy][dx]; }
}
MFEM_SYNC_THREAD;
}
}
}
else if constexpr (is_gradient_fop_v<output_fop_t>)
{
const auto [q1d, unused, d1d] = G.GetShape();
MFEM_CONTRACT_VAR(unused);
const int test_dim = output.size_on_qp / vdim;
const int f_vdim = f_slab ? 1 : vdim;
if constexpr (DIM == 2)
{
const auto fqp = Reshape(&f(0, 0, 0), f_vdim, test_dim, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, vdim);
ker::LoadMatrix(d1d, q1d, B, s.B);
ker::LoadMatrix(d1d, q1d, G, s.G);
ker::vd_regs2d_t<1, DIM, MQ1> X, Y;
for (int vd = vd_begin; vd < vd_end; vd++)
{
const int fi = f_slab ? 0 : vd;
MFEM_FOREACH_THREAD(qx, x, q1d)
MFEM_FOREACH_THREAD(qy, y, q1d)
for (int k = 0; k < DIM; k++)
{
X[0][k][qy][qx] = fqp(fi, k, qx, qy);
}
MFEM_SYNC_THREAD;
ker::Grad2d<1, DIM, MQ1, true>(d1d, q1d, s.M, s.B, s.G, X, Y);
MFEM_FOREACH_THREAD(dy, y, d1d)
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t u = 0.0;
for (int k = 0; k < DIM; k++) { u += Y[0][k][dy][dx]; }
yd(dx, dy, vd) += u;
}
MFEM_SYNC_THREAD;
}
}
else
{
const auto fqp = Reshape(&f(0, 0, 0), f_vdim, test_dim, q1d, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, d1d, vdim);
ker::LoadMatrix(d1d, q1d, B, s.B);
ker::LoadMatrix(d1d, q1d, G, s.G);
ker::vd_regs3d_t<1, DIM, MQ1> X, Y;
for (int vd = vd_begin; vd < vd_end; vd++)
{
const int fi = f_slab ? 0 : vd;
for (int qz = 0; qz < q1d; qz++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
MFEM_FOREACH_THREAD(qx, x, q1d)
for (int k = 0; k < DIM; k++)
{
X[0][k][qz][qy][qx] = fqp(fi, k, qx, qy, qz);
}
}
MFEM_SYNC_THREAD;
ker::Grad3d<1, DIM, MQ1, true>(d1d, q1d, s.M, s.B, s.G, X, Y);
for (int dz = 0; dz < d1d; dz++)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t u = 0.0;
for (int k = 0; k < DIM; k++) { u += Y[0][k][dz][dy][dx]; }
yd(dx, dy, dz, vd) += u;
}
}
MFEM_SYNC_THREAD;
}
}
}
else if constexpr (is_identity_fop_v<output_fop_t>)
{
const auto [q1d, unused, d1d] = B.GetShape();
MFEM_CONTRACT_VAR(unused);
MFEM_CONTRACT_VAR(d1d);
const int f_sq = f_slab ? 1 : output.size_on_qp;
const int sq_begin = f_slab ? tv_dof : 0;
const int sq_end = f_slab ? tv_dof + 1 : output.size_on_qp;
if constexpr (DIM == 2)
{
const auto fqp = Reshape(&f(0, 0, 0), f_sq, q1d, q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d, q1d);
for (int sq = sq_begin; sq < sq_end; sq++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
MFEM_FOREACH_THREAD(qx, x, q1d)
{
int qz = 0;
MFEM_CONTRACT_VAR(qz);
yqp(sq, qx, qy) = fqp(0, qx, qy);
}
MFEM_SYNC_THREAD;
}
}
else
{
const auto fqp = Reshape(&f(0, 0, 0), f_sq, q1d, q1d, q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d, q1d, q1d);
for (int sq = sq_begin; sq < sq_end; sq++)
{
for (int qz = 0; qz < q1d; qz++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
MFEM_FOREACH_THREAD(qx, x, q1d)
{ yqp(sq, qx, qy, qz) = fqp(0, qx, qy, qz); }
}
MFEM_SYNC_THREAD;
}
}
}
else
{
MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented"
" for this field descriptor with sum factorization on"
" tensor product elements");
}
}
template<int DIM,
int MQ1,
typename Shared,
typename input_fop_ts,
std::size_t n_inputs,
typename output_fop_t>
MFEM_HOST_DEVICE void assemble_element_mat_sumfact(
const DeviceTensor<5, real_t> &Ae,
const DeviceTensor<6, const real_t> &qpdc,
const int e,
const DeviceTensor<1, const real_t> &itod,
const input_fop_ts &inputs,
const output_fop_t &output,
const std::array<DofToQuadMap, n_inputs> &input_dtq_maps,
const DofToQuadMap &output_dtq,
const int q1d,
const int num_trial_dof_1d,
Shared &smem)
{
static constexpr int MQN = (DIM == 2) ? MQ1 * MQ1 : MQ1 * MQ1 * MQ1;
// Slab must hold full (test_vdim, test_op_dim, nq) fhat
static constexpr int FHAT_SLAB_MAX = MQN * 4;
static constexpr bool grad_out = is_gradient_fop_v<output_fop_t>;
static constexpr bool ident_out = is_identity_fop_v<output_fop_t>;
const int test_vdim = qpdc.GetShape()[3];
const int test_op_dim = qpdc.GetShape()[2];
const int trial_vdim = qpdc.GetShape()[1];
const int num_test_dof = Ae.GetShape()[0];
const int nq = qpdc.GetShape()[4];
const int size_on_qp = output.size_on_qp;
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_VERIFY(test_op_dim <= DIM,
"DerivativeAssemble: test_op_dim exceeds spatial DIM");
MFEM_VERIFY(test_op_dim * nq <= FHAT_SLAB_MAX,
"DerivativeAssemble: fhat slab exceeds capacity");
#endif
MFEM_SHARED real_t fhat_storage[FHAT_SLAB_MAX];
const auto &inputs_ref = inputs;
// Iterate quadrature points using the thread-block mapping
const auto foreach_qp = [&](auto &&body)
{
if constexpr (DIM == 2)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
MFEM_FOREACH_THREAD(qy, y, q1d) { body(qx, qy, 0); }
}
else
{
for (int qz = 0; qz < q1d; qz++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
MFEM_FOREACH_THREAD(qx, x, q1d) { body(qx, qy, qz); }
}
}
};
const auto zero_slab = [&](const int n_comp)
{
foreach_qp([&](const int qx, const int qy, const int qz)
{
const int q = tensor_idx<DIM>(qx, qy, qz, q1d);
for (int k = 0; k < n_comp; k++) { fhat_storage[k * nq + q] = 0.0; }
});
MFEM_SYNC_THREAD;
};
const auto accumulate_tv = [&](const int Jx,
const int Jy,
const int Jz,
const int j,
const int tv,
const int tod_only = -1)
{
int m_offset = 0;
for_constexpr<n_inputs>([&](auto inp)
{
using fop_t = std::decay_t<decltype(get<inp>(inputs_ref))>;
const int trial_op_dim = static_cast<int>(itod(static_cast<int>(inp)));
if (trial_op_dim == 0) { return; }
const auto &B = input_dtq_maps[inp].B;
const auto &G = input_dtq_maps[inp].G;
if constexpr (is_value_fop<fop_t>::value)
{
foreach_qp([&](const int qx, const int qy, const int qz)
{
const int q = tensor_idx<DIM>(qx, qy, qz, q1d);
const real_t w =
trial_basis_weight_value<DIM>(B, qx, qy, qz, Jx, Jy, Jz);
for (int m = 0; m < trial_op_dim; m++)
{
for (int k = 0; k < test_op_dim; k++)
{
if (tod_only >= 0 && k != tod_only) { continue; }
const real_t f = qpdc(m + m_offset, j, k, tv, q, e);
if constexpr (grad_out && !ident_out)
{
fhat_storage[k * nq + q] += f * w;
}
else
{
fhat_storage[q] += f * w;
}
}
}
});
}
else if constexpr (is_gradient_fop<fop_t>::value)
{
foreach_qp([&](const int qx, const int qy, const int qz)
{
const int q = tensor_idx<DIM>(qx, qy, qz, q1d);
for (int m = 0; m < trial_op_dim; m++)
{
const real_t w = trial_basis_weight_gradient<DIM>(
B, G, m, qx, qy, qz, Jx, Jy, Jz);
for (int k = 0; k < test_op_dim; k++)
{
if (tod_only >= 0 && k != tod_only) { continue; }
const real_t f = qpdc(m + m_offset, j, k, tv, q, e);
if constexpr (grad_out && !ident_out)
{
fhat_storage[k * nq + q] += f * w;
}
else
{
fhat_storage[q] += f * w;
}
}
}
});
}
else
{
MFEM_ABORT_KERNEL("sum factorized sparse matrix assemble routine "
"not implemented for field operator");
}
MFEM_SYNC_THREAD;
m_offset += trial_op_dim;
});
};
for (int Jz = 0; Jz < ((DIM == 2) ? 1 : num_trial_dof_1d); Jz++)
{
for (int Jy = 0; Jy < num_trial_dof_1d; Jy++)
{
for (int Jx = 0; Jx < num_trial_dof_1d; Jx++)
{
const int J = tensor_idx<DIM>(Jx, Jy, Jz, num_trial_dof_1d);
for (int j = 0; j < trial_vdim; j++)
{
auto bvtfhat =
Reshape(&Ae(0, 0, J, j, e), num_test_dof, test_vdim);
const int fhat_size = test_vdim * test_op_dim * nq;
if (fhat_size <= FHAT_SLAB_MAX)
{
auto fhat =
Reshape(&fhat_storage[0], test_vdim, test_op_dim, nq);
for (int tv = 0; tv < test_vdim; tv++)
{
for (int tod = 0; tod < test_op_dim; tod++)
{
foreach_qp([&](const int qx, const int qy, const int qz)
{
const int q = tensor_idx<DIM>(qx, qy, qz, q1d);
fhat(tv, tod, q) = 0.0;
});
}
}
MFEM_SYNC_THREAD;
int m_offset = 0;
for_constexpr<n_inputs>([&](auto inp)
{
using fop_t = std::decay_t<decltype(get<inp>(inputs_ref))>;
const int trial_op_dim =
static_cast<int>(itod(static_cast<int>(inp)));
if (trial_op_dim == 0) { return; }
const auto &B = input_dtq_maps[inp].B;
const auto &G = input_dtq_maps[inp].G;
if constexpr (is_value_fop<fop_t>::value)
{
foreach_qp([&](const int qx, const int qy, const int qz)
{
const int q = tensor_idx<DIM>(qx, qy, qz, q1d);
const real_t w = trial_basis_weight_value<DIM>(
B, qx, qy, qz, Jx, Jy, Jz);
for (int m = 0; m < trial_op_dim; m++)
{
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
const real_t f =
qpdc(m + m_offset, j, k, i, q, e);
fhat(i, k, q) += f * w;
}
}
}
});
}
else if constexpr (is_gradient_fop<fop_t>::value)
{
foreach_qp([&](const int qx, const int qy, const int qz)
{
const int q = tensor_idx<DIM>(qx, qy, qz, q1d);
for (int m = 0; m < trial_op_dim; m++)
{
const real_t w = trial_basis_weight_gradient<DIM>(
B, G, m, qx, qy, qz, Jx, Jy, Jz);
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
const real_t f =
qpdc(m + m_offset, j, k, i, q, e);
fhat(i, k, q) += f * w;
}
}
}
});
}
else
{
MFEM_ABORT_KERNEL(
"sum factorized sparse matrix assemble routine "
"not implemented for field operator");
}
MFEM_SYNC_THREAD;
m_offset += trial_op_dim;
});
map_quadrature_data_to_fields<DIM, MQ1>(
bvtfhat, fhat, output, output_dtq, smem);
}
else if constexpr (ident_out)
{
for (int sq = 0; sq < size_on_qp; sq++)
{
const int tv = sq / test_op_dim;
const int tod = sq % test_op_dim;
zero_slab(1);
accumulate_tv(Jx, Jy, Jz, j, tv, tod);
auto f_slab = Reshape(&fhat_storage[0], 1, 1, nq);
map_quadrature_data_to_fields<DIM, MQ1>(
bvtfhat, f_slab, output, output_dtq, smem, sq);
}
}
else if constexpr (grad_out)
{
for (int tv = 0; tv < test_vdim; tv++)
{
zero_slab(test_op_dim);
accumulate_tv(Jx, Jy, Jz, j, tv);
auto f_slab =
Reshape(&fhat_storage[0], 1, test_op_dim, nq);
map_quadrature_data_to_fields<DIM, MQ1>(
bvtfhat, f_slab, output, output_dtq, smem, tv);
}
}
else
{
for (int tv = 0; tv < test_vdim; tv++)
{
zero_slab(1);
accumulate_tv(Jx, Jy, Jz, j, tv);
auto f_slab = Reshape(&fhat_storage[0], 1, 1, nq);
map_quadrature_data_to_fields<DIM, MQ1>(
bvtfhat, f_slab, output, output_dtq, smem, tv);
}
}
}
}
}
}
}
} // namespace detail
// ────────────────────────────────────────────────────────────────────────────
// Assemble sparse Jacobian from cached quadrature derivatives (tensor 2D/3D)
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
class DerivativeAssemble
{
static constexpr auto inout_tuple =
merge_mfem_tuples_as_empty_std_tuple(inputs_t {}, outputs_t{});
static constexpr auto filtered_inout_tuple = filter_fields(inout_tuple);
static constexpr size_t nfields =
count_unique_field_ids(filtered_inout_tuple);
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
const IntegratorContext ctx;
const Vector &qp_cache;
inputs_t inputs;
outputs_t outputs;
const bool use_sum_factorization;
const std::vector<const DofToQuad *> dtqs;
const std::array<DofToQuadMap, n_inputs> input_dtq_maps;
const std::array<DofToQuadMap, n_outputs> output_dtq_maps;
const std::array<bool, n_inputs> input_is_dependent;
const size_t trial_field_uf;
const size_t test_field_uf;
const ParFiniteElementSpace *test_fes;
const ParFiniteElementSpace *trial_fes;
const int test_vdim;
const int test_op_dim;
const int num_test_dof;
const int trial_vdim;
const int trial_op_dim;
const int num_trial_dof;
const int dim, ne, nq, q1d;
const int num_trial_dof_1d;
const int total_trial_op_dim;
mutable Vector inputs_trial_op_dim;
mutable Vector Ae_mem;
public:
DerivativeAssemble() = delete;
DerivativeAssemble(IntegratorContext ctx_in,
qfunc_t /*qfunc*/,
inputs_t inputs_in,
outputs_t outputs_in,
const Vector &qp_cache_in):
ctx(ctx_in), qp_cache(qp_cache_in), inputs(inputs_in),
outputs(outputs_in), use_sum_factorization(
[&]
{
const Element::Type etype =
Element::TypeFromGeometry(ctx_in.mesh.GetTypicalElementGeometry());
return (etype == Element::QUADRILATERAL || etype == Element::HEXAHEDRON);
}()),
dtqs(
[&]
{
const DofToQuad::Mode dtq_mode = use_sum_factorization
? DofToQuad::Mode::TENSOR
: DofToQuad::Mode::FULL;
std::vector<const DofToQuad *> maps;
maps.reserve(ctx_in.unionfds.size());
for (const auto &field : ctx_in.unionfds)
{
maps.emplace_back(
GetDofToQuad<Entity::Element>(field, ctx_in.ir, dtq_mode));
}
return maps;
}()),
input_dtq_maps(create_dtq_maps<Entity::Element>(
inputs,
dtqs,
create_union_field_map_for_dtq(ctx_in, inputs),
ctx_in.unionfds,
ctx_in.ir)),
output_dtq_maps(create_dtq_maps<Entity::Element>(
outputs,
dtqs,
create_union_field_map_for_dtq(ctx_in, outputs),
ctx_in.unionfds,
ctx_in.ir)),
input_is_dependent(compute_input_is_dependent(inputs, derivative_id)),
trial_field_uf(find_union_field_index(ctx_in, derivative_id)),
test_field_uf(
find_union_field_index(ctx_in, get<0>(outputs).GetFieldId())),
test_fes(
[&]
{
const auto *fes = std::get_if<const ParFiniteElementSpace *>(
&ctx_in.unionfds[test_field_uf].data);
MFEM_ASSERT(fes != nullptr && *fes != nullptr,
"LocalQFBackend: test space is not a ParFiniteElementSpace");
return *fes;
}()),
trial_fes(
[&]
{
const auto *fes = std::get_if<const ParFiniteElementSpace *>(
&ctx_in.unionfds[trial_field_uf].data);
MFEM_ASSERT(fes != nullptr && *fes != nullptr,
"LocalQFBackend: trial space is not a ParFiniteElementSpace");
return *fes;
}()),
test_vdim(get<0>(outputs).vdim),
test_op_dim(get<0>(outputs).size_on_qp / test_vdim),
num_test_dof(test_fes->GetFE(0)->GetDof()),
trial_vdim(compute_trial_vdim(inputs, derivative_id)), trial_op_dim(
[&]
{
int top = 0;
for_constexpr<n_inputs>([&](auto i)
{
if (get<i>(inputs).GetFieldId() == derivative_id)
{
top = get<i>(inputs).size_on_qp / get<i>(inputs).vdim;
}
});
return top;
}()),
num_trial_dof(trial_fes->GetFE(0)->GetDof()),
dim(ctx_in.mesh.Dimension()), ne(ctx_in.nentities),
nq(ctx_in.ir.GetNPoints()), q1d(tensor_1d_size(nq, dim)),
num_trial_dof_1d(tensor_1d_size(num_trial_dof, dim)), total_trial_op_dim(
[&]
{
const auto in_qp_sizes =
get_input_size_on_qp(inputs, std::make_index_sequence<n_inputs>{});
return compute_total_trial_op_dim(
inputs, input_is_dependent, in_qp_sizes);
}()),
inputs_trial_op_dim(), Ae_mem()
{
MFEM_ASSERT(ctx.unionfds.size() == nfields,
"LocalQFBackend: unionfds size mismatch");
MFEM_ASSERT(trial_field_uf != SIZE_MAX,
"DerivativeAssemble: trial field not found in unionfds");
MFEM_ASSERT(test_field_uf != SIZE_MAX,
"DerivativeAssemble: test field not found in unionfds");
MFEM_ASSERT(trial_vdim > 0,
"LocalQFBackend: could not determine trial vdim");
MFEM_ASSERT(total_trial_op_dim > 0,
"LocalQFBackend: no dependent inputs found");
inputs_trial_op_dim.SetSize(n_inputs);
inputs_trial_op_dim.UseDevice(true);
for_constexpr<n_inputs>([&](auto i)
{
inputs_trial_op_dim[i] =
input_is_dependent[i]
? get<i>(inputs).size_on_qp / get<i>(inputs).vdim
: 0;
});
const int elem_mat_size =
num_test_dof * test_vdim * num_trial_dof * trial_vdim;
Ae_mem.SetSize(elem_mat_size * ne);
Ae_mem.UseDevice(true);
Ae_mem = 0.0;
}
void operator()(SparseMatrix *&A) const
{
if (ctx.attr.Size() == 0) { return; }
if (!(use_sum_factorization && (dim == 2 || dim == 3)))
{
MFEM_ABORT("DerivativeAssemble optimized path is implemented "
"for tensor-product 2D/3D elements only");
}
DerivativeAssembleHO::Run(dim,
q1d,
ctx,
qp_cache,
Ae_mem,
inputs,
outputs,
input_dtq_maps,
output_dtq_maps[0],
inputs_trial_op_dim,
test_vdim,
test_op_dim,
num_test_dof,
num_trial_dof,
num_trial_dof_1d,
trial_vdim,
total_trial_op_dim,
nq,
ne,
q1d,
dim);
A = new SparseMatrix(test_fes->GetVSize(), trial_fes->GetVSize());
auto Ae_host = Reshape(Ae_mem.HostReadWrite(),
num_test_dof * test_vdim,
num_trial_dof * trial_vdim,
ne);
for (int e = 0; e < ne; e++)
{
DenseMatrix Aee(&Ae_host(0, 0, e),
num_test_dof * test_vdim,
num_trial_dof * trial_vdim);
Array<int> test_vdofs, trial_vdofs;
test_fes->GetElementVDofs(e, test_vdofs);
trial_fes->GetElementVDofs(e, trial_vdofs);
Array<int> test_vdofs_mapped(test_vdofs.Size());
const Array<int> &test_dofmap =
dynamic_cast<const TensorBasisElement &>(*test_fes->GetFE(0))
.GetDofMap();
if (test_dofmap.Size() == 0) { test_vdofs_mapped = test_vdofs; }
else
{
for (int vd = 0; vd < test_vdim; vd++)
{
for (int i = 0; i < num_test_dof; i++)
{
test_vdofs_mapped[i + vd * num_test_dof] =
test_vdofs[test_dofmap[i] + vd * num_test_dof];
}
}
}
Array<int> trial_vdofs_mapped(trial_vdofs.Size());
const Array<int> &trial_dofmap =
dynamic_cast<const TensorBasisElement &>(*trial_fes->GetFE(0))
.GetDofMap();
if (trial_dofmap.Size() == 0) { trial_vdofs_mapped = trial_vdofs; }
else
{
for (int vd = 0; vd < trial_vdim; vd++)
{
for (int i = 0; i < num_trial_dof; i++)
{
trial_vdofs_mapped[i + vd * num_trial_dof] =
trial_vdofs[trial_dofmap[i] + vd * num_trial_dof];
}
}
}
A->AddSubMatrix(test_vdofs_mapped, trial_vdofs_mapped, Aee, 1);
}
A->Finalize();
}
template<typename backend_t = LocalQFHOBackend<3>, int T_Q1D = 0>
static void derivative_assemble_callback(
const IntegratorContext &ctx,
const Vector &qp_cache,
Vector &Ae_mem,
const inputs_t &inputs,
const outputs_t &outputs,
const std::array<DofToQuadMap, n_inputs> &input_dtq_maps,
const DofToQuadMap &output_dtq,
const Vector &inputs_trial_op_dim,
const int test_vdim,
const int test_op_dim,
const int num_test_dof,
const int num_trial_dof,
const int num_trial_dof_1d,
const int trial_vdim,
const int total_trial_op_dim,
const int nq,
const int ne,
const int q1d,
const int dim)
{
static constexpr int DIM = backend_t::DIM;
static constexpr int MQ1 = T_Q1D ? T_Q1D : backend_t::MQ1;
static constexpr int MNQ = (DIM == 2) ? MQ1 * MQ1 : MQ1 * MQ1 * MQ1;
MFEM_VERIFY(dim == DIM,
"DerivativeAssemble: mesh dim does not match backend");
MFEM_VERIFY(dim == ctx.mesh.Dimension(), "Dimension mismatch");
MFEM_VERIFY(q1d <= MQ1, "q1d exceeds backend MQ1 limit");
MFEM_VERIFY(nq <= MNQ,
"DerivativeAssemble: nq exceeds backend quadrature capacity");
MFEM_VERIFY(test_op_dim <= DIM,
"DerivativeAssemble: test_op_dim exceeds spatial DIM");
if (ctx.attr.Size() == 0) { return; }
const auto d_attr = ctx.attr.Read();
const bool has_attr = ctx.attr.Size() > 0;
const auto d_elem_attr = ctx.elem_attr->Read();
const auto qpdc = Reshape(qp_cache.Read(),
total_trial_op_dim,
trial_vdim,
test_op_dim,
test_vdim,
nq,
ne);
const auto itod = Reshape(inputs_trial_op_dim.Read(), n_inputs);
auto Ae = Reshape(Ae_mem.ReadWrite(),
num_test_dof,
test_vdim,
num_trial_dof,
trial_vdim,
ne);
dfem::forall(
[=] MFEM_HOST_DEVICE(const int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
static constexpr int DIM = backend_t::DIM;
static constexpr int MQ1 = T_Q1D ? T_Q1D : backend_t::MQ1;
MFEM_SHARED typename backend_t::Shared s;
detail::assemble_element_mat_sumfact<DIM, MQ1>(Ae,
qpdc,
e,
itod,
inputs,
get<0>(outputs),
input_dtq_maps,
output_dtq,
q1d,
num_trial_dof_1d,
s);
},
ne,
backend_t::thread_blocks(q1d),
0,
nullptr);
}
using AssembleKernelType =
decltype(&DerivativeAssemble::derivative_assemble_callback<>);
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeAssembleHO,
AssembleKernelType,
(int, int));
};
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline
typename DerivativeAssemble<derivative_id, qfunc_t, inputs_t, outputs_t>::
AssembleKernelType
DerivativeAssemble<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeAssembleHO::Kernel()
{
static_assert(DIM == 2 || DIM == 3);
using assemble_t =
DerivativeAssemble<derivative_id, qfunc_t, inputs_t, outputs_t>;
return assemble_t::template derivative_assemble_callback<
LocalQFHOBackend<DIM, Q1D>>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline
typename DerivativeAssemble<derivative_id, qfunc_t, inputs_t, outputs_t>::
AssembleKernelType
DerivativeAssemble<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeAssembleHO::Fallback(int dim, int q1d)
{
using assemble_t =
DerivativeAssemble<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeAssembleHO = typename assemble_t::DerivativeAssembleHO;
if (dim == 2)
{
return DispatchHOKernelByQ1D<DerivativeAssembleHO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchHOKernelByQ1D<DerivativeAssembleHO, 3, 8>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
} // namespace mfem::future::LocalQFImpl
@@ -0,0 +1,506 @@
// 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 "../../integrator_ctx.hpp"
#include "kernels.hpp"
#include "util.hpp"
#include <array>
namespace mfem::future::LocalQFImpl
{
// Assemble diagonal of cached Jacobian (square trial == test, tensor 2D/3D)
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
class DerivativeAssembleDiagonal
{
static constexpr auto inout_tuple =
merge_mfem_tuples_as_empty_std_tuple(inputs_t {}, outputs_t{});
static constexpr auto filtered_inout_tuple = filter_fields(inout_tuple);
static constexpr size_t nfields =
count_unique_field_ids(filtered_inout_tuple);
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
const IntegratorContext ctx;
const Vector &qp_cache;
inputs_t inputs;
outputs_t outputs;
const bool use_sum_factorization;
const std::vector<const DofToQuad *> dtqs;
const std::array<DofToQuadMap, n_inputs> input_dtq_maps;
const std::array<DofToQuadMap, n_outputs> output_dtq_maps;
const std::array<bool, n_inputs> input_is_dependent;
const size_t trial_field_uf;
const size_t test_field_uf;
const bool is_square;
const int test_vdim;
const int test_op_dim;
const int num_test_dof;
const int num_test_dof_1d;
const int trial_vdim;
const int total_trial_op_dim;
const int num_trial_dof_1d;
const int residual_size_on_qp;
const int dim, ne, nq, q1d;
const std::array<int, n_inputs> inputs_trial_op_dim;
mutable Vector Ye_mem;
public:
DerivativeAssembleDiagonal() = delete;
DerivativeAssembleDiagonal(IntegratorContext ctx_in,
qfunc_t /*qfunc*/,
inputs_t inputs_in,
outputs_t outputs_in,
const Vector &qp_cache_in):
ctx(ctx_in), qp_cache(qp_cache_in), inputs(inputs_in),
outputs(outputs_in), use_sum_factorization(
[&]
{
const Element::Type etype =
Element::TypeFromGeometry(ctx_in.mesh.GetTypicalElementGeometry());
return (etype == Element::QUADRILATERAL || etype == Element::HEXAHEDRON);
}()),
dtqs(
[&]
{
const DofToQuad::Mode dtq_mode = use_sum_factorization
? DofToQuad::Mode::TENSOR
: DofToQuad::Mode::FULL;
std::vector<const DofToQuad *> maps;
maps.reserve(ctx_in.unionfds.size());
for (const auto &field : ctx_in.unionfds)
{
maps.emplace_back(
GetDofToQuad<Entity::Element>(field, ctx_in.ir, dtq_mode));
}
return maps;
}()),
input_dtq_maps(create_dtq_maps<Entity::Element>(
inputs,
dtqs,
create_union_field_map_for_dtq(ctx_in, inputs),
ctx_in.unionfds,
ctx_in.ir)),
output_dtq_maps(create_dtq_maps<Entity::Element>(
outputs,
dtqs,
create_union_field_map_for_dtq(ctx_in, outputs),
ctx_in.unionfds,
ctx_in.ir)),
input_is_dependent(compute_input_is_dependent(inputs, derivative_id)),
trial_field_uf(find_union_field_index(ctx_in, derivative_id)),
test_field_uf(
find_union_field_index(ctx_in, get<0>(outputs).GetFieldId())),
is_square(
[&]
{
const auto *test_fes = std::get_if<const ParFiniteElementSpace *>(
&ctx_in.unionfds[test_field_uf].data);
const auto *trial_fes = std::get_if<const ParFiniteElementSpace *>(
&ctx_in.unionfds[trial_field_uf].data);
return test_fes && trial_fes && *test_fes && *trial_fes &&
(*test_fes == *trial_fes);
}()),
test_vdim(get<0>(outputs).vdim),
test_op_dim(get<0>(outputs).size_on_qp / test_vdim), num_test_dof(
[&]
{
const auto *test_fes = std::get_if<const ParFiniteElementSpace *>(
&ctx_in.unionfds[test_field_uf].data);
MFEM_ASSERT(test_fes != nullptr && *test_fes != nullptr,
"LocalQFBackend: test space is not a ParFiniteElementSpace");
return (*test_fes)->GetFE(0)->GetDof();
}()),
num_test_dof_1d(tensor_1d_size(num_test_dof, ctx_in.mesh.Dimension())),
trial_vdim(compute_trial_vdim(inputs, derivative_id)), total_trial_op_dim(
[&]
{
const auto input_size_on_qp =
get_input_size_on_qp(inputs, std::make_index_sequence<n_inputs>{});
return compute_total_trial_op_dim(
inputs, input_is_dependent, input_size_on_qp);
}()),
num_trial_dof_1d(
[&]
{
const auto *trial_fes = std::get_if<const ParFiniteElementSpace *>(
&ctx_in.unionfds[trial_field_uf].data);
MFEM_ASSERT(trial_fes != nullptr && *trial_fes != nullptr,
"LocalQFBackend: trial space is not a ParFiniteElementSpace");
const int num_trial_dof = (*trial_fes)->GetFE(0)->GetDof();
return tensor_1d_size(num_trial_dof, ctx_in.mesh.Dimension());
}()),
residual_size_on_qp(test_vdim * test_op_dim * trial_vdim *
total_trial_op_dim),
dim(ctx_in.mesh.Dimension()), ne(ctx_in.nentities),
nq(ctx_in.ir.GetNPoints()), q1d(tensor_1d_size(nq, dim)),
inputs_trial_op_dim(
[&]
{
std::array<int, n_inputs> itod{};
for_constexpr<n_inputs>([&](auto i)
{
itod[i] = input_is_dependent[i]
? get<i>(inputs).size_on_qp / get<i>(inputs).vdim
: 0;
});
return itod;
}()),
Ye_mem()
{
MFEM_ASSERT(ctx.unionfds.size() == nfields,
"LocalQFBackend: unionfds size mismatch");
MFEM_ASSERT(
trial_field_uf != SIZE_MAX,
"DerivativeAssembleDiagonal: trial field not found in unionfds");
MFEM_ASSERT(
test_field_uf != SIZE_MAX,
"DerivativeAssembleDiagonal: test field not found in unionfds");
MFEM_ASSERT(trial_vdim > 0,
"LocalQFBackend: could not determine trial vdim");
MFEM_ASSERT(total_trial_op_dim > 0,
"LocalQFBackend: no dependent inputs found");
if (is_square)
{
Ye_mem.SetSize(num_test_dof * test_vdim * ne);
Ye_mem.UseDevice(true);
}
}
template<typename Backend>
void run_kernels() const
{
Backend::Run(dim,
q1d,
ctx,
qp_cache,
Ye_mem,
inputs,
outputs,
output_dtq_maps[0],
input_dtq_maps,
test_vdim,
test_op_dim,
num_test_dof,
num_test_dof_1d,
trial_vdim,
total_trial_op_dim,
residual_size_on_qp,
inputs_trial_op_dim,
nq,
ne,
q1d,
dim);
}
void operator()(Vector &diag_e) const
{
if (!is_square) { return; }
if (ctx.attr.Size() == 0) { return; }
if (!(use_sum_factorization && (dim == 2 || dim == 3)))
{
MFEM_ABORT("DerivativeAssembleDiagonal optimized path is implemented "
"for tensor-product 2D/3D elements only");
}
MFEM_VERIFY(num_test_dof_1d == num_trial_dof_1d,
"DerivativeAssembleDiagonal requires matching tensor dofs");
MFEM_VERIFY(num_test_dof_1d <= DeviceDofQuadLimits::Get().MAX_D1D, "");
MFEM_VERIFY(q1d <= DeviceDofQuadLimits::Get().MAX_Q1D, "");
Ye_mem = 0.0;
if (q1d <= LocalQFLOBackendMQ1())
{
run_kernels<DerivativeAssembleDiagonalLO>();
}
else if (q1d <= LocalQFHOBackendMQ1())
{
run_kernels<DerivativeAssembleDiagonalHO>();
}
else
{
MFEM_ABORT("Unsupported quadrature order for LocalQF backend");
}
diag_e += Ye_mem;
}
template<typename backend_t = LocalQFLOBackend<3>, int T_Q1D = 0>
static void derivative_assemble_diagonal_callback(
const IntegratorContext &ctx,
const Vector &qp_cache,
Vector &Ye_mem,
const inputs_t &inputs,
const outputs_t &outputs,
const DofToQuadMap &output_dtq,
const std::array<DofToQuadMap, n_inputs> &input_dtq_maps,
const int test_vdim,
const int test_op_dim,
const int num_test_dof,
const int num_test_dof_1d,
const int trial_vdim,
const int total_trial_op_dim,
const int residual_size_on_qp,
const std::array<int, n_inputs> &inputs_trial_op_dim,
const int nq,
const int ne,
const int q1d,
const int dim)
{
MFEM_VERIFY(dim == ctx.mesh.Dimension(), "Dimension mismatch");
if (ctx.attr.Size() == 0) { return; }
static constexpr bool B2D = backend_t::DIM == 2;
static constexpr int MTPB = backend_t::MAX_THREADS_PER_BLOCK();
const auto d_attr = ctx.attr.Read();
const bool has_attr = ctx.attr.Size() > 0;
const auto d_elem_attr = ctx.elem_attr->Read();
auto cache_tensor = DeviceTensor<3, const real_t>(
qp_cache.Read(), residual_size_on_qp, nq, ne);
const int num_dofs_per_elem = num_test_dof * test_vdim;
auto Ye = Reshape(Ye_mem.ReadWrite(), num_dofs_per_elem, ne);
using test_fop_t = std::decay_t<decltype(get<0>(outputs))>;
dfem::forall<MTPB>(
[=] MFEM_HOST_DEVICE(const int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
auto qpdc = Reshape(&cache_tensor(0, 0, e),
test_vdim,
test_op_dim,
trial_vdim,
total_trial_op_dim,
nq);
// Test-basis factor along a spatial axis
const auto eval_test =
[&](const int k, const int axis, const int q, const int d)
{
const auto &B = output_dtq.B;
const auto &G = output_dtq.G;
if constexpr (is_value_fop<test_fop_t>::value)
{
return (k == 0) ? B(q, 0, d) : 0.0;
}
else if constexpr (is_gradient_fop<test_fop_t>::value)
{
return (k == axis) ? G(q, 0, d) : B(q, 0, d);
}
else
{
return 0.0;
}
};
// Backend-owned shared scratch for the sum-factorized contraction.
MFEM_SHARED typename backend_t::Shared s_diag;
const int nz_dof = B2D ? 1 : num_test_dof_1d;
for (int vd = 0; vd < test_vdim; vd++)
{
auto Y = Reshape(&Ye(vd * num_test_dof, e),
num_test_dof_1d,
num_test_dof_1d,
nz_dof);
MFEM_FOREACH_THREAD(dz_t, z, nz_dof)
{
MFEM_FOREACH_THREAD_DIRECT(dy_t, y, num_test_dof_1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx_t, x, num_test_dof_1d)
{ Y(dx_t, dy_t, dz_t) = 0.0; }
}
}
MFEM_SYNC_THREAD;
// Accumulate every (test op k, dependent input s, trial op m) block
// of the cached Jacobian into the diagonal via the backend driver.
for (int k = 0; k < test_op_dim; k++)
{
int m_offset = 0;
for_constexpr<n_inputs>([&](auto s)
{
using fop_t = std::decay_t<decltype(get<s>(inputs))>;
const int trial_op_dim =
inputs_trial_op_dim[static_cast<int>(s)];
if (trial_op_dim == 0) { return; }
const auto &in_dtq = input_dtq_maps[s];
const auto eval_input =
[&](const int m, const int axis, const int q, const int d)
{
if constexpr (is_value_fop<fop_t>::value)
{
return (m == 0) ? in_dtq.B(q, 0, d) : 0.0;
}
else if constexpr (is_gradient_fop<fop_t>::value)
{
return (m == axis) ? in_dtq.G(q, 0, d)
: in_dtq.B(q, 0, d);
}
else
{
return 0.0;
}
};
for (int m = 0; m < trial_op_dim; m++)
{
const int col = m_offset + m;
backend_t::DiagContract(
s_diag,
num_test_dof_1d,
q1d,
nz_dof,
[&](int axis, int q, int d)
{ return eval_test(k, axis, q, d); },
[&](int axis, int q, int d)
{ return eval_input(m, axis, q, d); },
[&](int q) { return qpdc(vd, k, vd, col, q); },
[&](int dx, int dy, int dz, real_t u)
{ Y(dx, dy, dz) += u; });
}
m_offset += trial_op_dim;
});
}
}
},
ne,
backend_t::thread_blocks(std::max(q1d, num_test_dof_1d)),
0,
nullptr);
}
using DiagonalKernelType =
decltype(&DerivativeAssembleDiagonal::
derivative_assemble_diagonal_callback<>);
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeAssembleDiagonalLO,
DiagonalKernelType,
(int, int) );
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeAssembleDiagonalHO,
DiagonalKernelType,
(int, int) );
};
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeAssembleDiagonal<derivative_id,
qfunc_t,
inputs_t,
outputs_t>::DiagonalKernelType
DerivativeAssembleDiagonal<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeAssembleDiagonalLO::Kernel()
{
static_assert((DIM == 2 || DIM == 3) && Q1D <= 8);
using diag_t =
DerivativeAssembleDiagonal<derivative_id, qfunc_t, inputs_t, outputs_t>;
return diag_t::template derivative_assemble_diagonal_callback<
LocalQFLOBackend<DIM, Q1D>>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeAssembleDiagonal<derivative_id,
qfunc_t,
inputs_t,
outputs_t>::DiagonalKernelType
DerivativeAssembleDiagonal<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeAssembleDiagonalLO::Fallback(int dim, int q1d)
{
using diag_t =
DerivativeAssembleDiagonal<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeAssembleDiagonalLO =
typename diag_t::DerivativeAssembleDiagonalLO;
if (dim == 2)
{
return DispatchLOKernelByQ1D<DerivativeAssembleDiagonalLO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchLOKernelByQ1D<DerivativeAssembleDiagonalLO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeAssembleDiagonal<derivative_id,
qfunc_t,
inputs_t,
outputs_t>::DiagonalKernelType
DerivativeAssembleDiagonal<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeAssembleDiagonalHO::Kernel()
{
using diag_t =
DerivativeAssembleDiagonal<derivative_id, qfunc_t, inputs_t, outputs_t>;
return diag_t::template derivative_assemble_diagonal_callback<
LocalQFHOBackend<DIM>,
Q1D>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeAssembleDiagonal<derivative_id,
qfunc_t,
inputs_t,
outputs_t>::DiagonalKernelType
DerivativeAssembleDiagonal<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeAssembleDiagonalHO::Fallback(int dim, int q1d)
{
using diag_t =
DerivativeAssembleDiagonal<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeAssembleDiagonalHO =
typename diag_t::DerivativeAssembleDiagonalHO;
if (dim == 2)
{
return DispatchHOKernelByQ1D<DerivativeAssembleDiagonalHO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchHOKernelByQ1D<DerivativeAssembleDiagonalHO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
} // namespace mfem::future::LocalQFImpl
@@ -0,0 +1,551 @@
// 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 "../../integrator_ctx.hpp"
#include "kernels.hpp"
#include "util.hpp"
#include <array>
namespace mfem::future::LocalQFImpl
{
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
class DerivativeSetup
{
static constexpr auto inout_tuple =
merge_mfem_tuples_as_empty_std_tuple(inputs_t {}, outputs_t{});
static constexpr auto filtered_inout_tuple = filter_fields(inout_tuple);
static constexpr size_t nfields =
count_unique_field_ids(filtered_inout_tuple);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
using args_tuple_t = decay_tuple<qf_param_ts>;
static constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
static constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
static_assert(n_inputs + n_outputs == tuple_size<qf_param_ts>::value,
"LocalQF: q-function arity must match inputs + outputs");
const qfunc_t qfunc;
const inputs_t inputs;
const outputs_t outputs;
const IntegratorContext ctx;
Vector &qp_cache;
const std::vector<const DofToQuad *> dtqs;
// inputs: dtq, idx, B, G, d1d, q1d, vdim
const std::array<DofToQuadMap, n_inputs> input_dtq;
const std::array<size_t, n_inputs> input_idx;
const std::array<const real_t *, n_inputs> input_B, input_G;
const std::array<int, n_inputs> input_d1d, input_q1d, input_vdim;
// Jacobian cache metadata
const std::array<bool, n_inputs> input_is_dependent;
const std::array<int, n_inputs> input_size_on_qp;
const std::array<int, n_outputs> out_vdim;
const std::array<int, n_outputs> out_op_dim;
const std::array<int, n_outputs> out_offsets;
const int output_size_on_qp;
const int trial_vdim;
const int total_trial_op_dim;
const int residual_size_on_qp;
// other constants
const int dim, ne, nq, q1d;
public:
//////////////////////////////////////////////////////////////////
DerivativeSetup() = delete;
DerivativeSetup(IntegratorContext ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs,
Vector &qp_cache):
qfunc(std::move(qfunc)), inputs(inputs), outputs(outputs), ctx(ctx),
qp_cache(qp_cache), dtqs(make_dtqs(ctx)),
input_dtq(create_dtq_maps<Entity::Element>(
inputs,
dtqs,
create_union_field_map_for_dtq(ctx, inputs),
ctx.unionfds,
ctx.ir)),
input_idx(create_input_vector_map(ctx, inputs)),
input_B(get_B(input_dtq)), input_G(get_G(input_dtq)),
input_d1d(get_D1D(input_dtq)), input_q1d(get_Q1D(input_dtq)),
input_vdim(get_vdim(inputs)),
input_is_dependent(compute_input_is_dependent(inputs, derivative_id)),
input_size_on_qp(
get_input_size_on_qp(inputs, std::make_index_sequence<n_inputs> {})),
out_vdim(get_vdim(outputs)), out_op_dim(compute_out_op_dim(outputs)),
out_offsets(compute_out_offsets(out_vdim, out_op_dim)), output_size_on_qp(
[&]
{
int s = 0;
for_constexpr<n_outputs>([&](auto o)
{ s += get<o>(outputs).size_on_qp; });
return s;
}()),
trial_vdim(compute_trial_vdim(inputs, derivative_id)),
total_trial_op_dim(compute_total_trial_op_dim(
inputs, input_is_dependent, input_size_on_qp)),
residual_size_on_qp(output_size_on_qp * trial_vdim * total_trial_op_dim),
dim(ctx.mesh.Dimension()), ne(ctx.nentities), nq(ctx.ir.GetNPoints()),
q1d(tensor_1d_size(nq, dim))
{
MFEM_ASSERT(ctx.unionfds.size() == nfields,
"LocalQFBackend: unionfds size mismatch");
qp_cache.SetSize(ne * nq * residual_size_on_qp);
qp_cache.UseDevice(true);
}
//////////////////////////////////////////////////////////////////
void operator()(const std::vector<Vector *> &xe) const
{
if (ctx.attr.Size() == 0) { return; }
auto cache_tensor = DeviceTensor<3, real_t>(
qp_cache.ReadWrite(), residual_size_on_qp, nq, ne);
if (q1d <= LocalQFLOBackendMQ1())
{
run_kernels<DerivativeSetupLO>(xe, cache_tensor);
}
else if (q1d <= LocalQFHOBackendMQ1())
{
run_kernels<DerivativeSetupHO>(xe, cache_tensor);
}
else
{
MFEM_ABORT("Unsupported quadrature order for LocalQF backend");
}
}
//////////////////////////////////////////////////////////////////
template<typename Backend>
void run_kernels(const std::vector<Vector *> &xe,
DeviceTensor<3, real_t> &cache_tensor) const
{
Backend::Run(dim,
q1d,
ctx,
qfunc,
// inputs
input_idx,
input_B,
input_G,
input_vdim,
input_d1d,
input_q1d,
input_size_on_qp,
input_is_dependent,
// outputs / cache metadata
out_vdim,
out_op_dim,
out_offsets,
trial_vdim,
total_trial_op_dim,
residual_size_on_qp,
// vectors
xe,
cache_tensor,
// fallback arguments
dim,
q1d);
}
//////////////////////////////////////////////////////////////////
template<typename backend_t = LocalQFLOBackend<3>, int T_Q1D = 0>
static void
derivative_setup_callback(const IntegratorContext &ctx,
const qfunc_t &qfunc,
// inputs: idx, B, G, vdim, d1d, q1d
const std::array<size_t, n_inputs> &in_idx,
const std::array<const real_t *, n_inputs> in_B,
const std::array<const real_t *, n_inputs> in_G,
const std::array<int, n_inputs> &in_vdim,
const std::array<int, n_inputs> &in_d1d,
const std::array<int, n_inputs> &in_q1d,
const std::array<int, n_inputs> &in_size_on_qp,
const std::array<bool, n_inputs> &input_dep,
// outputs / cache metadata
const std::array<int, n_outputs> &out_vdim,
const std::array<int, n_outputs> &out_op_dim,
const std::array<int, n_outputs> &out_offsets,
const int trial_vdim,
const int total_trial_op_dim,
const int residual_size_on_qp,
const std::vector<Vector *> &xe,
DeviceTensor<3, real_t> &cache_tensor,
// fallback arguments
const int dim,
const int q1d)
{
MFEM_VERIFY(dim == ctx.mesh.Dimension(), "Dimension mismatch");
if (ctx.attr.Size() == 0) { return; }
static constexpr auto B2D = backend_t::DIM == 2;
static constexpr auto MQ1 = T_Q1D ? T_Q1D : backend_t::MQ1;
static constexpr auto MTPB = backend_t::MAX_THREADS_PER_BLOCK();
const int ne = ctx.nentities;
MFEM_CONTRACT_VAR(residual_size_on_qp);
constexpr auto k_dim = [](const int k) { return k * k * (B2D ? 1 : k); };
// --------------------------------------------------
// INPUTS: XE, 3(max DIM) + 1(VDIM) + 1(number of elements)
// --------------------------------------------------
std::array<DeviceTensor<3 + 1 + 1, const real_t>, n_inputs> in_XE;
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const size_t k = in_idx[i];
const int d = in_d1d[i], q = in_q1d[i], v = in_vdim[i];
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
MFEM_VERIFY(xe[k]->Size() == k_dim(d) * v * ne, "Size mismatch");
in_XE[i] = Reshape(xe[k]->Read(), d, d, B2D ? 1 : d, v, ne);
}
else if constexpr (is_identity_fop_v<FOP>)
{
MFEM_VERIFY(xe[k]->Size() == k_dim(q) * v * ne, "Size mismatch");
in_XE[i] = Reshape(xe[k]->Read(), v, q, q, B2D ? 1 : q, ne);
}
else if constexpr (is_weight_fop_v<FOP>)
{
MFEM_VERIFY(ctx.ir.GetNPoints() == k_dim(q1d),
"tensor-product IR expected");
in_XE[i] = Reshape(
ctx.ir.GetWeights().Read(), q1d, q1d, B2D ? 1 : q1d, 1, 1);
}
else
{
static_assert(false, "Unsupported");
}
});
const auto d_attr = ctx.attr.Read();
const bool has_attr = ctx.attr.Size() > 0;
const auto d_elem_attr = ctx.elem_attr->Read();
dfem::forall<MTPB>(
[=] MFEM_HOST_DEVICE(const int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
// -----------------------------------------------
// Inputs argument registers + shared memory
// -----------------------------------------------
args_reg_t<backend_t, qfunc_t, inputs_t, outputs_t, MQ1> rargs;
MFEM_SHARED typename backend_t::Shared smem;
// -----------------------------------------------
// Load primal inputs (rargs) once for this element
// -----------------------------------------------
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
const auto &XE = in_XE[i];
const int d = in_d1d[i], q = in_q1d[i], Q1D = q1d;
const real_t *B = in_B[i], *G = in_G[i];
auto &rarg = get<i>(rargs);
using XE_t = decltype(XE);
using rarg_t = decltype(rarg);
using FOP = tuple_element_t<i, inputs_t>;
if constexpr (is_value_fop<FOP>::value)
{
backend_t::template LoadValue<rarg_t, XE_t>(
smem, e, d, q, Q1D, B, XE, rarg);
}
else if constexpr (is_gradient_fop_v<FOP>)
{
constexpr auto RNK = qf_param_slot<qfunc_t, i>::extents.size();
using FieldParamT =
typename qf_param_slot<qfunc_t, i>::qf_decay_param_t;
backend_t::template LoadGradient<RNK, rarg_t, XE_t, FieldParamT>(
smem, e, d, q, q1d, B, G, XE, rarg);
}
else if constexpr (is_weight_fop_v<FOP> || is_identity_fop_v<FOP>)
{
// qp values are read directly from in_XE / IR
}
else
{
static_assert(false, "Unsupported");
}
});
MFEM_SYNC_THREAD;
// -----------------------------------------------
// For each trial seed (j, dependent input s, m), differentiate the
// q-function with a unit tangent and store the result row in the
// cache.
// -----------------------------------------------
for (int j = 0; j < trial_vdim; j++)
{
int m_offset = 0;
for_constexpr<n_inputs>([&](auto sc)
{
constexpr size_t s = sc.value;
if (!input_dep[s]) { return; }
const int vdim_s = in_vdim[s];
const int op_dim_s = in_size_on_qp[s] / vdim_s;
for (int m = 0; m < op_dim_s; m++)
{
const int col_m = m + m_offset;
const int seed_c = j + vdim_s * m;
MFEM_FOREACH_THREAD(qz, z, (B2D ? 1 : q1d))
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
#ifdef MFEM_USE_ENZYME
args_tuple_t primal_args {}, shadow_args {};
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
auto &parg = get<i>(primal_args);
const auto &XE = in_XE[i];
using FOP = tuple_element_t<i, inputs_t>;
using ARG =
typename qf_param_slot<qfunc_t,
i>::qf_reg_param_t;
if constexpr (is_identity_fop_v<FOP>)
{
parg = as_tensor<ARG>(&XE(0, qx, qy, qz, e));
}
else if constexpr (is_weight_fop_v<FOP>)
{
parg = XE(qx, qy, qz, 0, 0);
}
else if constexpr (is_value_fop_v<FOP> ||
is_gradient_fop_v<FOP>)
{
parg = backend_t::template qp_pull<ARG>(
get<i>(rargs), qx, qy, qz);
}
else
{
static_assert(false, "Unsupported");
}
});
qf_set_flat_value(get<s>(shadow_args), seed_c, 1.0);
call_enzyme_fwddiff(qfunc, primal_args, shadow_args);
for_constexpr<n_outputs>([&](auto oc)
{
constexpr size_t o = oc.value, ao = n_inputs + o;
const auto &tangent = get<ao>(shadow_args);
const int tv = out_vdim[o], to = out_op_dim[o];
for (int i = 0; i < tv; i++)
{
for (int k = 0; k < to; k++)
{
const int row = out_offsets[o] + i * to + k;
const int cache_idx =
row * trial_vdim * total_trial_op_dim +
j * total_trial_op_dim + col_m;
cache_tensor(cache_idx, q, e) =
qf_flat_value(tangent, i + tv * k);
}
}
});
#else // MFEM_USE_ENZYME
args_tuple_t qargs;
for_constexpr<n_inputs>([&](auto ic)
{
constexpr size_t i = ic.value;
auto &qarg = get<i>(qargs);
const auto &XE = in_XE[i];
using FOP = tuple_element_t<i, inputs_t>;
using ARG =
typename qf_param_slot<qfunc_t,
i>::qf_reg_param_t;
if constexpr (is_identity_fop_v<FOP>)
{
using DT =
typename qf_param_slot<qfunc_t,
i>::qf_decay_param_t;
if constexpr (qf_param_uses_dual_v<DT>)
{
qarg = backend_t::
template identity_qp_pull_dual<DT>(
false, XE, XE, qx, qy, qz, e);
}
else
{
qarg =
as_tensor<ARG>(&XE(0, qx, qy, qz, e));
}
}
else if constexpr (is_weight_fop_v<FOP>)
{
qarg = XE(qx, qy, qz, 0, 0);
}
else if constexpr (is_value_fop_v<FOP> ||
is_gradient_fop_v<FOP>)
{
qarg = backend_t::template qp_pull<ARG>(
get<i>(rargs), qx, qy, qz);
}
else
{
static_assert(false, "Unsupported");
}
});
qf_set_flat_gradient(get<s>(qargs), seed_c, 1.0);
call_qfunc_no_move(qfunc, qargs);
for_constexpr<n_outputs>([&](auto oc)
{
constexpr size_t o = oc.value, ao = n_inputs + o;
const auto &tangent = get<ao>(qargs);
const int tv = out_vdim[o], to = out_op_dim[o];
for (int i = 0; i < tv; i++)
{
for (int k = 0; k < to; k++)
{
const int row = out_offsets[o] + i * to + k;
const int cache_idx =
row * trial_vdim * total_trial_op_dim +
j * total_trial_op_dim + col_m;
cache_tensor(cache_idx, q, e) =
qf_flat_gradient(tangent, i + tv * k);
}
}
});
#endif // MFEM_USE_ENZYME
}
}
}
MFEM_SYNC_THREAD;
}
m_offset += op_dim_s;
});
}
},
ne,
backend_t::thread_blocks(
compute_kernel_thread_1d<inputs_t>(q1d, in_d1d)),
0,
nullptr);
}
using SetupKernelType =
decltype(&DerivativeSetup::derivative_setup_callback<>);
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeSetupLO,
SetupKernelType,
(int, int) );
MFEM_REGISTER_KERNELS_HEADER_ONLY(DerivativeSetupHO,
SetupKernelType,
(int, int) );
};
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>::
SetupKernelType
DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeSetupLO::Kernel()
{
static_assert((DIM == 2 || DIM == 3) && Q1D <= 8);
using setup_t = DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>;
return setup_t::template derivative_setup_callback<
LocalQFLOBackend<DIM, Q1D>>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>::
SetupKernelType
DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeSetupLO::Fallback(int dim, int q1d)
{
using setup_t = DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeSetupLO = typename setup_t::DerivativeSetupLO;
if (dim == 2)
{
return DispatchLOKernelByQ1D<DerivativeSetupLO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchLOKernelByQ1D<DerivativeSetupLO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
template<int DIM, int Q1D>
inline typename DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>::
SetupKernelType
DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeSetupHO::Kernel()
{
using setup_t = DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>;
return setup_t::template derivative_setup_callback<LocalQFHOBackend<DIM>,
Q1D>;
}
template<int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
inline typename DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>::
SetupKernelType
DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>::
DerivativeSetupHO::Fallback(int dim, int q1d)
{
using setup_t = DerivativeSetup<derivative_id, qfunc_t, inputs_t, outputs_t>;
using DerivativeSetupHO = typename setup_t::DerivativeSetupHO;
if (dim == 2)
{
return DispatchHOKernelByQ1D<DerivativeSetupHO, 2>(q1d);
}
else if (dim == 3)
{
return DispatchHOKernelByQ1D<DerivativeSetupHO, 3>(q1d);
}
else
{
MFEM_ABORT("Unsupported dimension");
return nullptr;
}
}
} // namespace mfem::future::LocalQFImpl
@@ -0,0 +1,274 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "../util.hpp"
#ifdef MFEM_USE_ENZYME
namespace mfem::future
{
template <typename T> struct function_traits;
template <typename R, typename C, typename... Args>
struct function_traits<R (C::*)(Args...) const>
{
using primal_return_type = R;
using args_tuple = std::tuple<Args...>;
static constexpr size_t arity = sizeof...(Args);
};
// Component count and writable counterpart of a per-point argument, which
// is either a tensor or a plain scalar.
template <typename Arg> struct qp_traits
{
static_assert(std::is_arithmetic_v<Arg>,
"per-point arguments must be tensors or scalars");
using view_type = Arg;
static constexpr int components = 1;
};
template <typename T, int... Sizes> struct qp_traits<tensor<T, Sizes...>>
{
using view_type = tensor<std::remove_const_t<T>, Sizes...>;
static constexpr int components = (Sizes * ... * 1);
};
// Generic FwdDiff: computes the full gradient of a pointwise qfunction at a
// single quadrature point.
//
// active_input is the index of the argument to differentiate with respect
// to; active_output is the index of the (scalar) output argument whose
// derivative is taken.
//
// operator()(args...) takes the qfunction's arguments, except that the
// active output position receives the *gradient*, shaped like the active
// input (d(output)/d(input component)).
//
// Per input component d, one enzyme fwddiff call with the one-hot seed e_d
// in the input tangent yields gradient entry d, i.e. grad_components enzyme
// calls per point. The output tangent is not pre-zeroed, so the qfunction
// must fully write its output (tangent stores overwrite).
//
// operator() is MFEM_HOST_DEVICE, stateless and allocation-free, so it is
// callable inside a GPU kernel: seed, primal scratch and tangent all live
// on the stack.
template <typename Func, size_t active_input, size_t active_output>
struct FwdDiff
{
using traits = function_traits<decltype(&Func::operator())>;
using args_tuple = typename traits::args_tuple;
static constexpr size_t arity = traits::arity;
static_assert(std::is_void_v<typename traits::primal_return_type>,
"FwdDiff only supports primal functions with void return type");
static_assert(active_input < arity && active_output < arity,
"active argument indices must be within the function arity");
static_assert(active_input != active_output,
"active input and output must be different arguments");
using input_type =
std::decay_t<std::tuple_element_t<active_input, args_tuple>>;
using output_type =
std::decay_t<std::tuple_element_t<active_output, args_tuple>>;
using grad_type = typename qp_traits<input_type>::view_type;
using output_view = typename qp_traits<output_type>::view_type;
static constexpr int grad_components = qp_traits<input_type>::components;
static_assert(qp_traits<output_type>::components == 1,
"gradient output requires a scalar output");
// Signature of the differentiated qfunction: the primal arguments, with
// the Active output slot receiving the (writable) gradient instead.
// Exposed through create_function_signature below so that
// DifferentiableOperator can deduce the parameter types, which it cannot
// do from the variadic operator().
template <size_t I>
using qf_arg_t = std::conditional_t<I == active_output, grad_type &,
std::tuple_element_t<I, args_tuple>>;
template <size_t... Is>
static FunctionSignature<void(qf_arg_t<Is>...)>
signature_impl(std::index_sequence<Is...>);
using signature =
decltype(signature_impl(std::make_index_sequence<arity> {}));
// d-th scalar of a per-point argument in flat row-major order, regardless
// of rank, built on the native operator[] (tensor has no flat-index
// accessor; flatten() returns a copy, so it cannot be written through).
MFEM_HOST_DEVICE static double &component(double &t, int) { return t; }
template <typename T, int n0, int... n>
MFEM_HOST_DEVICE static T &component(tensor<T, n0, n...> &t, int d)
{
if constexpr (sizeof...(n) == 0)
{
return t[d];
}
else
{
constexpr int stride = (n * ... * 1);
return component(t[d / stride], d % stride);
}
}
// Plain function with the qfunction's exact (reference) signature, so it
// can be handed to Enzyme as a function pointer; references are pointers
// to Enzyme, so primal arguments and shadows are passed by address below.
template <size_t... Is>
MFEM_HOST_DEVICE static void
static_call(std::tuple_element_t<Is, args_tuple>... args)
{
Func{}(args...);
}
template <size_t... Is>
static constexpr auto fn_ptr(std::index_sequence<Is...>)
{
return &static_call<Is...>;
}
static constexpr auto fn = fn_ptr(std::make_index_sequence<arity> {});
// Writable, zero-initialized scratch with the shape of argument I, used
// as its enzyme shadow.
template <size_t I>
using shadow_t = typename qp_traits<
std::decay_t<std::tuple_element_t<I, args_tuple>>>::view_type;
template <size_t... Is>
MFEM_HOST_DEVICE static auto make_shadows(std::index_sequence<Is...>)
{
return mfem::future::make_tuple(shadow_t<Is> {}...);
}
template <typename Shadows, size_t... Is>
MFEM_HOST_DEVICE static auto make_shadow_ptrs(Shadows &shadows,
std::index_sequence<Is...>)
{
return mfem::future::make_tuple(&mfem::future::get<int(Is)>(shadows)...);
}
// The caller's argument pointers, except the active output slot, which
// points to scalar scratch: the caller's slot holds the gradient, while
// the primal function writes its scalar output there.
template <size_t I, typename Ptrs>
MFEM_HOST_DEVICE static auto primal_ptr(Ptrs &ptrs, output_view &primal)
{
if constexpr (I == active_output) { return &primal; }
else { return mfem::future::get<int(I)>(ptrs); }
}
template <typename Ptrs, size_t... Is>
MFEM_HOST_DEVICE static auto make_primal_ptrs(Ptrs &ptrs,
output_view &primal,
std::index_sequence<Is...>)
{
return mfem::future::make_tuple(primal_ptr<Is>(ptrs, primal)...);
}
// Single flat enzyme call. The activity markers must appear directly in
// the __enzyme_fwddiff argument list — Enzyme cannot trace markers that
// were forwarded through function parameters (e.g. at -O0, where nothing
// is inlined). Every argument is therefore enzyme_dup'd in one sticky
// group; Const arguments simply carry a zero tangent, which is equivalent
// to marking them enzyme_const.
//
// always_inline is load-bearing: when FwdDiff is itself differentiated
// (second derivatives, forward-over-forward), Enzyme only recognizes this
// nested __enzyme_fwddiff call if it sits at most one call level below
// the function handed to the outer __enzyme_fwddiff. Without inlining
// (-O0) it sits two levels down (wrapper -> operator() -> call_enzyme)
// and the outer pass treats it as a regular call: the activity marker
// ints then receive undef shadows, which misaligns the argument pairing
// (observed as "cannot compute with global variable that doesn't have
// marked shadow global" at compile time or null-shadow segfaults at
// runtime). The always-inliner runs even at -O0, hoisting this call into
// operator() where the nested handling applies.
template <typename PrimalPtrs, typename ShadowPtrs, size_t... Is>
__attribute__((always_inline))
MFEM_HOST_DEVICE static void call_enzyme(PrimalPtrs &primal_ptrs,
ShadowPtrs &shadow_ptrs,
std::index_sequence<Is...>)
{
__enzyme_fwddiff<void>(fn, enzyme_dup,
mfem::future::get<int(Is)>(primal_ptrs)...,
enzyme_interleave,
mfem::future::get<int(Is)>(shadow_ptrs)...,
enzyme_runtime_activity);
}
template <typename... Args>
MFEM_HOST_DEVICE void operator()(Args &&...args) const
{
static_assert(sizeof...(Args) == arity, "Wrong number of arguments");
auto ptrs = mfem::future::make_tuple(&args...);
auto &grad = *mfem::future::get<int(active_output)>(ptrs);
static_assert(std::is_same_v<std::decay_t<decltype(grad)>, grad_type>,
"gradient argument must be shaped like the Active input "
"(with writable scalars)");
constexpr auto seq = std::make_index_sequence<arity> {};
output_view primal{};
auto primal_ptrs = make_primal_ptrs(ptrs, primal, seq);
auto shadows = make_shadows(seq);
auto shadow_ptrs = make_shadow_ptrs(shadows, seq);
auto &seed = mfem::future::get<int(active_input)>(shadows);
auto &tangent = mfem::future::get<int(active_output)>(shadows);
// One enzyme call per input component d: seed e_d in the input tangent
// and read gradient entry d off the output tangent.
for (int d = 0; d < grad_components; d++)
{
component(seed, d) = 1.0;
call_enzyme(primal_ptrs, shadow_ptrs, seq);
component(grad, d) = component(tangent, 0);
component(seed, d) = 0.0;
}
}
static void print() { print_impl(std::make_index_sequence<arity> {}); }
template <size_t... Is> static void print_impl(std::index_sequence<Is...>)
{
mfem::out << "for d in [0, " << grad_components
<< "): __enzyme_fwddiff<void>(fptr, enzyme_dup";
((mfem::out << ", "
<< get_type_name<std::tuple_element_t<Is, args_tuple>>()),
...);
mfem::out << ", enzyme_interleave";
(([&]
{
if constexpr (Is == active_input) { mfem::out << ", e_d seed"; }
else if constexpr (Is == active_output) { mfem::out << ", tangent out"; }
else { mfem::out << ", zero tangent"; }
}()),
...);
mfem::out << ")\n";
}
};
template <typename Func, size_t active_input, size_t active_output>
struct create_function_signature<FwdDiff<Func, active_input, active_output>>
{
using type =
typename FwdDiff<Func, active_input, active_output>::signature;
};
} // namespace mfem::future
#endif // MFEM_USE_ENZYME
+83
View File
@@ -0,0 +1,83 @@
// 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.
// Explicit instantiation of the local Q-function backend device code
#include "../../../../config/config.hpp"
#ifdef MFEM_USE_MPI
#include "kernels.hpp"
namespace mfem::future
{
// ────────────────────────────────────────────────────────────────────────────
// Low-order backends instantiations for (DIM, Q1D)
// ────────────────────────────────────────────────────────────────────────────
template struct lo_ker_backend<2, 2>;
template struct lo_ker_backend<2, 3>;
template struct lo_ker_backend<2, 4>;
template struct lo_ker_backend<2, 5>;
template struct lo_ker_backend<2, 6>;
template struct lo_ker_backend<2, 7>;
template struct lo_ker_backend<2, 8>;
template struct lo_ker_backend<3, 2>;
template struct lo_ker_backend<3, 3>;
template struct lo_ker_backend<3, 4>;
template struct lo_ker_backend<3, 5>;
template struct lo_ker_backend<3, 6>;
template struct lo_ker_backend<3, 7>;
template struct lo_ker_backend<3, 8>;
template struct LocalQFLOBackend<2, 2>;
template struct LocalQFLOBackend<2, 3>;
template struct LocalQFLOBackend<2, 4>;
template struct LocalQFLOBackend<2, 5>;
template struct LocalQFLOBackend<2, 6>;
template struct LocalQFLOBackend<2, 7>;
template struct LocalQFLOBackend<2, 8>;
template struct LocalQFLOBackend<3, 2>;
template struct LocalQFLOBackend<3, 3>;
template struct LocalQFLOBackend<3, 4>;
template struct LocalQFLOBackend<3, 5>;
template struct LocalQFLOBackend<3, 6>;
template struct LocalQFLOBackend<3, 7>;
template struct LocalQFLOBackend<3, 8>;
// ────────────────────────────────────────────────────────────────────────────
// High-order backends instantiations for (DIM, Q1D)
// ────────────────────────────────────────────────────────────────────────────
template struct ho_ker_backend<2, 8>;
template struct ho_ker_backend<2, 10>;
template struct ho_ker_backend<2, 12>;
template struct ho_ker_backend<2, 16>;
template struct ho_ker_backend<3, 8>;
template struct ho_ker_backend<3, 10>;
template struct ho_ker_backend<3, 12>;
template struct ho_ker_backend<3, 16>;
template struct LocalQFHOBackend<2, 8>;
template struct LocalQFHOBackend<2, 10>;
template struct LocalQFHOBackend<2, 12>;
template struct LocalQFHOBackend<2, 16>;
template struct LocalQFHOBackend<3, 8>;
template struct LocalQFHOBackend<3, 10>;
template struct LocalQFHOBackend<3, 12>;
template struct LocalQFHOBackend<3, 16>;
} // namespace mfem::future
#endif // MFEM_USE_MPI
+77
View File
@@ -0,0 +1,77 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "kernels_lo.hpp" // IWYU pragma: export
#include "kernels_ho.hpp" // IWYU pragma: export
namespace mfem::future
{
// ────────────────────────────────────────────────────────────────────────────
// Low-order backends instantiations for (DIM, Q1D)
// ────────────────────────────────────────────────────────────────────────────
extern template struct lo_ker_backend<2, 2>;
extern template struct lo_ker_backend<2, 3>;
extern template struct lo_ker_backend<2, 4>;
extern template struct lo_ker_backend<2, 5>;
extern template struct lo_ker_backend<2, 6>;
extern template struct lo_ker_backend<2, 7>;
extern template struct lo_ker_backend<2, 8>;
extern template struct lo_ker_backend<3, 2>;
extern template struct lo_ker_backend<3, 3>;
extern template struct lo_ker_backend<3, 4>;
extern template struct lo_ker_backend<3, 5>;
extern template struct lo_ker_backend<3, 6>;
extern template struct lo_ker_backend<3, 7>;
extern template struct lo_ker_backend<3, 8>;
extern template struct LocalQFLOBackend<2, 2>;
extern template struct LocalQFLOBackend<2, 3>;
extern template struct LocalQFLOBackend<2, 4>;
extern template struct LocalQFLOBackend<2, 5>;
extern template struct LocalQFLOBackend<2, 6>;
extern template struct LocalQFLOBackend<2, 7>;
extern template struct LocalQFLOBackend<2, 8>;
extern template struct LocalQFLOBackend<3, 2>;
extern template struct LocalQFLOBackend<3, 3>;
extern template struct LocalQFLOBackend<3, 4>;
extern template struct LocalQFLOBackend<3, 5>;
extern template struct LocalQFLOBackend<3, 6>;
extern template struct LocalQFLOBackend<3, 7>;
extern template struct LocalQFLOBackend<3, 8>;
// ────────────────────────────────────────────────────────────────────────────
// High-order backends instantiations for (DIM, Q1D)
// ────────────────────────────────────────────────────────────────────────────
extern template struct ho_ker_backend<2, 8>;
extern template struct ho_ker_backend<2, 10>;
extern template struct ho_ker_backend<2, 12>;
extern template struct ho_ker_backend<2, 16>;
extern template struct ho_ker_backend<3, 8>;
extern template struct ho_ker_backend<3, 10>;
extern template struct ho_ker_backend<3, 12>;
extern template struct ho_ker_backend<3, 16>;
extern template struct LocalQFHOBackend<2, 8>;
extern template struct LocalQFHOBackend<2, 10>;
extern template struct LocalQFHOBackend<2, 12>;
extern template struct LocalQFHOBackend<2, 16>;
extern template struct LocalQFHOBackend<3, 8>;
extern template struct LocalQFHOBackend<3, 10>;
extern template struct LocalQFHOBackend<3, 12>;
extern template struct LocalQFHOBackend<3, 16>;
} // namespace mfem::future
+803
View File
@@ -0,0 +1,803 @@
// 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 "../../../kernels.hpp"
namespace ker = mfem::kernels::internal;
#include "../../util.hpp" // for ThreadBlocks
#include "util.hpp"
namespace mfem::future
{
// ────────────────────────────────────────────────────────────────────────────
inline constexpr int LocalQFHOBackendMQ1() { return 16; }
// ────────────────────────────────────────────────────────────────────────────
/// Register type for one HO q-function parameter
template<typename KerOps, typename T, int rank = qf_param_shape<T>::rank>
struct ho_qreg;
template<typename KerOps, typename T>
struct ho_qreg<KerOps, T, 0>
{
using type = typename KerOps::template val_reg_t<1>;
};
template<typename KerOps, typename T>
struct ho_qreg<KerOps, T, 1>
{
static constexpr int e0 = qf_param_shape<T>::extents[0];
using type = typename KerOps::template val_reg_t<e0>;
};
template<typename KerOps, typename T>
struct ho_qreg<KerOps, T, 2>
{
static constexpr int VDIM = qf_param_shape<T>::extents[0];
static constexpr int SDIM = qf_param_shape<T>::extents[1];
using type = typename KerOps::template del_reg_t<VDIM, SDIM>;
};
template<typename KerOps, typename T>
using ho_qreg_t = typename ho_qreg<KerOps, T>::type;
// ────────────────────────────────────────────────────────────────────────────
namespace hok
{
/// Load one quadrature-point value
template<int DIM, typename T, typename Reg>
MFEM_HOST_DEVICE inline auto load_at(Reg &reg, int qx, int qy, int qz)
{
static_assert(DIM == 2 || DIM == 3);
constexpr int RNK = qf_param_shape<T>::rank;
if constexpr (DIM == 2)
{
MFEM_CONTRACT_VAR(qz);
if constexpr (RNK == 0) { return T{ reg(0, qy, qx) }; }
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
T t{};
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd) { t(dd) = reg(dd, qy, qx); }
return t;
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
T t;
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j) { t(i, j) = reg(i, j, qy, qx); }
}
return t;
}
}
else
{
if constexpr (RNK == 0) { return T{ reg(0, qz, qy, qx) }; }
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
T t{};
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd) { t(dd) = reg(dd, qz, qy, qx); }
return t;
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
T t;
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j) { t(i, j) = reg(i, j, qz, qy, qx); }
}
return t;
}
}
}
template<bool tangent, typename U>
MFEM_HOST_DEVICE inline auto qp_store(const U &v)
{
if constexpr (tangent) { return qf_store_gradient(v); }
else
{
return qf_store_value(v);
}
}
// Store primal value or dual tangent at one quadrature point
template<int DIM, typename T, typename Reg, bool tangent>
MFEM_HOST_DEVICE inline void
store_at(Reg &reg, int qx, int qy, int qz, const T &out)
{
static_assert(DIM == 2 || DIM == 3);
constexpr int RNK = qf_param_shape<T>::rank;
if constexpr (DIM == 2)
{
MFEM_CONTRACT_VAR(qz);
if constexpr (RNK == 0) { reg(0, qy, qx) = qp_store<tangent>(out); }
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
reg(dd, qy, qx) = qp_store<tangent>(out(dd));
}
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
reg(i, j, qy, qx) = qp_store<tangent>(out(i, j));
}
}
}
}
else
{
if constexpr (RNK == 0) { reg(0, qz, qy, qx) = qp_store<tangent>(out); }
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
reg(dd, qz, qy, qx) = qp_store<tangent>(out(dd));
}
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
reg(i, j, qz, qy, qx) = qp_store<tangent>(out(i, j));
}
}
}
}
}
// Pull primal/tangent pair into a dual q-function argument
template<int DIM, typename T, typename Reg>
MFEM_HOST_DEVICE inline auto
pull_directional(Reg &preg, Reg &sreg, int qx, int qy, int qz, bool dependent)
{
if constexpr (!qf_param_uses_dual_v<T>)
{
return load_at<DIM, T>(preg, qx, qy, qz);
}
else
{
if (!dependent) { return load_at<DIM, T>(preg, qx, qy, qz); }
constexpr int RNK = qf_param_shape<T>::rank;
if constexpr (DIM == 2)
{
MFEM_CONTRACT_VAR(qz);
if constexpr (RNK == 0)
{
return T{ preg(0, qy, qx), sreg(0, qy, qx) };
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
T t{};
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
t(dd) = { preg(dd, qy, qx), sreg(dd, qy, qx) };
}
return t;
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
T t;
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
t(i, j) = { preg(i, j, qy, qx), sreg(i, j, qy, qx) };
}
}
return t;
}
}
else
{
if constexpr (RNK == 0)
{
return T{ preg(0, qz, qy, qx), sreg(0, qz, qy, qx) };
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
T t{};
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
t(dd) = { preg(dd, qz, qy, qx), sreg(dd, qz, qy, qx) };
}
return t;
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
T t;
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
t(i, j) = { preg(i, j, qz, qy, qx), sreg(i, j, qz, qy, qx) };
}
}
return t;
}
}
}
}
} // namespace hok
// ────────────────────────────────────────────────────────────────────────────
/// HO tensor-product kernels
template<int T_DIM, int MQ1>
struct ho_ker_backend
{
static constexpr int DIM = T_DIM;
static_assert(DIM == 2 || DIM == 3);
template<int VDIM>
using val_reg_t = std::conditional_t<(DIM == 2),
ker::v_regs2d_t<VDIM, MQ1>,
ker::v_regs3d_t<VDIM, MQ1>>;
template<int VDIM, int SDIM>
using del_reg_t = std::conditional_t<(DIM == 2),
ker::vd_regs2d_t<VDIM, SDIM, MQ1>,
ker::vd_regs3d_t<VDIM, SDIM, MQ1>>;
struct Shared
{
real_t M[MQ1][MQ1], B[MQ1][MQ1], G[MQ1][MQ1];
};
template<typename XE_t, typename Dofs>
static MFEM_HOST_DEVICE void
load_dofs(const int e, const int d, const XE_t &XE, Dofs &dofs)
{
if constexpr (DIM == 2) { ker::LoadDofs2d(e, d, XE, dofs); }
else
{
ker::LoadDofs3d(e, d, XE, dofs);
}
}
template<int VDIM, int SDIM, typename XE_t, typename Dofs>
static MFEM_HOST_DEVICE void
load_grad_dofs(const int e, const int d, const XE_t &XE, Dofs &dofs)
{
static_assert(SDIM == DIM, "gradient spatial dim must match kernel DIM");
load_dofs(e, d, XE, dofs);
}
template<typename Smem, typename Dofs, typename ArgReg>
static MFEM_HOST_DEVICE void
eval_value(const int d, const int q, Smem &s, Dofs &dofs, ArgReg &rarg)
{
if constexpr (DIM == 2) { ker::Eval2d(d, q, s.M, s.B, dofs, rarg); }
else
{
ker::Eval3d(d, q, s.M, s.B, dofs, rarg);
}
}
template<int VDIM, int SDIM, typename Smem, typename Dofs, typename ArgReg>
static MFEM_HOST_DEVICE void
grad(const int d, const int q, Smem &s, Dofs &dofs, ArgReg &rarg)
{
static_assert(SDIM == DIM, "gradient spatial dim must match kernel DIM");
if constexpr (DIM == 2) { ker::Grad2d(d, q, s.M, s.B, s.G, dofs, rarg); }
else
{
ker::Grad3d(d, q, s.M, s.B, s.G, dofs, rarg);
}
}
template<typename Smem, typename Dofs, typename ArgReg, typename YE_t>
static MFEM_HOST_DEVICE void write_value(const int d,
const int q,
const int e,
Smem &s,
ArgReg &rarg,
Dofs &dofs,
YE_t &YE)
{
if constexpr (DIM == 2)
{
ker::EvalTranspose2d(d, q, s.M, s.B, rarg, dofs);
ker::WriteDofs2d(e, d, dofs, YE);
}
else
{
ker::EvalTranspose3d(d, q, s.M, s.B, rarg, dofs);
ker::WriteDofs3d(e, d, dofs, YE);
}
}
template<typename Smem, typename Dofs, typename ArgReg, typename YE_t>
static MFEM_HOST_DEVICE void write_gradient_2d(const int d,
const int q,
const int e,
Smem &s,
ArgReg &rarg,
Dofs &dofs,
YE_t &YE)
{
ker::GradTranspose2d(d, q, s.M, s.B, s.G, rarg, dofs);
ker::WriteDofs2d(e, d, dofs, YE);
}
template<typename Smem, typename Dofs, typename ArgReg, typename YE_t>
static MFEM_HOST_DEVICE void write_gradient_3d(const int d,
const int q,
const int e,
Smem &s,
ArgReg &rarg,
Dofs &dofs,
YE_t &YE)
{
ker::GradTranspose3d(d, q, s.M, s.B, s.G, rarg, dofs);
ker::WriteDofs3d(e, d, dofs, YE);
}
template<int VDIM,
int SDIM,
typename Smem,
typename Dofs,
typename ArgReg,
typename YE_t>
static MFEM_HOST_DEVICE void write_gradient(const int d,
const int q,
const int e,
Smem &s,
ArgReg &rarg,
Dofs &dofs,
YE_t &YE)
{
static_assert(SDIM == DIM, "gradient spatial dim must match kernel DIM");
if constexpr (DIM == 2) { write_gradient_2d(d, q, e, s, rarg, dofs, YE); }
else
{
write_gradient_3d(d, q, e, s, rarg, dofs, YE);
}
}
};
// ────────────────────────────────────────────────────────────────────────────
template<int T_DIM, int T_Q1D = LocalQFHOBackendMQ1()>
struct LocalQFHOBackend
{
// ─────────────────────────────────────────────────────
static constexpr int DIM = T_DIM, MQ1 = T_Q1D, Q1D = T_Q1D;
static_assert(DIM == 2 || DIM == 3);
// ─────────────────────────────────────────────────────
static inline ThreadBlocks thread_blocks(const int q1d)
{
MFEM_ASSERT(q1d <= Q1D, "q1d must be <= " << Q1D);
return { q1d, q1d, 1 };
}
// ─────────────────────────────────────────────────────
static inline constexpr int MAX_THREADS_PER_BLOCK() { return Q1D * Q1D; }
// ─────────────────────────────────────────────────────
using backend_t = ho_ker_backend<DIM, Q1D>;
// ─────────────────────────────────────────────────────
using Shared = typename backend_t::Shared;
// ─────────────────────────────────────────────────────
template<typename WT, typename WI, typename Cache, typename AddY>
static MFEM_HOST_DEVICE inline void DiagContract(Shared &s,
const int num_dof_1d,
const int q1d,
const int nz_dof,
WT wt,
WI wi,
Cache cache,
AddY add_y)
{
MFEM_CONTRACT_VAR(nz_dof);
const int nqz = (DIM == 3) ? q1d : 1;
const int ndz = (DIM == 3) ? num_dof_1d : 1;
ker::s_regs3d_t<MQ1> rz, ry;
auto &smem = s.M;
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
for (int dz = 0; dz < ndz; dz++)
{
real_t u = 0.0;
for (int qz = 0; qz < nqz; qz++)
{
const int q = qx + (qy + qz * q1d) * q1d;
const real_t wz =
(DIM == 3) ? (wt(2, qz, dz) * wi(2, qz, dz)) : real_t(1);
u += wz * cache(q);
}
rz[dz][qy][qx] = u;
}
}
}
MFEM_SYNC_THREAD;
for (int dz = 0; dz < ndz; dz++)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{ smem[qy][qx] = rz[dz][qy][qx]; }
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD_DIRECT(dy, y, num_dof_1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
real_t u = 0.0;
for (int qy = 0; qy < q1d; qy++)
{
u += wt(1, qy, dy) * wi(1, qy, dy) * smem[qy][qx];
}
ry[dz][dy][qx] = u;
}
}
MFEM_SYNC_THREAD;
}
for (int dz = 0; dz < ndz; dz++)
{
MFEM_FOREACH_THREAD_DIRECT(dy, y, num_dof_1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{ smem[dy][qx] = ry[dz][dy][qx]; }
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD_DIRECT(dy, y, num_dof_1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx, x, num_dof_1d)
{
real_t u = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
u += wt(0, qx, dx) * wi(0, qx, dx) * smem[dy][qx];
}
add_y(dx, dy, dz, u);
}
}
MFEM_SYNC_THREAD;
}
}
// ─────────────────────────────────────────────────────
template<typename T>
using QReg = ho_qreg_t<backend_t, T>;
// ─────────────────────────────────────────────────────
template<typename ArgRegT, typename XE_T>
static inline MFEM_HOST_DEVICE void LoadValue(Shared &s,
const int e,
const int d,
const int q,
const int,
const real_t *B,
const XE_T &XE,
ArgRegT &rarg)
{
ker::LoadMatrix(d, q, B, s.B);
std::remove_reference_t<ArgRegT> dofs;
backend_t::load_dofs(e, d, XE, dofs);
backend_t::eval_value(d, q, s, dofs, rarg);
}
// ─────────────────────────────────────────────────────
template<int RNK,
typename ArgRegT,
typename XE_T,
typename FieldParamT = ArgRegT>
static inline MFEM_HOST_DEVICE void LoadGradient(Shared &s,
const int e,
const int d,
const int q,
const int,
const real_t *B,
const real_t *G,
const XE_T &XE,
ArgRegT &rarg)
{
ker::LoadMatrix(d, q, B, s.B);
ker::LoadMatrix(d, q, G, s.G);
static_assert(RNK == 1 || RNK == 2);
static constexpr int VDIM =
(RNK == 1) ? 1 : qf_param_shape<FieldParamT>::extents[0];
static constexpr int SDIM = (RNK == 1)
? qf_param_shape<FieldParamT>::extents[0]
: qf_param_shape<FieldParamT>::extents[1];
if constexpr (SDIM == DIM)
{
typename backend_t::template del_reg_t<VDIM, SDIM> dofs;
if constexpr (RNK == 1) { backend_t::load_dofs(e, d, XE, dofs); }
else
{
backend_t::template load_grad_dofs<VDIM, SDIM>(e, d, XE, dofs);
}
backend_t::template grad<VDIM, SDIM>(d, q, s, dofs, rarg);
}
}
// ─────────────────────────────────────────────────────
template<typename T>
static MFEM_HOST_DEVICE inline auto
qp_pull(QReg<T> &reg, int qx, int qy, int qz)
{ return hok::load_at<DIM, T>(reg, qx, qy, qz); }
// ─────────────────────────────────────────────────────
template<typename T>
static MFEM_HOST_DEVICE inline auto qp_pull_directional(
QReg<T> &preg, QReg<T> &sreg, int qx, int qy, int qz, bool dependent)
{ return hok::pull_directional<DIM, T>(preg, sreg, qx, qy, qz, dependent); }
// ─────────────────────────────────────────────────────
template<typename DT, typename XE_T>
static MFEM_HOST_DEVICE inline DT identity_qp_pull_dual(bool dependent,
const XE_T &XP,
const XE_T &XD,
int qx,
int qy,
int qz,
int e)
{
constexpr int RNK = qf_param_shape<DT>::rank;
if constexpr (RNK == 0)
{
DT t{};
t.value = XP(0, qx, qy, qz, e);
t.gradient = dependent ? XD(0, qx, qy, qz, e) : 0.0;
return t;
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
DT t{};
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
t(dd).value = XP(dd, qx, qy, qz, e);
t(dd).gradient = dependent ? XD(dd, qx, qy, qz, e) : 0.0;
}
return t;
}
else if constexpr (RNK == 2)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
constexpr int e1 = qf_param_shape<DT>::extents[1];
DT t{};
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
t(i, j).value = XP(i + e0 * j, qx, qy, qz, e);
t(i, j).gradient =
dependent ? XD(i + e0 * j, qx, qy, qz, e) : 0.0;
}
}
return t;
}
else
{
static_assert(false, "Unsupported");
}
}
// ─────────────────────────────────────────────────────
template<typename T>
static MFEM_HOST_DEVICE inline void
qp_push(QReg<T> &reg, int qx, int qy, int qz, const T &out)
{ hok::store_at<DIM, T, decltype(reg), false>(reg, qx, qy, qz, out); }
// ─────────────────────────────────────────────────────
template<typename T>
static MFEM_HOST_DEVICE inline void
qp_push_tangent(QReg<T> &reg, int qx, int qy, int qz, const T &out)
{
hok::store_at<DIM, T, decltype(reg), qf_param_uses_dual_v<T>>(
reg, qx, qy, qz, out);
}
// ─────────────────────────────────────────────────────
template<typename DT, typename YE_T>
static MFEM_HOST_DEVICE inline void identity_qp_write_value(
YE_T &YE, int qx, int qy, int qz, int e, const DT &qout)
{
constexpr int RNK = qf_param_shape<DT>::rank;
if constexpr (qf_param_uses_dual_v<DT>)
{
if constexpr (RNK == 0)
{
YE(0, qx, qy, qz, e) = qf_store_value(qout);
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
YE(dd, qx, qy, qz, e) = qf_store_value(qout(dd));
}
}
else if constexpr (RNK == 2)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
constexpr int e1 = qf_param_shape<DT>::extents[1];
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
YE(i + e0 * j, qx, qy, qz, e) = qf_store_value(qout(i, j));
}
}
}
else
{
static_assert(false, "Unsupported");
}
}
}
// ─────────────────────────────────────────────────────
template<typename DT, typename YE_T>
static MFEM_HOST_DEVICE inline void identity_qp_write_tangent(
YE_T &YE, int qx, int qy, int qz, int e, const DT &qout)
{
constexpr int RNK = qf_param_shape<DT>::rank;
if constexpr (qf_param_uses_dual_v<DT>)
{
if constexpr (RNK == 0)
{
YE(0, qx, qy, qz, e) = qf_store_gradient(qout);
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
YE(dd, qx, qy, qz, e) = qf_store_gradient(qout(dd));
}
}
else if constexpr (RNK == 2)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
constexpr int e1 = qf_param_shape<DT>::extents[1];
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
YE(i + e0 * j, qx, qy, qz, e) = qf_store_gradient(qout(i, j));
}
}
}
else
{
static_assert(false, "Unsupported");
}
}
}
// ─────────────────────────────────────────────────────
template<typename ArgRegT, typename YE_T>
static inline MFEM_HOST_DEVICE void WriteValue(Shared &s,
const int e,
const int d,
const int q,
const int,
const real_t *B,
YE_T &YE,
ArgRegT &rarg)
{
ker::LoadMatrix(d, q, B, s.B);
std::remove_reference_t<ArgRegT> dofs;
backend_t::write_value(d, q, e, s, rarg, dofs, YE);
}
// ─────────────────────────────────────────────────────
template<int RNK,
typename ArgRegT,
typename YE_T,
typename FieldParamT = ArgRegT>
static inline MFEM_HOST_DEVICE void WriteGradient(Shared &s,
const int e,
const int d,
const int q,
const int,
const real_t *B,
const real_t *G,
YE_T &YE,
ArgRegT &rarg)
{
ker::LoadMatrix(d, q, B, s.B);
ker::LoadMatrix(d, q, G, s.G);
static_assert(RNK == 1 || RNK == 2);
static constexpr int VDIM =
(RNK == 1) ? 1 : qf_param_shape<FieldParamT>::extents[0];
static constexpr int SDIM = (RNK == 1)
? qf_param_shape<FieldParamT>::extents[0]
: qf_param_shape<FieldParamT>::extents[1];
if constexpr (SDIM == DIM)
{
typename backend_t::template del_reg_t<VDIM, SDIM> dofs;
backend_t::template write_gradient<VDIM, SDIM>(
d, q, e, s, rarg, dofs, YE);
}
}
};
/// @brief Dispatch to a compile-time HO kernel with MQ1 >= runtime @a q1d.
template <typename HOKernelTable, int DIM, int MQ1 = LocalQFHOBackendMQ1()>
inline typename HOKernelTable::KernelSignature
DispatchHOKernelByQ1D(int q1d)
{
MFEM_VERIFY(q1d >= 2 && q1d <= MQ1,
"Unsupported HO quadrature order: " << q1d);
return HOKernelTable::template Kernel<DIM, MQ1>();
}
} // namespace mfem::future
+813
View File
@@ -0,0 +1,813 @@
// 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 "../../../kernels.hpp"
namespace ker = mfem::kernels::internal;
#include "../../util.hpp" // for ThreadBlocks
#include "../util.hpp" // for as_tensor
#include "util.hpp"
namespace mfem::future
{
// ────────────────────────────────────────────────────────────────────────────
inline constexpr int LocalQFLOBackendMQ1() { return 8; }
// ────────────────────────────────────────────────────────────────────────────
/// Register type for one LO q-function parameter
template<typename KerOps, typename T, int rank = qf_param_shape<T>::rank>
struct lo_qreg;
template<typename KerOps, typename T>
struct lo_qreg<KerOps, T, 0>
{
using type = typename KerOps::template qreg_t<1>;
};
template<typename KerOps, typename T>
struct lo_qreg<KerOps, T, 1>
{
static constexpr int e0 = qf_param_shape<T>::extents[0];
using type = typename KerOps::template qreg_t<e0>;
};
template<typename KerOps, typename T>
struct lo_qreg<KerOps, T, 2>
{
static constexpr int e0 = qf_param_shape<T>::extents[0];
static constexpr int e1 = qf_param_shape<T>::extents[1];
using type = typename KerOps::template qreg_vd_t<e0, e1>;
};
template<typename KerOps, typename T>
using lo_qreg_t = typename lo_qreg<KerOps, T>::type;
// ────────────────────────────────────────────────────────────────────────────
namespace lok
{
template<int DIM, typename Reg>
MFEM_HOST_DEVICE inline auto &at(Reg &reg, int qx, int qy, int qz)
{
if constexpr (DIM == 2)
{
MFEM_CONTRACT_VAR(qz);
return reg[qy][qx];
}
else
{
return reg[qz][qy][qx];
}
}
template<int DIM, typename T, typename Reg>
MFEM_HOST_DEVICE inline auto load_at(Reg &reg, int qx, int qy, int qz)
{
constexpr int RNK = qf_param_shape<T>::rank;
auto &qp = at<DIM>(reg, qx, qy, qz);
if constexpr (RNK == 0) { return T{ qp[0] }; }
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
T t{};
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd) { t(dd) = qp[dd]; }
return t;
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
T t;
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j) { t(i, j) = qp[i][j]; }
}
return t;
}
}
template<bool tangent, typename U>
MFEM_HOST_DEVICE inline auto qp_store(const U &v)
{
if constexpr (tangent) { return qf_store_gradient(v); }
else
{
return qf_store_value(v);
}
}
// Store primal value or dual tangent at one quadrature point
template<int DIM, typename T, typename Reg, bool tangent>
MFEM_HOST_DEVICE inline void
store_at(Reg &reg, int qx, int qy, int qz, const T &out)
{
constexpr int RNK = qf_param_shape<T>::rank;
auto &qp = at<DIM>(reg, qx, qy, qz);
if constexpr (RNK == 0) { qp[0] = qp_store<tangent>(out); }
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd) { qp[dd] = qp_store<tangent>(out(dd)); }
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
qp[i][j] = qp_store<tangent>(out(i, j));
}
}
}
}
// Pull primal/tangent pair into a dual q-function argument
template<int DIM, typename T, typename Reg>
MFEM_HOST_DEVICE inline auto
pull_directional(Reg &preg, Reg &sreg, int qx, int qy, int qz, bool dependent)
{
if constexpr (!qf_param_uses_dual_v<T>)
{
return load_at<DIM, T>(preg, qx, qy, qz);
}
else
{
if (!dependent) { return load_at<DIM, T>(preg, qx, qy, qz); }
constexpr int RNK = qf_param_shape<T>::rank;
auto &pqp = at<DIM>(preg, qx, qy, qz);
auto &sqp = at<DIM>(sreg, qx, qy, qz);
if constexpr (RNK == 0) { return T{ pqp[0], sqp[0] }; }
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
T t{};
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd) { t(dd) = { pqp[dd], sqp[dd] }; }
return t;
}
else
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
T t;
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j) { t(i, j) = { pqp[i][j], sqp[i][j] }; }
}
return t;
}
}
}
} // namespace lok
// ────────────────────────────────────────────────────────────────────────────
/// LO tensor-product kernels
template<int T_DIM, int MQ1>
struct lo_ker_backend
{
static constexpr int DIM = T_DIM;
static_assert(DIM == 2 || DIM == 3);
template<int VDIM>
using qreg_t = std::conditional_t<(DIM == 2),
ker::regs2d_t<VDIM, MQ1>,
ker::regs3d_t<VDIM, MQ1>>;
template<int VDIM, int SDIM>
using qreg_vd_t = std::conditional_t<(DIM == 2),
ker::regs2d_vd_t<VDIM, SDIM, MQ1>,
ker::regs3d_vd_t<VDIM, SDIM, MQ1>>;
struct Shared2d
{
real_t M[2][MQ1][MQ1][DIM];
real_t B[MQ1][MQ1], G[MQ1][MQ1];
};
struct Shared3d
{
real_t M[2][MQ1][MQ1][MQ1][DIM];
real_t B[MQ1][MQ1], G[MQ1][MQ1];
};
using Shared = std::conditional_t<(DIM == 2), Shared2d, Shared3d>;
template<typename FieldParamT, typename ArgRegT, typename XE_T>
static MFEM_HOST_DEVICE void load_value(Shared &s,
const int e,
const int d,
const int q,
const real_t *B,
const XE_T &XE,
ArgRegT &rarg)
{
ker::LoadMatrix(d, q, B, s.B);
using field_t = std::remove_cv_t<std::remove_reference_t<FieldParamT>>;
constexpr int RNK = qf_param_shape<field_t>::rank;
constexpr int VDIM = []()
{
if constexpr (RNK == 0) { return 1; }
else { return qf_param_shape<field_t>::extents[RNK - 1]; }
}();
if constexpr (DIM == 2)
{
ker::LoadDofs2d<VDIM, DIM, MQ1>(e, d, XE, s.M[0]);
ker::Eval2d(d, q, s.B, s.M[0], s.M[1], rarg);
}
else
{
ker::LoadDofs3d<VDIM, DIM, MQ1>(e, d, XE, s.M[0]);
ker::Eval3d(d, q, s.B, s.M[0], s.M[1], rarg);
}
}
template<int RNK,
typename ArgRegT,
typename XE_T,
typename FieldParamT = ArgRegT>
static MFEM_HOST_DEVICE void load_gradient(Shared &s,
const int e,
const int d,
const int q,
const real_t *B,
const real_t *G,
const XE_T &XE,
ArgRegT &rarg)
{
ker::LoadMatrix(d, q, B, s.B);
ker::LoadMatrix(d, q, G, s.G);
static_assert(RNK == 1 || RNK == 2);
if constexpr (RNK == 1)
{
static constexpr int SDIM = qf_param_shape<FieldParamT>::extents[0];
if constexpr (SDIM == DIM)
{
if constexpr (DIM == 2)
{
ker::LoadDofs2d(e, d, 0, XE, s.M[0]);
ker::Grad2d(d, q, s.B, s.G, s.M[0], s.M[1], rarg);
}
else
{
ker::LoadDofs3d(e, d, 0, XE, s.M[0]);
ker::Grad3d(d, q, s.B, s.G, s.M[0], s.M[1], rarg);
}
}
}
if constexpr (RNK == 2)
{
static constexpr int VDIM = qf_param_shape<FieldParamT>::extents[0];
static constexpr int SDIM = qf_param_shape<FieldParamT>::extents[1];
if constexpr (SDIM == DIM)
{
for (int c = 0; c < VDIM; ++c)
{
if constexpr (DIM == 2)
{
ker::LoadDofs2d(e, d, c, XE, s.M[0]);
ker::VectorGrad2d(d, q, c, s.B, s.G, s.M[0], s.M[1], rarg);
}
else
{
ker::LoadDofs3d(e, d, c, XE, s.M[0]);
ker::VectorGrad3d(d, q, c, s.B, s.G, s.M[0], s.M[1], rarg);
}
}
}
}
}
template<typename ArgRegT, typename YE_T>
static MFEM_HOST_DEVICE void write_value(Shared &s,
const int e,
const int d,
const int q,
const real_t *B,
const YE_T &YE,
ArgRegT &rarg)
{
ker::LoadMatrix(d, q, B, s.B);
if constexpr (DIM == 2)
{
ker::EvalTranspose2d(d, q, s.B, rarg, s.M[1], s.M[0]);
ker::WriteEvalDofs2d(d, 0, e, rarg, YE);
}
else
{
ker::EvalTranspose3d(d, q, s.B, rarg, s.M[1], s.M[0]);
ker::WriteEvalDofs3d(d, 0, e, rarg, YE);
}
}
template<int RNK,
typename ArgRegT,
typename YE_T,
typename FieldParamT = ArgRegT>
static MFEM_HOST_DEVICE void write_gradient(Shared &s,
const int e,
const int d,
const int q,
const real_t *B,
const real_t *G,
YE_T &YE,
ArgRegT &rarg)
{
ker::LoadMatrix(d, q, B, s.B);
ker::LoadMatrix(d, q, G, s.G);
static_assert(RNK == 1 || RNK == 2);
if constexpr (RNK == 1)
{
static constexpr int SDIM = qf_param_shape<FieldParamT>::extents[0];
if constexpr (SDIM == DIM)
{
if constexpr (DIM == 2)
{
ker::GradTranspose2d(d, q, s.B, s.G, rarg, s.M[1], s.M[0]);
ker::WriteGradDofs2d(d, 0, e, rarg, YE);
}
else
{
ker::GradTranspose3d(d, q, s.B, s.G, rarg, s.M[1], s.M[0]);
ker::WriteGradDofs3d(d, 0, e, rarg, YE);
}
}
}
else if constexpr (RNK == 2)
{
static constexpr int VDIM = qf_param_shape<FieldParamT>::extents[0];
static constexpr int SDIM = qf_param_shape<FieldParamT>::extents[1];
if constexpr (SDIM == DIM)
{
for (int c = 0; c < VDIM; ++c)
{
if constexpr (DIM == 2)
{
ker::VectorGradTranspose2d(
d, q, c, s.B, s.G, s.M[0], s.M[1], rarg);
ker::WriteGradDofs2d(d, c, e, rarg, YE);
}
else
{
ker::VectorGradTranspose3d(
d, q, c, s.B, s.G, s.M[0], s.M[1], rarg);
ker::WriteGradDofs3d(d, c, e, rarg, YE);
}
}
}
}
else
{
static_assert(false, "Unsupported");
}
}
};
// ────────────────────────────────────────────────────────────────────────────
template<int T_DIM, int T_Q1D = LocalQFLOBackendMQ1()>
struct LocalQFLOBackend
{
// ─────────────────────────────────────────────────────
static constexpr int DIM = T_DIM, MQ1 = T_Q1D, Q1D = T_Q1D;
static_assert(DIM == 2 || DIM == 3);
// ─────────────────────────────────────────────────────
static inline ThreadBlocks thread_blocks(const int q1d)
{
MFEM_ASSERT(q1d <= Q1D, "q1d must be <= " << Q1D);
return { q1d, q1d, (DIM == 2) ? 1 : q1d };
}
// ─────────────────────────────────────────────────────
static inline constexpr int MAX_THREADS_PER_BLOCK()
{ return Q1D * Q1D * ((DIM == 2) ? 1 : Q1D); }
// ─────────────────────────────────────────────────────
using backend_t = lo_ker_backend<DIM, Q1D>;
// ─────────────────────────────────────────────────────
using Shared = typename backend_t::Shared;
// ─────────────────────────────────────────────────────
template<typename WT, typename WI, typename Cache, typename AddY>
static MFEM_HOST_DEVICE inline void DiagContract(Shared &s,
const int num_dof_1d,
const int q1d,
const int nz_dof,
WT wt,
WI wi,
Cache cache,
AddY add_y)
{
MFEM_CONTRACT_VAR(nz_dof);
real_t *base = reinterpret_cast<real_t *>(&s.M[0]);
auto s0 = reinterpret_cast<real_t(*)[Q1D][Q1D]>(base);
if constexpr (DIM == 3)
{
auto s1 =
reinterpret_cast<real_t(*)[Q1D][Q1D]>(base + Q1D * Q1D * Q1D);
// reduce qz → dz : s0[dz][qy][qx]
MFEM_FOREACH_THREAD(dz, z, num_dof_1d)
MFEM_FOREACH_THREAD(qy, y, q1d)
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t u = 0.0;
for (int qz = 0; qz < q1d; qz++)
{
const int q = qx + (qy + qz * q1d) * q1d;
u += wt(2, qz, dz) * wi(2, qz, dz) * cache(q);
}
s0[dz][qy][qx] = u;
}
MFEM_SYNC_THREAD;
// reduce qy → dy : s1[dz][dy][qx]
MFEM_FOREACH_THREAD(dz, z, num_dof_1d)
MFEM_FOREACH_THREAD(dy, y, num_dof_1d)
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t u = 0.0;
for (int qy = 0; qy < q1d; qy++)
{
u += wt(1, qy, dy) * wi(1, qy, dy) * s0[dz][qy][qx];
}
s1[dz][dy][qx] = u;
}
MFEM_SYNC_THREAD;
// reduce qx → dx : Y(dx,dy,dz)
MFEM_FOREACH_THREAD(dz, z, num_dof_1d)
MFEM_FOREACH_THREAD(dy, y, num_dof_1d)
MFEM_FOREACH_THREAD(dx, x, num_dof_1d)
{
real_t u = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
u += wt(0, qx, dx) * wi(0, qx, dx) * s1[dz][dy][qx];
}
add_y(dx, dy, dz, u);
}
MFEM_SYNC_THREAD;
}
else
{
// reduce qy → dy : s0[0][dy][qx]
MFEM_FOREACH_THREAD(dy, y, num_dof_1d)
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t u = 0.0;
for (int qy = 0; qy < q1d; qy++)
{
const int q = qx + qy * q1d;
u += wt(1, qy, dy) * wi(1, qy, dy) * cache(q);
}
s0[0][dy][qx] = u;
}
MFEM_SYNC_THREAD;
// reduce qx → dx : Y(dx,dy,0)
MFEM_FOREACH_THREAD(dy, y, num_dof_1d)
MFEM_FOREACH_THREAD(dx, x, num_dof_1d)
{
real_t u = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
u += wt(0, qx, dx) * wi(0, qx, dx) * s0[0][dy][qx];
}
add_y(dx, dy, 0, u);
}
MFEM_SYNC_THREAD;
}
}
// ─────────────────────────────────────────────────────
template<typename T>
using QReg = lo_qreg_t<backend_t, T>;
// ─────────────────────────────────────────────────────
template<typename ArgRegT, typename XE_T>
static inline MFEM_HOST_DEVICE void LoadValue(Shared &s,
const int e,
const int d,
const int q,
const int,
const real_t *B,
const XE_T &XE,
ArgRegT &rarg)
{
backend_t::template load_value<ArgRegT>(s, e, d, q, B, XE, rarg);
}
// ─────────────────────────────────────────────────────
template<int RNK,
typename ArgRegT,
typename XE_T,
typename FieldParamT = ArgRegT>
static inline MFEM_HOST_DEVICE void LoadGradient(Shared &s,
const int e,
const int d,
const int q,
const int,
const real_t *B,
const real_t *G,
const XE_T &XE,
ArgRegT &rarg)
{
backend_t::template load_gradient<RNK, ArgRegT, XE_T, FieldParamT>(
s, e, d, q, B, G, XE, rarg);
}
// ─────────────────────────────────────────────────────
template<typename T>
static MFEM_HOST_DEVICE inline auto
qp_pull(QReg<T> &reg, int qx, int qy, int qz)
{
if constexpr (qf_param_uses_dual_v<T>)
{
return lok::load_at<DIM, T>(reg, qx, qy, qz);
}
else
{
constexpr int RNK = qf_param_shape<T>::rank;
if constexpr (RNK == 0)
{
return as_tensor<real_t>(&lok::at<DIM>(reg, qx, qy, qz)[0]);
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
return as_tensor<real_t, e0>(&lok::at<DIM>(reg, qx, qy, qz)[0]);
}
else if constexpr (RNK == 2)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
return as_tensor<real_t, e0, e1>(
&lok::at<DIM>(reg, qx, qy, qz)[0][0]);
}
else
{
static_assert(false, "Unsupported");
}
}
}
// ─────────────────────────────────────────────────────
template<typename T>
static MFEM_HOST_DEVICE inline auto qp_pull_directional(
QReg<T> &preg, QReg<T> &sreg, int qx, int qy, int qz, bool dependent)
{ return lok::pull_directional<DIM, T>(preg, sreg, qx, qy, qz, dependent); }
// ─────────────────────────────────────────────────────
template<typename DT, typename XE_T>
static MFEM_HOST_DEVICE inline DT identity_qp_pull_dual(bool dependent,
const XE_T &XP,
const XE_T &XD,
int qx,
int qy,
int qz,
int e)
{
constexpr int RNK = qf_param_shape<DT>::rank;
if constexpr (RNK == 0)
{
DT t{};
t.value = XP(0, qx, qy, qz, e);
t.gradient = dependent ? XD(0, qx, qy, qz, e) : 0.0;
return t;
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
DT t{};
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
t(dd).value = XP(dd, qx, qy, qz, e);
t(dd).gradient = dependent ? XD(dd, qx, qy, qz, e) : 0.0;
}
return t;
}
else if constexpr (RNK == 2)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
constexpr int e1 = qf_param_shape<DT>::extents[1];
DT t{};
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
t(i, j).value = XP(i + e0 * j, qx, qy, qz, e);
t(i, j).gradient =
dependent ? XD(i + e0 * j, qx, qy, qz, e) : 0.0;
}
}
return t;
}
else
{
static_assert(false, "Unsupported");
}
}
// ─────────────────────────────────────────────────────
template<typename T>
static MFEM_HOST_DEVICE inline void
qp_push(QReg<T> &reg, int qx, int qy, int qz, const T &out)
{
if constexpr (qf_param_uses_dual_v<T>)
{
lok::store_at<DIM, T, decltype(reg), false>(reg, qx, qy, qz, out);
}
else
{
constexpr int RNK = qf_param_shape<T>::rank;
if constexpr (RNK == 0)
{
as_tensor<real_t>(&lok::at<DIM>(reg, qx, qy, qz)[0]) = out;
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
as_tensor<real_t, e0>(&lok::at<DIM>(reg, qx, qy, qz)[0]) = out;
}
else if constexpr (RNK == 2)
{
constexpr int e0 = qf_param_shape<T>::extents[0];
constexpr int e1 = qf_param_shape<T>::extents[1];
as_tensor<real_t, e0, e1>(&lok::at<DIM>(reg, qx, qy, qz)[0][0]) =
out;
}
else
{
static_assert(false, "Unsupported");
}
}
}
// ─────────────────────────────────────────────────────
template<typename T>
static MFEM_HOST_DEVICE inline void
qp_push_tangent(QReg<T> &reg, int qx, int qy, int qz, const T &out)
{
if constexpr (!qf_param_uses_dual_v<T>)
{
qp_push<T>(reg, qx, qy, qz, out);
}
else
{
lok::store_at<DIM, T, decltype(reg), true>(reg, qx, qy, qz, out);
}
}
// ─────────────────────────────────────────────────────
template<typename DT, typename YE_T>
static MFEM_HOST_DEVICE inline void identity_qp_write_value(
YE_T &YE, int qx, int qy, int qz, int e, const DT &qout)
{
constexpr int RNK = qf_param_shape<DT>::rank;
if constexpr (qf_param_uses_dual_v<DT>)
{
if constexpr (RNK == 0)
{
YE(0, qx, qy, qz, e) = qf_store_value(qout);
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
YE(dd, qx, qy, qz, e) = qf_store_value(qout(dd));
}
}
else if constexpr (RNK == 2)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
constexpr int e1 = qf_param_shape<DT>::extents[1];
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
YE(i + e0 * j, qx, qy, qz, e) = qf_store_value(qout(i, j));
}
}
}
else
{
static_assert(false, "Unsupported");
}
}
}
// ─────────────────────────────────────────────────────
template<typename DT, typename YE_T>
static MFEM_HOST_DEVICE inline void identity_qp_write_tangent(
YE_T &YE, int qx, int qy, int qz, int e, const DT &qout)
{
constexpr int RNK = qf_param_shape<DT>::rank;
if constexpr (qf_param_uses_dual_v<DT>)
{
if constexpr (RNK == 0)
{
YE(0, qx, qy, qz, e) = qf_store_gradient(qout);
}
else if constexpr (RNK == 1)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
MFEM_UNROLL(e0)
for (int dd = 0; dd < e0; ++dd)
{
YE(dd, qx, qy, qz, e) = qf_store_gradient(qout(dd));
}
}
else if constexpr (RNK == 2)
{
constexpr int e0 = qf_param_shape<DT>::extents[0];
constexpr int e1 = qf_param_shape<DT>::extents[1];
MFEM_UNROLL(e0)
for (int i = 0; i < e0; ++i)
{
MFEM_UNROLL(e1)
for (int j = 0; j < e1; ++j)
{
YE(i + e0 * j, qx, qy, qz, e) = qf_store_gradient(qout(i, j));
}
}
}
else
{
static_assert(false, "Unsupported");
}
}
}
// ─────────────────────────────────────────────────────
template<typename ArgRegT, typename YE_T>
static inline MFEM_HOST_DEVICE void WriteValue(Shared &s,
const int e,
const int d,
const int q,
const int,
const real_t *B,
const YE_T &YE,
ArgRegT &rarg)
{ backend_t::write_value(s, e, d, q, B, YE, rarg); }
// ─────────────────────────────────────────────────────
template<int RNK,
typename ArgRegT,
typename YE_T,
typename FieldParamT = ArgRegT>
static inline MFEM_HOST_DEVICE void WriteGradient(Shared &s,
const int e,
const int d,
const int q,
const int,
const real_t *B,
const real_t *G,
YE_T &YE,
ArgRegT &rarg)
{
backend_t::template write_gradient<RNK, ArgRegT, YE_T, FieldParamT>(
s, e, d, q, B, G, YE, rarg);
}
};
/// @brief Dispatch to a compile-time LO kernel matching runtime @a q1d.
template <typename LOKernelTable, int DIM, int MQ1 = LocalQFLOBackendMQ1()>
inline typename LOKernelTable::KernelSignature
DispatchLOKernelByQ1D(int q1d)
{
MFEM_VERIFY(q1d >= 2 && q1d <= MQ1,
"Unsupported LO quadrature order: " << q1d);
return LOKernelTable::template Kernel<DIM, MQ1>();
}
} // namespace mfem::future
+232
View File
@@ -0,0 +1,232 @@
// 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 "../../integrator_ctx.hpp"
#include "action.hpp"
#include "derivative_action.hpp"
#include "derivative_setup.hpp"
#include "derivative_apply.hpp"
#include "derivative_assemble.hpp"
#include "derivative_assemble_diagonal.hpp"
#include "derivative_apply_transpose.hpp"
namespace mfem::future
{
struct LocalQFBackend
{
/**
* @brief Make an action for a local Q-function backend.
*
* @param ctx The integrator context.
* @param args The arguments to the action.
* @return The action.
*/
template<typename... Args>
static auto MakeAction(const IntegratorContext &ctx, Args... args)
{
return LocalQFImpl::Action<Args...>(ctx, args...);
}
/**
* @brief Make a derivative action for a local Q-function backend.
*
* @tparam derivative_id The id of the derivative.
* @param ctx The integrator context.
* @param args The arguments to the derivative action.
* @return The derivative action.
*/
template<int id, typename... Args>
static auto MakeDerivativeAction(const IntegratorContext &ctx, Args... args)
{
return LocalQFImpl::DerivativeAction<id, Args...>(ctx, args...);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
static auto MakeDerivativeSetup(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs,
Vector &qp_cache)
{
return LocalQFImpl::DerivativeSetup<
derivative_id, qfunc_t, inputs_t, outputs_t>(ctx, qfunc, inputs,
outputs, qp_cache);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
static auto MakeDerivativeApply(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache)
{
return LocalQFImpl::DerivativeApply<
derivative_id, qfunc_t, inputs_t, outputs_t>(ctx, qfunc, inputs,
outputs, qp_cache);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
static auto MakeDerivativeApplyTranspose(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache)
{
return LocalQFImpl::DerivativeApplyTranspose<
derivative_id, qfunc_t, inputs_t, outputs_t>(ctx, qfunc, inputs,
outputs, qp_cache);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
static auto MakeDerivativeAssemble(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache)
{
return LocalQFImpl::DerivativeAssemble<
derivative_id, qfunc_t, inputs_t, outputs_t>(ctx, qfunc, inputs,
outputs, qp_cache);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
static auto MakeDerivativeAssembleDiagonal(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs,
const Vector &qp_cache)
{
return LocalQFImpl::DerivativeAssembleDiagonal<
derivative_id, qfunc_t, inputs_t, outputs_t>(ctx, qfunc, inputs,
outputs, qp_cache);
}
};
// ────────────────────────────────────────────────────────────────────────────
template<int DIM, int Q1D, typename QT, typename IT, typename OT>
inline void AddAction()
{
using ker = LocalQFImpl::Action<QT, IT, OT>;
if constexpr (Q1D <= 8)
{
ker::ActionLO::template Specialization<DIM, Q1D>::Add();
}
else
{
ker::ActionHO::template Specialization<DIM, Q1D>::Add();
}
}
// ────────────────────────────────────────────────────────────────────────────
template<int DIM, int Q1D, int DID, typename QT, typename IT, typename OT>
inline void AddDerivativeAction()
{
using ker = LocalQFImpl::DerivativeAction<DID, QT, IT, OT>;
if constexpr (Q1D <= 8)
{
ker::DerivativeActionLO::template Specialization<DIM, Q1D>::Add();
}
else
{
ker::DerivativeActionHO::template Specialization<DIM, Q1D>::Add();
}
}
// ────────────────────────────────────────────────────────────────────────────
template<int DIM, int Q1D, int DID, typename QT, typename IT, typename OT>
inline void AddDerivativeSetup()
{
using ker = LocalQFImpl::DerivativeSetup<DID, QT, IT, OT>;
if constexpr (Q1D <= 8)
{
ker::DerivativeSetupLO::template Specialization<DIM, Q1D>::Add();
}
else
{
ker::DerivativeSetupHO::template Specialization<DIM, Q1D>::Add();
}
}
// ────────────────────────────────────────────────────────────────────────────
template<int DIM, int Q1D, int DID, typename QT, typename IT, typename OT>
inline void AddDerivativeApply()
{
using ker = LocalQFImpl::DerivativeApply<DID, QT, IT, OT>;
if constexpr (Q1D <= 8)
{
ker::DerivativeApplyLO::template Specialization<DIM, Q1D>::Add();
}
else
{
ker::DerivativeApplyHO::template Specialization<DIM, Q1D>::Add();
}
}
// ────────────────────────────────────────────────────────────────────────────
template<int DIM, int Q1D, int DID, typename QT, typename IT, typename OT>
inline void AddDerivativeApplyTranspose()
{
using ker = LocalQFImpl::DerivativeApplyTranspose<DID, QT, IT, OT>;
if constexpr (Q1D <= 8)
{
ker::DerivativeApplyTransposeLO::template Specialization<DIM, Q1D>::Add();
}
else
{
ker::DerivativeApplyTransposeHO::template Specialization<DIM, Q1D>::Add();
}
}
// ────────────────────────────────────────────────────────────────────────────
template<int DIM, int Q1D, typename QT, typename IT, typename OT,
typename derivative_ids_t = std::index_sequence<>>
inline void AddLocalSpecializations()
{
AddAction<DIM, Q1D, QT, IT, OT>();
for_constexpr([&](auto i)
{
using derivative_id = decltype(i);
AddDerivativeAction<DIM, Q1D, derivative_id::value, QT, IT, OT>();
AddDerivativeSetup<DIM, Q1D, derivative_id::value, QT, IT, OT>();
AddDerivativeApply<DIM, Q1D, derivative_id::value, QT, IT, OT>();
AddDerivativeApplyTranspose<DIM, Q1D, derivative_id::value, QT, IT, OT>();
}, derivative_ids_t{});
}
}
@@ -0,0 +1,524 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "../util.hpp"
#include "util.hpp"
#include "../../util.hpp"
namespace mfem::future
{
////////////////////////////////////////////////////
///---- Function traits for q-function types ----///
////////////////////////////////////////////////////
template <typename T> struct function_traits;
template <typename R, typename C, typename... Args>
struct function_traits<R (C::*)(Args...) const>
{
using primal_return_type = R;
using args_tuple = tuple<Args...>;
static constexpr size_t arity = sizeof...(Args);
};
template <typename T>
struct qp_scalar_traits
{
using view_type = T;
using dual_type = dual<T, T>;
};
template <typename V, typename G>
struct qp_scalar_traits<dual<V, G>>
{
using view_type = V;
using dual_type = dual<V, G>;
};
///////////////////////////////////////////////////////////////
///---- Utils for Nested dual numbers (2nd derivatives) ----///
///////////////////////////////////////////////////////////////
//<--- Types
template <typename T>
struct make_nested_qp_type
{
using type = T;
};
template <typename V, typename G>
struct make_nested_qp_type<dual<V, G>>
{
using type = dual<dual<V, G>, dual<V, G>>;
};
template <typename S, int... Sizes>
struct make_nested_qp_type<tensor<S, Sizes...>>
{
using type = tensor<typename make_nested_qp_type<S>::type, Sizes...>;
};
template <typename T>
using make_nested_qp_type_t = typename make_nested_qp_type<T>::type;
using native_dual_t = typename qp_scalar_traits<real_t>::dual_type;
using nested_native_dual_t = make_nested_qp_type_t<native_dual_t>;
//<--- Qfunc rebinding to nested duals arguments
// Rebinds qfunc to lift scalar types to nested duals for 2nd derivative computation
// This is used in the RevDiff transformer to compute 2nd derivatives with hyper dual numbers.
//
// Currently lifts the first scalar type in the qfunc signature to nested duals, and leaves the rest unchanged.
//
template <typename qfunc_t, typename nested_scalar_t, typename = void>
struct rebind_qfunc_scalar
{
static constexpr bool supported = false;
};
template <template <typename> class qfunc_template_t,
typename old_scalar_t,
typename nested_scalar_t>
struct rebind_qfunc_scalar<qfunc_template_t<old_scalar_t>, nested_scalar_t>
{
static constexpr bool supported = true;
using type = qfunc_template_t<nested_scalar_t>;
};
template <template <typename, auto...> class qfunc_template_t,
typename old_scalar_t,
auto... Params,
typename nested_scalar_t>
struct rebind_qfunc_scalar<qfunc_template_t<old_scalar_t, Params...>,
nested_scalar_t,
std::enable_if_t<(sizeof...(Params) > 0)>>
{
static constexpr bool supported = true;
using type = qfunc_template_t<nested_scalar_t, Params...>;
};
template <typename qfunc_t, typename nested_scalar_t>
using rebind_qfunc_scalar_t =
typename rebind_qfunc_scalar<qfunc_t, nested_scalar_t>::type;
// Component count and writable counterpart of a per-point argument, which
// is either a tensor or a plain scalar.
template <typename Arg> struct qp_traits
{
static_assert(std::is_arithmetic_v<Arg> || is_dual_number<Arg>::value,
"per-point arguments must be tensors or scalars");
using scalar_type = std::remove_const_t<Arg>;
using view_type = typename qp_scalar_traits<scalar_type>::view_type;
using dual_type = typename qp_scalar_traits<scalar_type>::dual_type;
static constexpr int components = 1;
};
template <typename T, int... Sizes> struct qp_traits<tensor<T, Sizes...>>
{
using scalar_type = std::remove_const_t<T>;
using scalar_traits = qp_traits<scalar_type>;
using view_type = tensor<typename scalar_traits::view_type, Sizes...>;
using dual_type = tensor<typename scalar_traits::dual_type, Sizes...>;
static constexpr int components = (Sizes * ... * 1);
};
template <typename... T1s, typename... T2s>
constexpr tuple<T1s..., T2s...> concat_tuples(tuple<T1s...>, tuple<T2s...>);
//<--- Lift of dual to nested dual
// Maps dual(a,b) --> dual(dual(a,0), dual(b,0)) for 2nd derivative computation
template <typename Dst, typename Src>
MFEM_HOST_DEVICE void lift_to_nested_arg(const Src &src, Dst &dst)
{
using dst_t = std::decay_t<Dst>;
constexpr bool dst_uses_dual = is_dual_number<dst_t>::value ||
qf_param_uses_dual_v<dst_t> ||
is_nested_dual_number<dst_t>::value ||
qf_param_uses_nested_dual_v<dst_t>;
if constexpr (dst_uses_dual)
{
constexpr int ncomp = qp_traits<dst_t>::components;
for (int component = 0; component < ncomp; component++)
{
qf_set_flat_value(dst, component,
qf_flat_value(src, component));
qf_set_flat_gradient(dst, component,
qf_flat_gradient(src, component));
}
}
else
{
// If the destination type does not use dual numbers, just copy the source to destination.
dst = src;
}
}
// RevDiff: computes the full gradient of a pointwise qfunction at a single
// quadrature point using one Enzyme reverse-mode (autodiff) call.
//
// operator()(args...) takes all qfunction input primals followed by one
// writable gradient output per Active input (same shape as that input).
// The qfunction's own output is not passed; Enzyme writes it to stack
// scratch (enzyme_dupnoneed).
//
// A single __enzyme_autodiff call with the output adjoint seeded to 1
// yields all gradient blocks simultaneously — O(1) calls per point
// regardless of input size, vs O(ncomp) for forward mode.
//
// operator() is MFEM_HOST_DEVICE, stateless and allocation-free.
//<--- Determines whether the RevDiff transformer is used in a gradient action (Eval)
// or to compute second derivatives (Derivative).
// This only affects dual number mode, and allows to compute 2nd derivatives with hyper dual numbers.
enum class RevDiffDualMode
{
Eval,
Derivative
};
template <typename Func, typename InputActivityTuple,
typename OutputActivityTuple,
RevDiffDualMode mode = RevDiffDualMode::Eval>
struct RevDiff
{
using traits = function_traits<decltype(&Func::operator())>;
using args_tuple = typename traits::args_tuple;
using activity =
decltype(concat_tuples(InputActivityTuple{}, OutputActivityTuple{}));
static constexpr size_t arity = traits::arity;
static constexpr size_t num_inputs = tuple_size<InputActivityTuple>::value;
static_assert(std::is_void_v<typename traits::primal_return_type>,
"RevDiff only supports primal functions with void return type");
static_assert(tuple_size<activity>::value == arity,
"Number of input and output activity tags must match function "
"arity");
template <size_t I>
static constexpr bool is_active = qf_param_is_active_v<activity, I>;
// Number of Active inputs and their argument indices, in ascending order.
// A qfunction may have several Active inputs at once: e.g. a field's value
// u and its gradient dudx both feed the output and both must be
// differentiated (the chain-rule contraction with the value/gradient shape
// functions then happens at the FE-operator level). We produce one gradient
// block, d(output)/d(input), per Active input — each computed with the other
// Active inputs frozen, so they come out as isolated partials.
template <size_t... Is>
static constexpr size_t count_active_inputs(std::index_sequence<Is...>)
{
return ((Is < num_inputs && is_active<Is> ? size_t{1} : size_t{0}) + ...);
}
static constexpr size_t num_active_inputs =
count_active_inputs(std::make_index_sequence<arity> {});
template <size_t... Is>
static constexpr std::array<size_t, num_active_inputs>
collect_active_inputs(std::index_sequence<Is...>)
{
std::array<size_t, num_active_inputs> idx{};
size_t j = 0;
(((Is < num_inputs && is_active<Is>) ? (idx[j++] = Is) : size_t{0}), ...);
return idx;
}
static constexpr auto active_inputs =
collect_active_inputs(std::make_index_sequence<arity> {});
// Slot index of argument I in the active_inputs array (compile-time).
template <size_t I>
static constexpr size_t slot_of = []() constexpr -> size_t
{
for (size_t s = 0; s < num_active_inputs; s++)
if (active_inputs[s] == I) { return s; }
return num_active_inputs;
}();
static constexpr size_t active_output =
find_single_active_qparam<activity, num_inputs, arity>();
static_assert(active_output < arity,
"gradient mode requires exactly one Active output");
static_assert(num_active_inputs >= 1,
"gradient mode requires at least one Active input");
static_assert(tuple_size<OutputActivityTuple>::value == 1,
"gradient mode requires exactly one (scalar) output");
using output_type =
std::decay_t<tuple_element_t<active_output, args_tuple>>;
using output_view = typename qp_traits<output_type>::view_type;
static_assert(qp_traits<output_type>::components == 1,
"gradient output requires a scalar output");
template <size_t I>
using primal_arg_t = tuple_element_t<I, args_tuple>;
#ifdef MFEM_USE_ENZYME
static constexpr bool use_native_dual_derivative = false;
#else
static constexpr bool use_native_dual_derivative =
mode == RevDiffDualMode::Derivative;
#endif
template <size_t I>
using derivative_arg_t =
std::conditional_t<use_native_dual_derivative && is_active<I>,
typename qp_traits<std::decay_t<tuple_element_t<I, args_tuple>>>::dual_type,
primal_arg_t<I>>;
template <size_t S>
using active_arg_decay_t =
std::decay_t<tuple_element_t<active_inputs[S], args_tuple>>;
template <size_t S>
static constexpr bool active_arg_uses_dual =
is_dual_number<active_arg_decay_t<S>>::value ||
qf_param_uses_dual_v<active_arg_decay_t<S>>;
template <size_t S>
using grad_arg_t =
std::conditional_t<use_native_dual_derivative || active_arg_uses_dual<S>,
typename qp_traits<active_arg_decay_t<S>>::dual_type,
typename qp_traits<active_arg_decay_t<S>>::view_type>
&;
template <typename qfunc_type>
using qfunc_args_tuple_t = decay_tuple<typename function_traits<decltype(&qfunc_type::operator())>::args_tuple>;
template <size_t... Is, size_t... Ss>
static FunctionSignature<void(derivative_arg_t<Is>..., grad_arg_t<Ss>...)>
signature_impl(std::index_sequence<Is...>, std::index_sequence<Ss...>);
using signature = decltype(signature_impl(std::make_index_sequence<num_inputs> {},
std::make_index_sequence<num_active_inputs> {}));
// Plain function with the qfunction's exact (reference) signature so it can
// be handed to Enzyme as a function pointer.
template <size_t... Is>
MFEM_HOST_DEVICE static __attribute__((always_inline)) void
static_call(tuple_element_t<Is, args_tuple>... args)
{
Func{}(args...);
}
template <size_t... Is>
static constexpr auto fn_ptr(std::index_sequence<Is...>)
{
return &static_call<Is...>;
}
static constexpr auto fn = fn_ptr(std::make_index_sequence<arity> {});
// Load primal inputs from the pointer tuple into a local qargs copy.
// All dual gradient parts are implicitly zero because qargs is value-initialized.
template <typename QArgs, typename AllPtrs, size_t... Is>
MFEM_HOST_DEVICE static __attribute__((always_inline)) void load_qargs(
QArgs &qargs, AllPtrs &ptrs, std::index_sequence<Is...>)
{
((mfem::future::get<int(Is)>(qargs) =
*mfem::future::get<int(Is)>(ptrs)), ...);
}
template <typename QArgs, typename AllPtrs, size_t... Is>
MFEM_HOST_DEVICE static __attribute__((always_inline)) void lift_qargs_to_nested_dual(
QArgs &qargs, AllPtrs &ptrs, std::index_sequence<Is...>)
{
(lift_to_nested_arg(*mfem::future::get<int(Is)>(ptrs), get<Is>(qargs)), ...);
}
template <size_t S, typename AllPtrs>
MFEM_HOST_DEVICE static __attribute__((always_inline)) void seed_active_input(
AllPtrs &ptrs)
{
constexpr size_t input_idx = active_inputs[S];
using active_arg_t = std::decay_t<tuple_element_t<input_idx, args_tuple>>;
constexpr int ncomp = qp_traits<active_arg_t>::components;
for (int component = 0; component < ncomp; component++)
{
// Use RevDiff for gradient action computation
if constexpr (mode == RevDiffDualMode::Eval)
{
// Fresh default-constructed qargs: primal values are loaded below,
// all dual gradient parts start at zero (no explicit clear needed).
qfunc_args_tuple_t<Func> qargs{};
load_qargs(qargs, ptrs, std::make_index_sequence<num_inputs>{});
auto &grad = *get<num_inputs + S>(ptrs);
qf_set_flat_gradient(get<input_idx>(qargs), component, 1.0);
call_qfunc_no_move(Func{}, qargs);
auto &out = get<num_inputs>(qargs);
qf_set_flat_value(grad, component, qf_flat_gradient(out, 0));
}
else // Use RevDiffDualMode::RevDiff for second derivative computation, we need nested dual numbers to avoid overwriting the first dual pair.
{
// Lift dFEM's dual input (a,b) to ((a,c),(b,d)). Here b is
// the incoming Hessian-action direction, c is RevDiff's local
// component seed. After evaluating E, the nested scalar output is
// ((E, dE/dx_i), (E'[b], H_i[b])); after computing the qfunc,
// we unpack the result for the return to dfem as (dE/dx_i, H_i[b]).
static_assert(rebind_qfunc_scalar<Func, nested_native_dual_t>::supported,
"RevDiff native-dual derivative mode requires q-function "
"types of the form QFunc<scalar_t> so they can be "
"rebound to nested dual scalars");
using nested_func_t = rebind_qfunc_scalar_t<Func, nested_native_dual_t>;
qfunc_args_tuple_t<nested_func_t> nested_qargs{};
lift_qargs_to_nested_dual(nested_qargs, ptrs, std::make_index_sequence<num_inputs>{});
qf_set_flat_value_gradient(get<input_idx>(nested_qargs), component, 1.0);
call_qfunc_no_move(nested_func_t{}, nested_qargs);
// Unpack the nested dual output into the gradient output for the 2nd derivative.
auto &out = get<active_output>(nested_qargs);
auto &grad = *get<num_inputs + S>(ptrs);
qf_set_flat_value(grad, component,
qf_flat_value_gradient(out, 0));
qf_set_flat_gradient(grad, component,
qf_flat_gradient_gradient(out, 0));
}
}
}
// Compute gradient of the qfunction with respect to all Active inputs at once, using forward-mode seeding.
// This is the dual-number fallback when Enzyme is not available.
template <typename AllPtrs>
MFEM_HOST_DEVICE static __attribute__((always_inline)) void call_dual_rev(
AllPtrs &ptrs)
{
for_constexpr<num_active_inputs>([&](auto s)
{
// Seed the s-th Active input w/ dual-number tangent 1.0, then call the qfunction to compute the corresponding gradient block.
seed_active_input<decltype(s)::value>(ptrs);
});
}
#ifdef MFEM_USE_ENZYME
// Recursively flatten the enzyme argument list for a single reverse-mode
// call that differentiates ALL active inputs at once:
// active input: enzyme_dup, &primal, &grad — grad accumulates
// const input: enzyme_const, &primal
// active output: enzyme_dupnoneed, &scratch, &adjoint_seed
// ptrs holds all primal pointers in [0, num_inputs) and all gradient output
// pointers in [num_inputs, num_inputs+num_active_inputs).
template <size_t I = 0, typename AllPtrs, typename... Built>
MFEM_HOST_DEVICE static __attribute__((always_inline)) void
call_enzyme_rev(AllPtrs &ptrs, output_view &out_scratch,
output_view &out_adjoint, Built... built)
{
if constexpr (I == arity)
{
__enzyme_autodiff<void>(fn, built...);
}
else if constexpr (I == active_output)
{
call_enzyme_rev<I + 1>(ptrs, out_scratch, out_adjoint, built...,
enzyme_dupnoneed, &out_scratch, &out_adjoint);
}
else if constexpr (is_active<I>)
{
call_enzyme_rev<I + 1>(
ptrs, out_scratch, out_adjoint, built..., enzyme_dup,
mfem::future::get<int(I)>(ptrs),
mfem::future::get<int(num_inputs + slot_of<I>)>(ptrs));
}
else
{
call_enzyme_rev<I + 1>(ptrs, out_scratch, out_adjoint, built...,
enzyme_const, mfem::future::get<int(I)>(ptrs));
}
}
#endif
// Zero all gradient outputs before the enzyme call (Enzyme accumulates).
template <typename AllPtrs, size_t... Ss>
MFEM_HOST_DEVICE static __attribute__((always_inline)) void zero_grads(
AllPtrs &ptrs,
std::index_sequence<Ss...>)
{
((*mfem::future::get<int(num_inputs + Ss)>(ptrs) =
std::decay_t<decltype(*mfem::future::get<int(num_inputs + Ss)>(ptrs))> {}),
...);
}
// Called once per quadrature point. Arguments are, in order:
// * the primal value of every qfunction input (active and const), then
// * one gradient output per Active input (ascending index order), each
// shaped like its Active input.
// The qfunction's own output slot is not passed; Enzyme writes it to stack
// scratch (enzyme_dupnoneed). A single __enzyme_autodiff call yields all
// gradient blocks simultaneously.
template <typename... Args>
MFEM_HOST_DEVICE __attribute__((always_inline)) void operator()(
Args &&...args) const
{
static_assert(sizeof...(Args) == num_inputs + num_active_inputs,
"expected one primal per input plus one gradient output per "
"Active input");
auto ptrs = mfem::future::make_tuple(&args...);
zero_grads(ptrs, std::make_index_sequence<num_active_inputs>{});
output_view out_scratch{};
output_view out_adjoint{1.0}; // seed: d(output)/d(output) = 1
#ifdef MFEM_USE_ENZYME
call_enzyme_rev(ptrs, out_scratch, out_adjoint);
#else
call_dual_rev(ptrs);
#endif
}
static __attribute__((always_inline)) void print() { print_impl(std::make_index_sequence<arity> {}); }
template <size_t... Is> static __attribute__((always_inline)) void print_impl(
std::index_sequence<Is...>)
{
mfem::out << "__enzyme_autodiff<void>(fptr";
(([&]
{
auto name = get_type_name<tuple_element_t<Is, args_tuple>>();
if constexpr (Is == active_output)
mfem::out << ", enzyme_dupnoneed, " << name << ", adjoint=1";
else if constexpr (is_active<Is>)
mfem::out << ", enzyme_dup, " << name << ", grad out";
else
{
mfem::out << ", enzyme_const, " << name;
}
}()),
...);
mfem::out << ")\n";
}
};
template <typename Func, typename InputActivityTuple, typename OutputActivityTuple,
RevDiffDualMode mode>
struct create_function_signature<RevDiff<Func, InputActivityTuple,
OutputActivityTuple, mode>>
{
using type = typename
RevDiff<Func, InputActivityTuple, OutputActivityTuple, mode>::signature;
};
} // namespace mfem::future
+677
View File
@@ -0,0 +1,677 @@
// 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
// Compile-time quadrature argument metadata for local q-functions
#include "../../../../linalg/tensor.hpp"
#include "../../integrator_ctx.hpp"
#include "../../util.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <type_traits>
namespace mfem::future
{
///////////////////////////////////////////////////////////////////////////////
template <typename T>
MFEM_HOST_DEVICE auto qf_store_value(const T &v)
{
if constexpr (is_nested_dual_number<T>::value) { return v.value.value; }
else if constexpr (is_dual_number<T>::value) { return v.value; }
else { return v; }
}
template <typename T>
MFEM_HOST_DEVICE auto qf_store_gradient(const T &v)
{
if constexpr (is_nested_dual_number<T>::value) { return v.gradient.value; }
else if constexpr (is_dual_number<T>::value) { return v.gradient; }
else { return v; }
}
template <typename T>
struct qf_param_uses_nested_dual : std::false_type {};
template <typename S, int... Is>
struct qf_param_uses_nested_dual<tensor<S, Is...>> : is_nested_dual_number<S> {};
template <typename V, typename G>
struct qf_param_uses_nested_dual<dual<V, G>> : is_nested_dual_number<dual<V, G>> {};
template <typename T>
constexpr bool qf_param_uses_nested_dual_v =
qf_param_uses_nested_dual<T>::value;
///////////////////////////////////////////////////////////////////////////////
/// Static shape for one decayed q-function parameter type
template <typename T>
struct qf_param_shape
{
static constexpr int rank = 0;
static constexpr std::array<int, 0> extents {};
};
template <typename scalar_t, int... Is>
struct qf_param_shape<tensor<scalar_t, Is...>>
{
static constexpr int rank = sizeof...(Is);
static constexpr std::array<int, sizeof...(Is)> extents {{Is...}};
};
template <typename scalar_t>
struct qf_param_shape<tensor<scalar_t>>
{
static constexpr int rank = 0;
static constexpr std::array<int, 0> extents {};
};
template <>
struct qf_param_shape<real_t>
{
static constexpr int rank = 0;
static constexpr std::array<int, 0> extents {};
};
///////////////////////////////////////////////////////////////////////////////
/// Type used in quadrature registers for parameter
template <typename T>
struct qf_reg_t { using type = T; };
template <>
struct qf_reg_t<real_t> { using type = tensor<real_t>; };
///////////////////////////////////////////////////////////////////////////////
/// Per-parameter tensor info for slot `I` in the decayed q-function parameter tuple
template <typename qfunc_t, std::size_t I>
struct qf_param_slot
{
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
using qf_raw_param_t = typename tuple_element<I, qf_param_ts>::type;
using qf_decay_param_t =
std::remove_cv_t<std::remove_reference_t<qf_raw_param_t>>;
using qf_reg_param_t = typename qf_reg_t<qf_decay_param_t>::type;
static constexpr auto extents = qf_param_shape<qf_decay_param_t>::extents;
};
///////////////////////////////////////////////////////////////////////////////
template <
typename backend_t,
typename qfunc_t, typename inputs_t, typename outputs_t, int MQ1,
std::size_t K, std::size_t N, typename... Acc>
struct build_args_reg_tuple_impl;
template <
typename backend_t,
typename qfunc_t, typename inputs_t, typename outputs_t, int MQ1,
std::size_t N, typename... Acc>
struct build_args_reg_tuple_impl<backend_t, qfunc_t, inputs_t, outputs_t, MQ1, N, N, Acc...>
{
using type = tuple<Acc...>;
static_assert(sizeof...(Acc) == N);
static_assert(sizeof...(Acc) <= 9);
};
template <
typename backend_t,
typename qfunc_t, typename inputs_t, typename outputs_t, int MQ1,
std::size_t K, std::size_t N, typename... Acc>
struct build_args_reg_tuple_impl
{
using qf_reg_param_t = typename qf_param_slot<qfunc_t, K>::qf_reg_param_t;
using R = typename backend_t::template QReg<qf_reg_param_t>;
using type = typename build_args_reg_tuple_impl<backend_t, qfunc_t, inputs_t,
outputs_t, MQ1, K + 1, N, Acc..., R>::type;
};
template <
typename backend_t,
typename qfunc_t, typename inputs_t, typename outputs_t, int MQ1>
using args_reg_t = typename build_args_reg_tuple_impl<backend_t, qfunc_t,
inputs_t, outputs_t, MQ1, 0,
tuple_size<inputs_t>::value + tuple_size<outputs_t>::value>::type;
/// Register bank covering q-function inputs only (same types as first
/// `n_inputs` slots of args_reg_t). Used where shadow / tangent paths never
/// touch output parameter registers.
template <
typename backend_t,
typename qfunc_t, typename inputs_t, typename outputs_t, int MQ1>
using input_args_reg_t = typename build_args_reg_tuple_impl<backend_t, qfunc_t,
inputs_t, outputs_t, MQ1, 0,
tuple_size<inputs_t>::value>::type;
///////////////////////////////////////////////////////////////////////////////
/// Flat component access for a q-function argument (scalar / dual / tensor).
///
/// Components are addressed column-major as `c = i_vdim + extents[0]*i_opdim`,
/// matching the byVDIM layout used by `process_qf_arg` / `process_qf_result`.
/// These let the cached derivative setup/apply seed trial directions and gather
/// Jacobian rows directly through the register driver, without an intermediate
/// per-quadrature-point buffer or `map_scratch`.
///
/// 'nested duals' dual<dual<V,G>, dual<V,G>> are used for second derivatives
/// computation for the native dual number backend.
///
/// When Dual number is 'lifted' to nested dual, the following mapping is used:
///
/// Dual(V, G) -> ( (V, 0), (G, 0) ) = dual<dual<V,G>, dual<V,G>>
/// ( a, b ) -> ( (a, c), (b, d) )
///
/// a,b are the original primal/gradient, c,d are the new primal/gradient for the nested dual number.
/// c is used for seeding the second derivative, d is used to retrieve the second derivative.
///
/// The notation for getter/setter is the following:
/// qf_set_flat_value -> value.value
/// qf_set_flat_gradient -> gradient.value
///
/// qf_set_flat_value_gradient -> value.gradient
/// qf_flat_gradient_gradient -> gradient.gradient
///
template <typename ARG>
MFEM_HOST_DEVICE inline real_t qf_flat_value(const ARG &a, int c)
{
if constexpr (std::is_same_v<ARG, real_t> || is_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
return qf_store_value(a);
}
else
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { return qf_store_value(a(0)); }
else if constexpr (RNK == 1) { return qf_store_value(a(c)); }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
return qf_store_value(a(c % e0, c / e0));
}
}
}
template <typename ARG>
MFEM_HOST_DEVICE inline real_t qf_flat_gradient(const ARG &a, int c)
{
if constexpr (is_nested_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
return a.gradient.value;
}
else if constexpr (qf_param_uses_nested_dual<ARG>::value)
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { return a(0).gradient.value; }
else if constexpr (RNK == 1) { return a(c).gradient.value; }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
return a(c % e0, c / e0).gradient.value;
}
}
else if constexpr (is_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
return a.gradient;
}
else if constexpr (qf_param_uses_dual_v<ARG>)
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { return a(0).gradient; }
else if constexpr (RNK == 1) { return a(c).gradient; }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
return a(c % e0, c / e0).gradient;
}
}
else
{
// Non-dual argument carries no tangent: its derivative contribution is 0.
MFEM_CONTRACT_VAR(a);
MFEM_CONTRACT_VAR(c);
return real_t(0);
}
}
template <typename ARG>
MFEM_HOST_DEVICE inline void qf_set_flat_value(ARG &a, int c, real_t v)
{
if constexpr (std::is_same_v<ARG, real_t>) { MFEM_CONTRACT_VAR(c); a = v; }
else if constexpr (is_nested_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
a.value.value = v;
}
else if constexpr (qf_param_uses_nested_dual_v<ARG>)
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { a(0).value.value = v; }
else if constexpr (RNK == 1) { a(c).value.value = v; }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
a(c % e0, c / e0).value.value = v;
}
}
else if constexpr (is_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
a.value = v;
}
else
{
constexpr int RNK = qf_param_shape<ARG>::rank;
constexpr bool D = qf_param_uses_dual_v<ARG>;
if constexpr (RNK == 0)
{
if constexpr (D) { a(0).value = v; }
else { a(0) = v; }
}
else if constexpr (RNK == 1)
{
if constexpr (D) { a(c).value = v; }
else { a(c) = v; }
}
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
if constexpr (D) { a(c % e0, c / e0).value = v; }
else { a(c % e0, c / e0) = v; }
}
}
}
template <typename ARG>
MFEM_HOST_DEVICE inline void qf_set_flat_gradient(ARG &a, int c, real_t v)
{
if constexpr (is_nested_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
a.gradient.value = v;
}
else if constexpr (qf_param_uses_nested_dual_v<ARG>)
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { a(0).gradient.value = v; }
else if constexpr (RNK == 1) { a(c).gradient.value = v; }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
a(c % e0, c / e0).gradient.value = v;
}
}
else if constexpr (is_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
a.gradient = v;
}
else if constexpr (qf_param_uses_dual_v<ARG>)
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { a(0).gradient = v; }
else if constexpr (RNK == 1) { a(c).gradient = v; }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
a(c % e0, c / e0).gradient = v;
}
}
else
{
// Non-dual argument (e.g. Weight): never an active trial direction.
MFEM_CONTRACT_VAR(a);
MFEM_CONTRACT_VAR(c);
MFEM_CONTRACT_VAR(v);
}
}
template <typename ARG>
MFEM_HOST_DEVICE void qf_set_flat_value_gradient(ARG &a, int c, real_t v)
{
if constexpr (is_nested_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
a.value.gradient = v;
}
else if constexpr (qf_param_uses_nested_dual_v<ARG>)
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { a(0).value.gradient = v; }
else if constexpr (RNK == 1) { a(c).value.gradient = v; }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
a(c % e0, c / e0).value.gradient = v;
}
}
else
{
MFEM_CONTRACT_VAR(a);
MFEM_CONTRACT_VAR(c);
MFEM_CONTRACT_VAR(v);
}
}
template <typename ARG>
MFEM_HOST_DEVICE inline real_t qf_flat_value_gradient(const ARG &a, int c)
{
if constexpr (is_nested_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
return a.value.gradient;
}
else if constexpr (qf_param_uses_nested_dual_v<ARG>)
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { return a(0).value.gradient; }
else if constexpr (RNK == 1) { return a(c).value.gradient; }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
return a(c % e0, c / e0).value.gradient;
}
}
else
{
MFEM_CONTRACT_VAR(a);
MFEM_CONTRACT_VAR(c);
return real_t(0);
}
}
template <typename ARG>
MFEM_HOST_DEVICE inline real_t qf_flat_gradient_gradient(const ARG &a, int c)
{
if constexpr (is_nested_dual_number<ARG>::value)
{
MFEM_CONTRACT_VAR(c);
return a.gradient.gradient;
}
else if constexpr (qf_param_uses_nested_dual_v<ARG>)
{
constexpr int RNK = qf_param_shape<ARG>::rank;
if constexpr (RNK == 0) { return a(0).gradient.gradient; }
else if constexpr (RNK == 1) { return a(c).gradient.gradient; }
else
{
constexpr int e0 = qf_param_shape<ARG>::extents[0];
return a(c % e0, c / e0).gradient.gradient;
}
}
else
{
MFEM_CONTRACT_VAR(a);
MFEM_CONTRACT_VAR(c);
return real_t(0);
}
}
///////////////////////////////////////////////////////////////////////////////
/// Maps each FOP slot to unionfds indices — used with dtqs / create_dtq_maps
template<typename C, typename T>
const auto create_union_field_map_for_dtq(C& ctx, T& io)
{
using FE = Entity::Element;
return create_descriptors_to_fields_map<FE>(ctx.unionfds, io);
}
/// **`xe[i]`** slot per input FOP — indices into **`ctx.infds`** (`SIZE_MAX` for Weight).
template<typename C, typename T>
const auto create_input_vector_map(C& ctx, T& io)
{
using FE = Entity::Element;
return create_descriptors_to_fields_map<FE>(ctx.infds, io);
}
/// **`ye[i]`** slot per output FOP — indices into **`ctx.outfds`**.
template<typename C, typename T>
const auto create_output_vector_map(C& ctx, T& io)
{
using FE = Entity::Element;
return create_descriptors_to_fields_map<FE>(ctx.outfds, io);
}
template<typename C>
const auto make_dtqs(C& ctx)
{
std::vector<const DofToQuad*> dtq_vec;
dtq_vec.reserve(ctx.unionfds.size());
constexpr auto dtq_mode = DofToQuad::Mode::TENSOR;
for (const auto &field: ctx.unionfds)
{
auto dtq = GetDofToQuad<Entity::Element>(field, ctx.ir, dtq_mode);
dtq_vec.emplace_back(dtq);
}
return dtq_vec;
}
///////////////////////////////////////////////////////////////////////////////
template<typename Tuple>
constexpr auto get_vdim(const Tuple& fields)
{
return future::apply([](const auto&... f)
{
return std::array<int, sizeof...(f)> {f.vdim...};
}, fields);
}
template<typename Tuple>
constexpr auto get_B(const Tuple& fields)
{
return future::apply([](const auto&... f)
{
return std::array<const real_t*, sizeof...(f)> {f.B...};
}, fields);
}
template<typename Tuple>
constexpr auto get_G(const Tuple& fields)
{
return future::apply([](const auto&... f)
{
return std::array<const real_t*, sizeof...(f)> {f.G...};
}, fields);
}
template<typename Tuple>
constexpr auto get_D1D(const Tuple& fields)
{
return future::apply([](const auto&... f)
{
return std::array<int, sizeof...(f)> {f.B.GetShape()[2]...};
}, fields);
}
template<typename Tuple>
constexpr auto get_Q1D(const Tuple& fields)
{
return future::apply([](const auto&... f)
{
return std::array<int, sizeof...(f)> {f.B.GetShape()[0]...};
}, fields);
}
///////////////////////////////////////////////////////////////////////////////
/// Per-output FOP layout metadata (shared by derivative setup / apply kernels).
template<typename outputs_t>
constexpr auto compute_out_qp_size(const outputs_t &outs)
{
constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
std::array<int, n_outputs> sizes{};
for_constexpr<n_outputs>([&](auto o) { sizes[o] = get<o>(outs).size_on_qp; });
return sizes;
}
template<typename outputs_t>
constexpr auto compute_out_op_dim(const outputs_t &outs)
{
constexpr std::size_t n_outputs = tuple_size<outputs_t>::value;
std::array<int, n_outputs> op{};
for_constexpr<n_outputs>([&](auto o)
{
op[o] = get<o>(outs).size_on_qp / get<o>(outs).vdim;
});
return op;
}
template<std::size_t N>
constexpr std::array<int, N> compute_out_offsets(
const std::array<int, N> &vdim,
const std::array<int, N> &op_dim)
{
std::array<int, N> offsets{};
offsets[0] = 0;
for (std::size_t o = 1; o < N; o++)
{
offsets[o] = offsets[o - 1] + vdim[o - 1] * op_dim[o - 1];
}
return offsets;
}
template<std::size_t N>
constexpr std::array<int, N> compute_out_flat_offsets(
const std::array<int, N> &vdim,
const std::array<int, N> &op_dim,
const int num_qp)
{
std::array<int, N> offsets{};
offsets[0] = 0;
for (std::size_t o = 1; o < N; o++)
{
offsets[o] = offsets[o - 1] + vdim[o - 1] * op_dim[o - 1] * num_qp;
}
return offsets;
}
template<typename inputs_t>
const auto compute_input_is_dependent(const inputs_t &ins, int deriv_id)
{
auto dependency_map = make_dependency_map(ins);
auto it = dependency_map.find(deriv_id);
MFEM_ASSERT(it != dependency_map.end(),
"Derivative ID not found in dependency map");
return it->second;
}
template<typename inputs_t>
constexpr int compute_trial_vdim(const inputs_t &ins, int deriv_id)
{
constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
int v = 1;
for_constexpr<n_inputs>([&](auto i)
{
if (get<i>(ins).GetFieldId() == deriv_id) { v = get<i>(ins).vdim; }
});
return v;
}
template<typename inputs_t>
constexpr int compute_total_trial_op_dim(
const inputs_t &ins,
const std::array<bool, tuple_size<inputs_t>::value> &dep,
const std::array<int, tuple_size<inputs_t>::value> &size_on_qp)
{
constexpr std::size_t n_inputs = tuple_size<inputs_t>::value;
int total = 0;
for_constexpr<n_inputs>([&](auto i)
{
if (dep[i]) { total += size_on_qp[i] / get<i>(ins).vdim; }
});
return total;
}
inline size_t find_union_field_index(const IntegratorContext &ctx, int field_id)
{
for (size_t uf = 0; uf < ctx.unionfds.size(); uf++)
{
if (static_cast<int>(ctx.unionfds[uf].id) == field_id) { return uf; }
}
return SIZE_MAX;
}
inline size_t find_infd_index(const IntegratorContext &ctx, int field_id)
{
for (size_t i = 0; i < ctx.infds.size(); i++)
{
if (static_cast<int>(ctx.infds[i].id) == field_id) { return i; }
}
return SIZE_MAX;
}
template<typename entity_t = Entity::Element>
inline int compute_element_dof_sz(
const FieldDescriptor &fd,
int num_entities,
ElementDofOrdering ordering)
{
auto R = get_restriction<entity_t>(fd, ordering);
MFEM_ASSERT(R != nullptr, "LocalQF: missing element restriction");
return num_entities ? (R->Height() / num_entities) : 0;
}
// ────────────────────────────────────────────────────────────────────────────
// Number of threads per 1D direction to launch the kernel with
template <typename inputs_t, typename outputs_t,
std::size_t N_in, std::size_t N_out>
inline int compute_kernel_thread_1d(
const int q1d,
const std::array<int, N_in> &in_d1d,
const std::array<int, N_out> &out_d1d)
{
int t1d = q1d;
for_constexpr<N_in>([&](auto ic)
{
using FOP = tuple_element_t<ic.value, inputs_t>;
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
t1d = std::max(t1d, in_d1d[ic.value]);
}
});
for_constexpr<N_out>([&](auto ic)
{
using FOP = tuple_element_t<ic.value, outputs_t>;
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
t1d = std::max(t1d, out_d1d[ic.value]);
}
});
return t1d;
}
// Inputs-only variant: used by kernels whose outputs are written at qp
template <typename inputs_t, std::size_t N_in>
inline int compute_kernel_thread_1d(
const int q1d,
const std::array<int, N_in> &in_d1d)
{
int t1d = q1d;
for_constexpr<N_in>([&](auto ic)
{
using FOP = tuple_element_t<ic.value, inputs_t>;
if constexpr (is_value_fop_v<FOP> || is_gradient_fop_v<FOP>)
{
t1d = std::max(t1d, in_d1d[ic.value]);
}
});
return t1d;
}
} // namespace mfem::future
File diff suppressed because it is too large Load Diff
+251 -19
View File
@@ -11,44 +11,276 @@
#include "doperator.hpp"
#include <algorithm>
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
void DifferentiableOperator::SetParameters(std::vector<Vector *> p) const
namespace
{
MFEM_ASSERT(parameters.size() == p.size(),
"number of parameters doesn't match descriptors");
for (size_t i = 0; i < parameters.size(); i++)
int GetTotalTrueVSize(const std::vector<FieldDescriptor> &fds)
{
int size = 0;
for (const auto &fd : fds)
{
p[i]->Read();
parameters_l[i] = *p[i];
size += mfem::future::GetTrueVSize(fd);
}
return size;
}
using DerivativeActionMap =
std::map<size_t, std::vector<derivative_action_t>>;
using DerivativeSetupMap =
std::map<size_t, std::vector<derivative_setup_t>>;
using DerivativeFieldMap =
std::map<size_t, std::vector<FieldDescriptor>>;
using SparseAssemblyMap =
std::map<size_t,
std::vector<assemble_derivative_sparsematrix_callback_t>>;
using HypreAssemblyMap =
std::map<size_t,
std::vector<assemble_derivative_hypreparmatrix_callback_t>>;
using DiagonalAssemblyMap =
std::map<size_t, std::vector<assemble_diagonal_callback_t>>;
template <typename map_t>
const typename map_t::mapped_type &FindOrDefault(
const map_t &map, size_t id, const typename map_t::mapped_type &fallback)
{
const auto it = map.find(id);
return it == map.end() ? fallback : it->second;
}
template <typename map_t>
typename map_t::mapped_type FindOrEmpty(const map_t &map, size_t id)
{
const auto it = map.find(id);
return it == map.end() ? typename map_t::mapped_type{} : it->second;
}
const std::vector<derivative_action_t> &SelectActionCallbacks(
const std::vector<derivative_action_t> &direct_actions,
const DerivativeActionMap &cached_actions,
size_t derivative_id,
bool use_cached_setup)
{
if (use_cached_setup)
{
const auto it_apply = cached_actions.find(derivative_id);
if (it_apply != cached_actions.end() && !it_apply->second.empty())
{
return it_apply->second;
}
}
return direct_actions;
}
struct DerivativeCallbackSet
{
const DerivativeActionMap &actions;
const DerivativeActionMap &cached_actions;
const DerivativeActionMap &transpose_actions;
const DerivativeFieldMap &outfds;
const SparseAssemblyMap &assemble_sparse;
const HypreAssemblyMap &assemble_hypre;
const DiagonalAssemblyMap &assemble_diagonal;
const DerivativeSetupMap &setup;
const char *missing_action_message;
};
template <typename vector_t>
std::shared_ptr<DerivativeOperator> MakeStatefulDerivativeOperator(
size_t derivative_id,
const vector_t &x,
const std::vector<FieldDescriptor> &infds,
const std::vector<FieldDescriptor> &default_outfds,
const DerivativeCallbackSet &callbacks,
bool use_cached_setup)
{
const auto it_action = callbacks.actions.find(derivative_id);
MFEM_ASSERT(it_action != callbacks.actions.end(),
callbacks.missing_action_message << derivative_id);
const size_t dfidx = FindIdx(derivative_id, infds);
const auto &doutfds =
FindOrDefault(callbacks.outfds, derivative_id, default_outfds);
const auto &mult_callbacks =
SelectActionCallbacks(it_action->second, callbacks.cached_actions,
derivative_id, use_cached_setup);
return std::make_shared<DerivativeOperator>(
GetTotalTrueVSize(doutfds),
GetTrueVSize(infds[dfidx]),
mult_callbacks,
FindOrEmpty(callbacks.transpose_actions, derivative_id),
infds[dfidx],
x,
infds,
doutfds,
FindOrEmpty(callbacks.assemble_sparse, derivative_id),
FindOrEmpty(callbacks.assemble_hypre, derivative_id),
FindOrEmpty(callbacks.assemble_diagonal, derivative_id),
FindOrEmpty(callbacks.setup, derivative_id));
}
}
DifferentiableOperator::DifferentiableOperator(
const std::vector<FieldDescriptor> &solutions,
const std::vector<FieldDescriptor> &parameters,
const std::vector<FieldDescriptor> &infds,
const std::vector<FieldDescriptor> &outfds,
const ParMesh &mesh) :
Operator(),
mesh(mesh),
solutions(solutions),
parameters(parameters)
infds(infds),
outfds(outfds)
{
fields.resize(solutions.size() + parameters.size());
fields_e.resize(fields.size());
solutions_l.resize(solutions.size());
parameters_l.resize(parameters.size());
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());
for (size_t i = 0; i < solutions.size(); i++)
infields_l.resize(infds.size());
for (size_t i = 0; i < infds.size(); i++)
{
fields[i] = solutions[i];
infields_l[i] = new Vector(GetVSize(infds[i]));
}
for (size_t i = 0; i < parameters.size(); i++)
infields_e.resize(infds.size());
}
void DifferentiableOperator::SetMultLevel(MultLevel level)
{
mult_level = level;
}
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)
{
return MakeStatefulDerivativeOperator(
derivative_id, x, infds, outfds,
{
fields[i + solutions.size()] = parameters[i];
}
derivative_action_callbacks,
derivative_apply_callbacks,
daction_transpose_callbacks,
derivative_outfds,
assemble_derivative_sparsematrix_callbacks,
assemble_derivative_hypreparmatrix_callbacks,
assemble_diagonal_callbacks,
derivative_setup_callbacks,
"no derivative action has been found for ID "
},
true);
}
std::shared_ptr<DerivativeOperator> DifferentiableOperator::GetDerivative(
size_t derivative_id, const MultiVector &x, const bool use_cached_setup)
{
return MakeStatefulDerivativeOperator(
derivative_id, x, infds, outfds,
{
derivative_action_callbacks,
derivative_apply_callbacks,
daction_transpose_callbacks,
derivative_outfds,
assemble_derivative_sparsematrix_callbacks,
assemble_derivative_hypreparmatrix_callbacks,
assemble_diagonal_callbacks,
derivative_setup_callbacks,
"no derivative action has been found for ID "
},
use_cached_setup);
}
std::shared_ptr<DerivativeOperator> DifferentiableOperator::GetDerivative(
size_t derivative_id)
{
MFEM_ASSERT(has_functional_integrator,
"stateless GetDerivative is available only for functionals");
const auto it_action = derivative_action_callbacks.find(derivative_id);
MFEM_ASSERT(it_action != derivative_action_callbacks.end(),
"no derivative action has been found for ID " << derivative_id);
const size_t dfidx = FindIdx(derivative_id, infds);
const auto &doutfds =
FindOrDefault(derivative_outfds, derivative_id, outfds);
return std::make_shared<DerivativeOperator>(
GetTotalTrueVSize(doutfds),
GetTrueVSize(infds[dfidx]),
it_action->second,
infds,
doutfds);
}
std::shared_ptr<DerivativeOperator> DifferentiableOperator::GetSecondDerivative(
size_t derivative_id, const Vector &x)
{
MFEM_ASSERT(has_functional_integrator,
"second derivatives are available only for functionals");
return MakeStatefulDerivativeOperator(
derivative_id, x, infds, outfds,
{
second_derivative_action_callbacks,
second_derivative_apply_callbacks,
second_daction_transpose_callbacks,
second_derivative_outfds,
assemble_second_derivative_sparsematrix_callbacks,
assemble_second_derivative_hypreparmatrix_callbacks,
assemble_second_derivative_diagonal_callbacks,
second_derivative_setup_callbacks,
"no second derivative action has been found for ID "
},
false);
}
std::shared_ptr<DerivativeOperator> DifferentiableOperator::GetSecondDerivative(
size_t derivative_id, const MultiVector &x, const bool use_cached_setup)
{
MFEM_ASSERT(has_functional_integrator,
"second derivatives are available only for functionals");
return MakeStatefulDerivativeOperator(
derivative_id, x, infds, outfds,
{
second_derivative_action_callbacks,
second_derivative_apply_callbacks,
second_daction_transpose_callbacks,
second_derivative_outfds,
assemble_second_derivative_sparsematrix_callbacks,
assemble_second_derivative_hypreparmatrix_callbacks,
assemble_second_derivative_diagonal_callbacks,
second_derivative_setup_callbacks,
"no second derivative action has been found for ID "
},
use_cached_setup);
}
#endif // MFEM_USE_MPI
+714 -958
View File
File diff suppressed because it is too large Load Diff
+64
View File
@@ -0,0 +1,64 @@
// 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 "../qspace.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 VectorQuadratureSpace *,
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);
}
};
}
+47 -2
View File
@@ -10,6 +10,7 @@
// CONTRIBUTING.md for details.
#pragma once
#include <ostream>
#include <type_traits>
namespace mfem::future
@@ -74,6 +75,15 @@ struct is_identity_fop : std::false_type {};
template <int FIELD_ID>
struct is_identity_fop<Identity<FIELD_ID>> : std::true_type {};
template <typename T>
constexpr bool is_identity_fop_v = is_identity_fop<T>::value;
template <int FIELD_ID>
inline std::ostream& operator<<(std::ostream& out, Identity<FIELD_ID>)
{
return out << "Identity<" << FIELD_ID << ">";
}
/// @brief Weight FieldOperator.
///
/// This FieldOperator is used to signal that this field contains the quadrature
@@ -90,6 +100,14 @@ struct is_weight_fop : std::false_type {};
template <>
struct is_weight_fop<Weight> : std::true_type {};
template <typename T>
constexpr bool is_weight_fop_v = is_weight_fop<T>::value;
inline std::ostream& operator<<(std::ostream& out, Weight)
{
return out << "Weight";
}
/// @brief Value FieldOperator.
///
/// This FieldOperator is used to signal that the field contains the
@@ -101,11 +119,20 @@ public:
constexpr Value() : FieldOperator<FIELD_ID>() {};
};
template< typename T >
template <typename T>
struct is_value_fop : std::false_type {};
template <int T>
struct is_value_fop<Value<T>> : std::true_type {};
template <typename T>
constexpr bool is_value_fop_v = is_value_fop<T>::value;
template <int FIELD_ID>
struct is_value_fop<Value<FIELD_ID>> : std::true_type {};
inline std::ostream& operator<<(std::ostream& out, Value<FIELD_ID>)
{
return out << "Value<" << FIELD_ID << ">";
}
/// @brief Gradient FieldOperator.
///
@@ -124,6 +151,15 @@ struct is_gradient_fop : std::false_type {};
template <int FIELD_ID>
struct is_gradient_fop<Gradient<FIELD_ID>> : std::true_type {};
template <typename T>
constexpr bool is_gradient_fop_v = is_gradient_fop<T>::value;
template <int FIELD_ID>
inline std::ostream& operator<<(std::ostream& out, Gradient<FIELD_ID>)
{
return out << "Gradient<" << FIELD_ID << ">";
}
/// @brief Sum FieldOperator.
///
/// This FieldOperator is commonly used to signal that an output of a quadrature
@@ -141,4 +177,13 @@ struct is_sum_fop : std::false_type {};
template <int FIELD_ID>
struct is_sum_fop<Sum<FIELD_ID>> : std::true_type {};
template <typename T>
constexpr bool is_sum_fop_v = is_sum_fop<T>::value;
template <int FIELD_ID>
inline std::ostream& operator<<(std::ostream& out, Sum<FIELD_ID>)
{
return out << "Sum<" << FIELD_ID << ">";
}
} // namespace mfem::future
-536
View File
@@ -1,536 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "util.hpp"
namespace mfem::future
{
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields_impl(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
// assuming the quadrature point residual has to "play nice with
// the test function"
if constexpr (is_value_fop<std::decay_t<output_t>>::value)
{
const auto [num_qp, cdim, num_dof] = B.GetShape();
const int vdim = output.vdim > 0 ? output.vdim : cdim ;
for (int dof = 0; dof < num_dof; dof++)
{
for (int vd = 0; vd < vdim; vd++)
{
real_t acc = 0.0;
for (int qp = 0; qp < num_qp; qp++)
{
acc += B(qp, 0, dof) * f(vd, 0, qp);
}
y(dof, vd) += acc;
}
}
}
else if constexpr (
is_gradient_fop<std::decay_t<output_t>>::value)
{
const auto [num_qp, dim, num_dof] = G.GetShape();
const int vdim = output.vdim;
for (int dof = 0; dof < num_dof; dof++)
{
for (int vd = 0; vd < vdim; vd++)
{
real_t acc = 0.0;
for (int d = 0; d < dim; d++)
{
for (int qp = 0; qp < num_qp; qp++)
{
acc += G(qp, d, dof) * f(vd, d, qp);
}
}
y(dof, vd) += acc;
}
}
}
else if constexpr (is_sum_fop<std::decay_t<output_t>>::value)
{
// This is the "integral over all quadrature points type" applying
// B = 1 s.t. B^T * C \in R^1.
const auto [num_qp, unused, unused1] = B.GetShape();
auto cc = Reshape(&f(0, 0, 0), num_qp);
for (int i = 0; i < num_qp; i++)
{
y(0, 0) += cc(i);
}
}
else if constexpr (is_identity_fop<std::decay_t<output_t>>::value)
{
const auto [num_qp, unused, num_dof] = B.GetShape();
const auto vdim = output.vdim;
auto cc = Reshape(&f(0, 0, 0), num_qp * vdim);
auto yy = Reshape(&y(0, 0), num_qp * vdim);
for (int i = 0; i < num_qp * vdim; i++)
{
yy(i) = cc(i);
}
}
else
{
MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented"
" for this field descriptor");
}
}
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields_tensor_impl_1d(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq,
std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d);
auto yd = Reshape(&y(0, 0), d1d, vdim);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t acc = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
acc += fqp(vd, 0, qx) * B(qx, 0, dx);
}
yd(dx, vd) = acc;
}
}
MFEM_SYNC_THREAD;
}
else if constexpr (is_gradient_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = G.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d);
auto yd = Reshape(&y(0, 0), d1d, vdim);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t acc = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
acc += fqp(vd, 0, qx) * G(qx, 0, dx);
}
yd(dx, vd) = acc;
}
}
MFEM_SYNC_THREAD;
}
else if constexpr (is_identity_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
auto fqp = Reshape(&f(0, 0, 0), output.size_on_qp, q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d);
for (int sq = 0; sq < output.size_on_qp; sq++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
yqp(sq, qx) = fqp(sq, qx);
}
MFEM_SYNC_THREAD;
}
}
else
{
MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented"
"for this field descriptor with sum factorization on"
" tensor product elements");
}
}
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields_tensor_impl_2d(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq,
std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, vdim);
auto s0 = Reshape(&scratch_mem[0](0), q1d, d1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t acc = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
acc += fqp(vd, 0, qx, qy) * B(qx, 0, dx);
}
s0(qy, dx) = acc;
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t acc = 0.0;
for (int qy = 0; qy < q1d; qy++)
{
acc += s0(qy, dx) * B(qy, 0, dy);
}
yd(dx, dy, vd) += acc;
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_gradient_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = G.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, vdim);
auto s0 = Reshape(&scratch_mem[0](0), q1d, d1d);
auto s1 = Reshape(&scratch_mem[1](0), q1d, d1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uv[2] = {0.0, 0.0};
for (int qx = 0; qx < q1d; qx++)
{
uv[0] += fqp(vd, 0, qx, qy) * G(qx, 0, dx);
uv[1] += fqp(vd, 1, qx, qy) * B(qx, 0, dx);
}
s0(qy, dx) = uv[0];
s1(qy, dx) = uv[1];
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uv[2] = {0.0, 0.0};
for (int qy = 0; qy < q1d; qy++)
{
uv[0] += s0(qy, dx) * B(qy, 0, dy);
uv[1] += s1(qy, dx) * G(qy, 0, dy);
}
yd(dx, dy, vd) += uv[0] + uv[1];
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_identity_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
// // TODO: Check if this is the right fix for all cases
// auto fqp = Reshape(&f(0, 0, 0), output.size_on_qp, q1d);
// auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d);
// for (int sq = 0; sq < output.size_on_qp; sq++)
// {
// MFEM_FOREACH_THREAD(qx, x, q1d)
// {
// yqp(sq, qx) = fqp(sq, qx);
// }
// MFEM_SYNC_THREAD;
// }
auto fqp = Reshape(&f(0, 0, 0), output.size_on_qp, q1d, q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d, q1d);
for (int sq = 0; sq < output.size_on_qp; sq++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
yqp(sq, qx, qy) = fqp(sq, qx, qy);
}
}
MFEM_SYNC_THREAD;
}
}
else
{
MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented"
" for this field descriptor with sum factorization on"
" tensor product elements");
}
}
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields_tensor_impl_3d(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq,
std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, d1d, vdim);
auto s0 = Reshape(&scratch_mem[0](0), q1d, q1d, d1d);
auto s1 = Reshape(&scratch_mem[1](0), q1d, d1d, d1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
real_t acc = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
acc += fqp(vd, 0, qx, qy, qz) * B(qx, 0, dx);
}
s0(qz, qy, dx) = acc;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
real_t acc = 0.0;
for (int qy = 0; qy < q1d; qy++)
{
acc += s0(qz, qy, dx) * B(qy, 0, dy);
}
s1(qz, dy, dx) = acc;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
MFEM_FOREACH_THREAD(dz, z, d1d)
{
real_t acc = 0.0;
for (int qz = 0; qz < q1d; qz++)
{
acc += s1(qz, dy, dx) * B(qz, 0, dz);
}
yd(dx, dy, dz, vd) += acc;
}
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_gradient_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = G.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, d1d, vdim);
auto s0 = Reshape(&scratch_mem[0](0), q1d, q1d, d1d);
auto s1 = Reshape(&scratch_mem[1](0), q1d, q1d, d1d);
auto s2 = Reshape(&scratch_mem[2](0), q1d, q1d, d1d);
auto s3 = Reshape(&scratch_mem[3](0), q1d, d1d, d1d);
auto s4 = Reshape(&scratch_mem[4](0), q1d, d1d, d1d);
auto s5 = Reshape(&scratch_mem[5](0), q1d, d1d, d1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int qx = 0; qx < q1d; qx++)
{
uvw[0] += fqp(vd, 0, qx, qy, qz) * G(qx, 0, dx);
uvw[1] += fqp(vd, 1, qx, qy, qz) * B(qx, 0, dx);
uvw[2] += fqp(vd, 2, qx, qy, qz) * B(qx, 0, dx);
}
s0(qz, qy, dx) = uvw[0];
s1(qz, qy, dx) = uvw[1];
s2(qz, qy, dx) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qz, z, q1d)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int qy = 0; qy < q1d; qy++)
{
uvw[0] += s0(qz, qy, dx) * B(qy, 0, dy);
uvw[1] += s1(qz, qy, dx) * G(qy, 0, dy);
uvw[2] += s2(qz, qy, dx) * B(qy, 0, dy);
}
s3(qz, dy, dx) = uvw[0];
s4(qz, dy, dx) = uvw[1];
s5(qz, dy, dx) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int qz = 0; qz < q1d; qz++)
{
uvw[0] += s3(qz, dy, dx) * B(qz, 0, dz);
uvw[1] += s4(qz, dy, dx) * B(qz, 0, dz);
uvw[2] += s5(qz, dy, dx) * G(qz, 0, dz);
}
yd(dx, dy, dz, vd) += uvw[0] + uvw[1] + uvw[2];
}
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_identity_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
auto fqp = Reshape(&f(0, 0, 0), output.size_on_qp, q1d, q1d, q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d, q1d, q1d);
for (int sq = 0; sq < output.size_on_qp; sq++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
yqp(sq, qx, qy, qz) = fqp(sq, qx, qy, qz);
}
}
}
MFEM_SYNC_THREAD;
}
}
else
{
MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented"
" for this field descriptor with sum factorization on"
" tensor product elements");
}
}
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq,
std::array<DeviceTensor<1>, 6> &scratch_mem,
const int &dimension,
const bool &use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 1)
{
map_quadrature_data_to_fields_tensor_impl_1d(y, f, output, dtq, scratch_mem);
}
else if (dimension == 2)
{
map_quadrature_data_to_fields_tensor_impl_2d(y, f, output, dtq, scratch_mem);
}
else if (dimension == 3)
{
map_quadrature_data_to_fields_tensor_impl_3d(y, f, output, dtq, scratch_mem);
}
else { MFEM_ABORT_KERNEL("dimension not supported"); }
}
else
{
map_quadrature_data_to_fields_impl(y, f, output, dtq);
}
}
} // namespace mfem::future
+37
View File
@@ -0,0 +1,37 @@
// 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 <typeindex>
#include <unordered_map>
#include <vector>
#include "../../general/array.hpp"
#include "fielddescriptor.hpp"
namespace mfem::future
{
struct IntegratorContext
{
const ParMesh &mesh;
const Array<int> *elem_attr;
Array<int> attr;
const 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;
};
}
-678
View File
@@ -1,678 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "util.hpp"
namespace mfem::future
{
template <typename field_operator_t>
MFEM_HOST_DEVICE inline
void map_field_to_quadrature_data_tensor_product_3d(
DeviceTensor<2> &field_qp,
const DofToQuadMap &dtq,
const DeviceTensor<1> &field_e,
const field_operator_t &input,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<field_operator_t>>::value)
{
auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e[0], d1d, d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, q1d, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, d1d, q1d);
auto s1 = Reshape(&scratch_mem[1](0), d1d, q1d, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t acc = 0.0;
for (int dx = 0; dx < d1d; dx++)
{
acc += B(qx, 0, dx) * field(dx, dy, dz, vd);
}
s0(dz, dy, qx) = acc;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
real_t acc = 0.0;
for (int dy = 0; dy < d1d; dy++)
{
acc += s0(dz, dy, qx) * B(qy, 0, dy);
}
s1(dz, qy, qx) = acc;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qz, z, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t acc = 0.0;
for (int dz = 0; dz < d1d; dz++)
{
acc += s1(dz, qy, qx) * B(qz, 0, dz);
}
fqp(vd, qx, qy, qz) = acc;
}
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (
is_gradient_fop<std::decay_t<field_operator_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const int dim = input.dim;
const auto field = Reshape(&field_e[0], d1d, d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, dim, q1d, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, d1d, q1d);
auto s1 = Reshape(&scratch_mem[1](0), d1d, d1d, q1d);
auto s2 = Reshape(&scratch_mem[2](0), d1d, q1d, q1d);
auto s3 = Reshape(&scratch_mem[3](0), d1d, q1d, q1d);
auto s4 = Reshape(&scratch_mem[4](0), d1d, q1d, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uv[2] = {0.0, 0.0};
for (int dx = 0; dx < d1d; dx++)
{
const real_t f = field(dx, dy, dz, vd);
uv[0] += f * B(qx, 0, dx);
uv[1] += f * G(qx, 0, dx);
}
s0(dz, dy, qx) = uv[0];
s1(dz, dy, qx) = uv[1];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int dy = 0; dy < d1d; dy++)
{
const real_t s0i = s0(dz, dy, qx);
uvw[0] += s1(dz, dy, qx) * B(qy, 0, dy);
uvw[1] += s0i * G(qy, 0, dy);
uvw[2] += s0i * B(qy, 0, dy);
}
s2(dz, qy, qx) = uvw[0];
s3(dz, qy, qx) = uvw[1];
s4(dz, qy, qx) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qz, z, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int dz = 0; dz < d1d; dz++)
{
uvw[0] += s2(dz, qy, qx) * B(qz, 0, dz);
uvw[1] += s3(dz, qy, qx) * B(qz, 0, dz);
uvw[2] += s4(dz, qy, qx) * G(qz, 0, dz);
}
fqp(vd, 0, qx, qy, qz) = uvw[0];
fqp(vd, 1, qx, qy, qz) = uvw[1];
fqp(vd, 2, qx, qy, qz) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
}
}
// TODO: Create separate function for clarity
else if constexpr (
std::is_same_v<std::decay_t<field_operator_t>, Weight>)
{
const int num_qp = integration_weights.GetShape()[0];
// TODO: eeek
const int q1d = (int)floor(std::pow(num_qp, 1.0/input.dim) + 0.5);
auto w = Reshape(&integration_weights[0], q1d, q1d, q1d);
auto f = Reshape(&field_qp[0], q1d, q1d, q1d);
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
f(qx, qy, qz) = w(qx, qy, qz);
}
}
}
MFEM_SYNC_THREAD;
}
else if constexpr (is_identity_fop<std::decay_t<field_operator_t>>::value)
{
const int q1d = B.GetShape()[0];
auto field = Reshape(&field_e[0], input.size_on_qp, q1d * q1d * q1d);
field_qp = field;
}
else
{
static_assert(dfem::always_false<std::decay_t<field_operator_t>>,
"can't map field to quadrature data");
}
}
template <typename field_operator_t>
MFEM_HOST_DEVICE inline
void map_field_to_quadrature_data_tensor_product_2d(
DeviceTensor<2> &field_qp,
const DofToQuadMap &dtq,
const DeviceTensor<1> &field_e,
const field_operator_t &input,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<field_operator_t>>::value)
{
auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e[0], d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t acc = 0.0;
for (int dx = 0; dx < d1d; dx++)
{
acc += B(qx, 0, dx) * field(dx, dy, vd);
}
s0(dy, qx) = acc;
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
real_t acc = 0.0;
for (int dy = 0; dy < d1d; dy++)
{
acc += s0(dy, qx) * B(qy, 0, dy);
}
fqp(vd, qx, qy) = acc;
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (
is_gradient_fop<std::decay_t<field_operator_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const int dim = input.dim;
const auto field = Reshape(&field_e[0], d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, dim, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, q1d);
auto s1 = Reshape(&scratch_mem[1](0), d1d, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uv[2] = {0.0, 0.0};
for (int dx = 0; dx < d1d; dx++)
{
const real_t f = field(dx, dy, vd);
uv[0] += f * B(qx, 0, dx);
uv[1] += f * G(qx, 0, dx);
}
s0(dy, qx) = uv[0];
s1(dy, qx) = uv[1];
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uv[2] = {0.0, 0.0};
for (int dy = 0; dy < d1d; dy++)
{
const real_t s0i = s0(dy, qx);
uv[0] += s1(dy, qx) * B(qy, 0, dy);
uv[1] += s0i * G(qy, 0, dy);
}
fqp(vd, 0, qx, qy) = uv[0];
fqp(vd, 1, qx, qy) = uv[1];
}
}
MFEM_SYNC_THREAD;
}
}
// TODO: Create separate function for clarity
else if constexpr (
std::is_same_v<std::decay_t<field_operator_t>, Weight>)
{
const int num_qp = integration_weights.GetShape()[0];
// TODO: eeek
const int q1d = (int)floor(std::pow(num_qp, 1.0/input.dim) + 0.5);
auto w = Reshape(&integration_weights[0], q1d, q1d);
auto f = Reshape(&field_qp[0], q1d, q1d);
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
f(qx, qy) = w(qx, qy);
}
}
MFEM_SYNC_THREAD;
}
else if constexpr (is_identity_fop<std::decay_t<field_operator_t>>::value)
{
const int q1d = B.GetShape()[0];
auto field = Reshape(&field_e[0], input.size_on_qp, q1d * q1d);
field_qp = field;
}
else
{
static_assert(dfem::always_false<std::decay_t<field_operator_t>>,
"can't map field to quadrature data");
}
}
template <typename field_operator_t>
MFEM_HOST_DEVICE inline
void map_field_to_quadrature_data_tensor_product_1d(
DeviceTensor<2> &field_qp,
const DofToQuadMap &dtq,
const DeviceTensor<1> &field_e,
const field_operator_t &input,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<field_operator_t>>::value)
{
auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e[0], d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t acc = 0.0;
for (int dx = 0; dx < d1d; dx++)
{
acc += B(qx, 0, dx) * field(dx, vd);
}
fqp(vd, qx) = acc;
}
}
MFEM_SYNC_THREAD;
}
else if constexpr (
is_gradient_fop<std::decay_t<field_operator_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const int dim = input.dim;
const auto field = Reshape(&field_e[0], d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, dim, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t acc = 0.0;
for (int dx = 0; dx < d1d; dx++)
{
acc += G(qx, 0, dx) * field(dx, vd);
}
fqp(vd, 0, qx) = acc;
}
MFEM_SYNC_THREAD;
}
}
// TODO: Create separate function for clarity
else if constexpr (
std::is_same_v<std::decay_t<field_operator_t>, Weight>)
{
const int num_qp = integration_weights.GetShape()[0];
// TODO: eeek
const int q1d = (int)floor(std::pow(num_qp, 1.0/input.dim) + 0.5);
auto w = Reshape(&integration_weights[0], q1d);
auto f = Reshape(&field_qp[0], q1d);
MFEM_FOREACH_THREAD(qx, x, q1d)
{
f(qx) = w(qx);
}
MFEM_SYNC_THREAD;
}
else if constexpr (is_identity_fop<std::decay_t<field_operator_t>>::value)
{
const int q1d = B.GetShape()[0];
auto field = Reshape(&field_e[0], input.size_on_qp, q1d);
field_qp = field;
}
else
{
static_assert(dfem::always_false<std::decay_t<field_operator_t>>,
"can't map field to quadrature data");
}
}
template <typename field_operator_t>
MFEM_HOST_DEVICE
void map_field_to_quadrature_data(
DeviceTensor<2> field_qp,
const DofToQuadMap &dtq,
const DeviceTensor<1> &field_e,
const field_operator_t &input,
const DeviceTensor<1, const real_t> &integration_weights)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<field_operator_t>::value)
{
auto [num_qp, dim, num_dof] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e(0), num_dof, vdim);
for (int vd = 0; vd < vdim; vd++)
{
for (int qp = 0; qp < num_qp; qp++)
{
real_t acc = 0.0;
for (int dof = 0; dof < num_dof; dof++)
{
acc += B(qp, 0, dof) * field(dof, vd);
}
field_qp(vd, qp) = acc;
}
}
}
else if constexpr (is_gradient_fop<field_operator_t>::value)
{
const auto [num_qp, dim, num_dof] = G.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e(0), num_dof, vdim);
auto f = Reshape(&field_qp[0], vdim, dim, num_qp);
for (int vd = 0; vd < vdim; vd++)
{
for (int qp = 0; qp < num_qp; qp++)
{
for (int d = 0; d < dim; d++)
{
real_t acc = 0.0;
for (int dof = 0; dof < num_dof; dof++)
{
acc += G(qp, d, dof) * field(dof, vd);
}
f(vd, d, qp) = acc;
}
}
}
}
else if constexpr (std::is_same_v<field_operator_t, Weight>)
{
const int num_qp = integration_weights.GetShape()[0];
auto f = Reshape(&field_qp[0], num_qp);
for (int qp = 0; qp < num_qp; qp++)
{
f(qp) = integration_weights(qp);
}
}
else if constexpr (is_identity_fop<field_operator_t>::value)
{
auto [num_qp, unused, num_dof] = B.GetShape();
const int size_on_qp = input.size_on_qp;
const auto field = Reshape(&field_e[0], size_on_qp * num_qp);
auto f = Reshape(&field_qp[0], size_on_qp * num_qp);
for (int i = 0; i < size_on_qp * num_qp; i++)
{
f(i) = field(i);
}
}
else
{
static_assert(dfem::always_false<field_operator_t>,
"can't map field to quadrature data");
}
}
template <typename field_operator_ts, size_t num_inputs, size_t num_fields>
MFEM_HOST_DEVICE inline
void map_fields_to_quadrature_data(
std::array<DeviceTensor<2>, num_inputs> &fields_qp,
const std::array<DeviceTensor<1>, num_fields> &fields_e,
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
const std::array<size_t, num_inputs> &input_to_field,
const field_operator_ts &fops,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const int &dimension,
const bool &use_sum_factorization = false)
{
// When the input_to_field map returns -1, this means the requested input
// is the integration weight. Weights don't have a user defined field
// attached to them and we create a dummy field which is not accessed
// inside the functions it is passed to.
const auto dummy_field_weight = DeviceTensor<1>(nullptr, 0);
for_constexpr<num_inputs>([&](auto i)
{
const DeviceTensor<1> &field_e =
(input_to_field[i] == SIZE_MAX) ? dummy_field_weight :
fields_e[input_to_field[i]];
if (use_sum_factorization)
{
if (dimension == 1)
{
map_field_to_quadrature_data_tensor_product_1d(
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
integration_weights, scratch_mem);
}
else if (dimension == 2)
{
map_field_to_quadrature_data_tensor_product_2d(
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
integration_weights, scratch_mem);
}
else if (dimension == 3)
{
map_field_to_quadrature_data_tensor_product_3d(
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
integration_weights, scratch_mem);
}
else
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ABORT("unsupported dimension");
#endif
}
}
else
{
map_field_to_quadrature_data(
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
integration_weights);
}
});
}
template <typename field_operator_t>
MFEM_HOST_DEVICE
void map_field_to_quadrature_data_conditional(
DeviceTensor<2> &field_qp,
const DeviceTensor<1> &field_e,
const DofToQuadMap &dtqmap,
field_operator_t &fop,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const bool &condition,
const int &dimension,
const bool &use_sum_factorization = false)
{
if (condition)
{
if (use_sum_factorization)
{
if (dimension == 1)
{
map_field_to_quadrature_data_tensor_product_1d(
field_qp, dtqmap, field_e, fop, integration_weights, scratch_mem);
}
else if (dimension == 2)
{
map_field_to_quadrature_data_tensor_product_2d(
field_qp, dtqmap, field_e, fop, integration_weights, scratch_mem);
}
else if (dimension == 3)
{
map_field_to_quadrature_data_tensor_product_3d(
field_qp, dtqmap, field_e, fop, integration_weights, scratch_mem);
}
}
else
{
map_field_to_quadrature_data(
field_qp, dtqmap, field_e, fop, integration_weights);
}
}
}
template <size_t num_fields, size_t num_inputs, typename field_operator_ts>
MFEM_HOST_DEVICE
void map_fields_to_quadrature_data_conditional(
std::array<DeviceTensor<2>, num_inputs> &fields_qp,
const std::array<DeviceTensor<1, const real_t>, num_fields> &fields_e,
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
field_operator_ts fops,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const std::array<bool, num_inputs> &conditions,
const bool &use_sum_factorization = false)
{
for_constexpr<num_inputs>([&](auto i)
{
map_field_to_quadrature_data_conditional(
fields_qp[i], fields_e[i], dtqmaps[i], get<i>(fops), integration_weights,
scratch_mem, conditions[i], use_sum_factorization);
});
}
template <size_t num_inputs, typename field_operator_ts>
MFEM_HOST_DEVICE
void map_direction_to_quadrature_data_conditional(
std::array<DeviceTensor<2>, num_inputs> &directions_qp,
const DeviceTensor<1> &direction_e,
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
field_operator_ts fops,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const std::array<bool, num_inputs> &conditions,
const int &dimension,
const bool &use_sum_factorization)
{
for_constexpr<num_inputs>([&](auto i)
{
if (conditions[i])
{
if (use_sum_factorization)
{
if (dimension == 1)
{
map_field_to_quadrature_data_tensor_product_1d(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights, scratch_mem);
}
else if (dimension == 2)
{
map_field_to_quadrature_data_tensor_product_2d(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights, scratch_mem);
}
else if (dimension == 3)
{
map_field_to_quadrature_data_tensor_product_3d(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights, scratch_mem);
}
}
else
{
map_field_to_quadrature_data(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights);
}
}
});
}
}
+8 -5
View File
@@ -20,7 +20,7 @@ namespace mfem::future
class ParameterSpace
{
public:
ParameterSpace(int vdim = 1) : vdim(vdim) {}
ParameterSpace(int vdim = 1) : vdim(vdim) { dtq.FE = nullptr; dtq.IntRule = nullptr; }
/// @brief Get vector dimension at each point
///
@@ -43,7 +43,7 @@ public:
/// Get spatial dimension
///
/// returns always 1.
int Dimension() const
constexpr int Dimension() const
{
return 1;
}
@@ -65,7 +65,7 @@ public:
/// It should not be used by a user.
///
/// returns identity by default that is lazy evaluated.
virtual const Operator* GetElementRestriction(ElementDofOrdering o) const
virtual const Operator* GetElementRestriction(ElementDofOrdering) const
{
if (!elem_restr)
{
@@ -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
-619
View File
@@ -1,619 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "util.hpp"
#include "qfunction_transform.hpp"
namespace mfem::future
{
/// @brief Call a qfunction with the given parameters.
///
/// @param qfunc the qfunction to call.
/// @param input_shmem the input shared memory.
/// @param residual_shmem the residual shared memory.
/// @param rs_qp the size of the residual.
/// @param num_qp the number of quadrature points.
/// @param q1d the number of quadrature points in 1D.
/// @param dimension the spatial dimension.
/// @param use_sum_factorization whether to use sum factorization.
/// @tparam qf_param_ts the tuple type of the qfunction parameters.
template <
typename qf_param_ts,
typename qfunc_t,
std::size_t num_fields>
MFEM_HOST_DEVICE inline
void call_qfunction(
qfunc_t &qfunc,
const std::array<DeviceTensor<2>, num_fields> &input_shmem,
DeviceTensor<2> &residual_shmem,
const int &rs_qp,
const int &num_qp,
const int &q1d,
const int &dimension,
const bool &use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 1)
{
MFEM_FOREACH_THREAD_DIRECT(q, x, q1d)
{
auto qf_args = decay_tuple<qf_param_ts> {};
auto r = Reshape(&residual_shmem(0, q), rs_qp);
apply_kernel(r, qfunc, qf_args, input_shmem, q);
}
}
else if (dimension == 2)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
const int q = qx + q1d * qy;
auto qf_args = decay_tuple<qf_param_ts> {};
auto r = Reshape(&residual_shmem(0, q), rs_qp);
apply_kernel(r, qfunc, qf_args, input_shmem, q);
}
}
}
else if (dimension == 3)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
auto qf_args = decay_tuple<qf_param_ts> {};
auto r = Reshape(&residual_shmem(0, q), rs_qp);
apply_kernel(r, qfunc, qf_args, input_shmem, q);
}
}
}
}
else
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ABORT("unsupported dimension for sum factorization");
#endif
}
MFEM_SYNC_THREAD;
}
else
{
MFEM_FOREACH_THREAD_DIRECT(q, x, num_qp)
{
auto qf_args = decay_tuple<qf_param_ts> {};
auto r = Reshape(&residual_shmem(0, q), rs_qp);
apply_kernel(r, qfunc, qf_args, input_shmem, q);
}
}
}
/// @brief Call a qfunction with the given parameters and
/// compute it's derivative action.
///
/// @param qfunc the qfunction to call.
/// @param input_shmem the input shared memory.
/// @param shadow_shmem the shadow shared memory.
/// @param residual_shmem the residual shared memory.
/// @param das_qp the size of the derivative action.
/// @param num_qp the number of quadrature points.
/// @param q1d the number of quadrature points in 1D.
/// @param dimension the spatial dimension.
/// @param use_sum_factorization whether to use sum factorization.
/// @tparam qf_param_ts the tuple type of the qfunction parameters.
template <
typename qf_param_ts,
typename qfunc_t,
std::size_t num_fields>
MFEM_HOST_DEVICE inline
void call_qfunction_derivative_action(
qfunc_t &qfunc,
const std::array<DeviceTensor<2>, num_fields> &input_shmem,
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
DeviceTensor<2> &residual_shmem,
const int &das_qp,
const int &num_qp,
const int &q1d,
const int &dimension,
const bool &use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 1)
{
MFEM_FOREACH_THREAD_DIRECT(q, x, q1d)
{
auto r = Reshape(&residual_shmem(0, q), das_qp);
auto qf_args = decay_tuple<qf_param_ts> {};
#ifdef MFEM_USE_ENZYME
auto qf_shadow_args = decay_tuple<qf_param_ts> {};
apply_kernel_fwddiff_enzyme(r, qfunc, qf_args, qf_shadow_args, input_shmem,
shadow_shmem, q);
#else
apply_kernel_native_dual(r, qfunc, qf_args, input_shmem, shadow_shmem, q);
#endif
}
}
else if (dimension == 2)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
const int q = qx + q1d * qy;
auto r = Reshape(&residual_shmem(0, q), das_qp);
auto qf_args = decay_tuple<qf_param_ts> {};
#ifdef MFEM_USE_ENZYME
auto qf_shadow_args = decay_tuple<qf_param_ts> {};
apply_kernel_fwddiff_enzyme(r, qfunc, qf_args, qf_shadow_args, input_shmem,
shadow_shmem, q);
#else
apply_kernel_native_dual(r, qfunc, qf_args, input_shmem, shadow_shmem, q);
#endif
}
}
}
else if (dimension == 3)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
auto r = Reshape(&residual_shmem(0, q), das_qp);
auto qf_args = decay_tuple<qf_param_ts> {};
#ifdef MFEM_USE_ENZYME
auto qf_shadow_args = decay_tuple<qf_param_ts> {};
apply_kernel_fwddiff_enzyme(r, qfunc, qf_args, qf_shadow_args, input_shmem,
shadow_shmem, q);
#else
apply_kernel_native_dual(r, qfunc, qf_args, input_shmem, shadow_shmem, q);
#endif
}
}
}
}
else
{
MFEM_ABORT_KERNEL("unsupported dimension");
}
}
else
{
MFEM_FOREACH_THREAD_DIRECT(q, x, num_qp)
{
auto r = Reshape(&residual_shmem(0, q), das_qp);
auto qf_args = decay_tuple<qf_param_ts> {};
#ifdef MFEM_USE_ENZYME
auto qf_shadow_args = decay_tuple<qf_param_ts> {};
apply_kernel_fwddiff_enzyme(r, qfunc, qf_args, qf_shadow_args, input_shmem,
shadow_shmem, q);
#else
apply_kernel_native_dual(r, qfunc, qf_args, input_shmem, shadow_shmem, q);
#endif
}
}
MFEM_SYNC_THREAD;
}
namespace detail
{
template <
typename qf_param_ts,
typename qfunc_t,
std::size_t num_fields>
MFEM_HOST_DEVICE inline
void call_qfunction_derivative(
qfunc_t &qfunc,
const std::array<DeviceTensor<2>, num_fields> &input_shmem,
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
DeviceTensor<2> &residual_shmem,
DeviceTensor<5> &qpdc,
const DeviceTensor<1, const real_t> &itod,
const int &das_qp,
const int &q)
{
const int test_vdim = qpdc.GetShape()[0];
const int test_op_dim = qpdc.GetShape()[1];
const int trial_vdim = qpdc.GetShape()[2];
const int num_qp = qpdc.GetShape()[4];
const size_t num_inputs = itod.GetShape()[0];
for (int j = 0; j < trial_vdim; j++)
{
int m_offset = 0;
for (size_t s = 0; s < num_inputs; s++)
{
const int trial_op_dim = static_cast<int>(itod(s));
if (trial_op_dim == 0)
{
continue;
}
auto d_qp = Reshape(&(shadow_shmem[s])[0], trial_vdim, trial_op_dim, num_qp);
for (int m = 0; m < trial_op_dim; m++)
{
d_qp(j, m, q) = 1.0;
auto r = Reshape(&residual_shmem(0, q), das_qp);
auto qf_args = decay_tuple<qf_param_ts> {};
#ifdef MFEM_USE_ENZYME
auto qf_shadow_args = decay_tuple<qf_param_ts> {};
apply_kernel_fwddiff_enzyme(r, qfunc, qf_args, qf_shadow_args, input_shmem,
shadow_shmem, q);
#else
apply_kernel_native_dual(r, qfunc, qf_args, input_shmem, shadow_shmem, q);
#endif
d_qp(j, m, q) = 0.0;
auto f = Reshape(&r(0), test_vdim, test_op_dim);
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
qpdc(i, k, j, m + m_offset, q) = f(i, k);
}
}
}
m_offset += trial_op_dim;
}
}
}
}
/// @brief Call a qfunction with the given parameters and
/// compute it's derivative represented by the Jacobian on
/// each quadrature point.
///
/// @param qfunc the qfunction to call.
/// @param input_shmem the input shared memory.
/// @param shadow_shmem the shadow shared memory.
/// @param residual_shmem the residual shared memory.
/// @param qpdc the quadrature point data cache holding the resulting
/// Jacobians on each quadrature point.
/// @param itod inputs trial operator dimension.
/// If input is dependent the value corresponds to the spatial dimension, otherwise
/// a zero indicates non-dependence on the variable.
/// @param das_qp the size of the derivative action.
/// @param q1d the number of quadrature points in 1D.
/// @param dimension the spatial dimension.
/// @param use_sum_factorization whether to use sum factorization.
/// @tparam qf_param_ts the tuple type of the qfunction parameters.
template <
typename qf_param_ts,
typename qfunc_t,
std::size_t num_fields>
MFEM_HOST_DEVICE inline
void call_qfunction_derivative(
qfunc_t &qfunc,
const std::array<DeviceTensor<2>, num_fields> &input_shmem,
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
DeviceTensor<2> &residual_shmem,
DeviceTensor<5> &qpdc,
const DeviceTensor<1, const real_t> &itod,
const int &das_qp,
const int &q1d,
const int &dimension,
const bool &use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 1)
{
MFEM_FOREACH_THREAD_DIRECT(q, x, q1d)
{
detail::call_qfunction_derivative<qf_param_ts>(
qfunc, input_shmem, shadow_shmem, residual_shmem, qpdc, itod, das_qp, q);
}
}
else if (dimension == 2)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
const int q = qx + q1d * qy;
detail::call_qfunction_derivative<qf_param_ts>(
qfunc, input_shmem, shadow_shmem, residual_shmem, qpdc, itod, das_qp, q);
}
}
}
else if (dimension == 3)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
detail::call_qfunction_derivative<qf_param_ts>(
qfunc, input_shmem, shadow_shmem, residual_shmem, qpdc, itod, das_qp, q);
}
}
}
}
else
{
MFEM_ABORT_KERNEL("unsupported dimension");
}
}
else
{
const int num_qp = qpdc.GetShape()[4];
MFEM_FOREACH_THREAD_DIRECT(q, x, num_qp)
{
detail::call_qfunction_derivative<qf_param_ts>(
qfunc, input_shmem, shadow_shmem, residual_shmem, qpdc, itod, das_qp, q);
}
}
MFEM_SYNC_THREAD;
}
namespace detail
{
/// @brief Apply the quadrature point data cache (qpdc) to a vector
/// (usually a direction) on quadrature point q.
///
/// The qpdc consists of compatible data to be used for integration with a test
/// operator, e.g. Jacobians of a linearization from a FE operation with a trial
/// function including integration weights and necessesary transformations.
///
/// @param fhat the qpdc applied to a vector in shadow_memory.
/// @param shadow_shmem the shadow shared memory.
/// @param qpdc the quadrature point data cache holding the resulting
/// Jacobians on each quadrature point.
/// @param itod inputs trial operator dimension.
/// If input is dependent the value corresponds to the spatial dimension, otherwise
/// a zero indicates non-dependence on the variable.
/// @param q the current quadrature point index.
template <size_t num_fields>
MFEM_HOST_DEVICE inline
void apply_qpdc(
DeviceTensor<3> &fhat,
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
const DeviceTensor<5, const real_t> &qpdc,
const DeviceTensor<1, const real_t> &itod,
const int &q)
{
const int test_vdim = qpdc.GetShape()[0];
const int test_op_dim = qpdc.GetShape()[1];
const int trial_vdim = qpdc.GetShape()[2];
const int num_qp = qpdc.GetShape()[4];
const size_t num_inputs = itod.GetShape()[0];
for (int i = 0; i < test_vdim; i++)
{
for (int k = 0; k < test_op_dim; k++)
{
real_t sum = 0.0;
int m_offset = 0;
for (size_t s = 0; s < num_inputs; s++)
{
const int trial_op_dim = static_cast<int>(itod(s));
if (trial_op_dim == 0)
{
continue;
}
const auto d_qp =
Reshape(&(shadow_shmem[s])[0], trial_vdim, trial_op_dim, num_qp);
for (int j = 0; j < trial_vdim; j++)
{
for (int m = 0; m < trial_op_dim; m++)
{
sum += qpdc(i, k, j, m + m_offset, q) * d_qp(j, m, q);
}
}
m_offset += trial_op_dim;
}
fhat(i, k, q) = sum;
}
}
}
}
/// @brief Apply the quadrature point data cache (qpdc) to a vector
/// (usually a direction).
///
/// The qpdc consists of compatible data to be used for integration with a test
/// operator, e.g. Jacobians of a linearization from a FE operation with a trial
/// function including integration weights and necessesary transformations.
///
/// @param fhat the qpdc applied to a vector in shadow_memory.
/// @param shadow_shmem the shadow shared memory.
/// @param qpdc the quadrature point data cache holding the resulting
/// Jacobians on each quadrature point.
/// @param itod inputs trial operator dimension.
/// If input is dependent the value corresponds to the spatial dimension, otherwise
/// a zero indicates non-dependence on the variable.
/// @param q1d number of quadrature points in 1D.
/// @param dimension spatial dimension.
/// @param use_sum_factorization whether to use sum factorization.
template <size_t num_fields>
MFEM_HOST_DEVICE inline
void apply_qpdc(
DeviceTensor<3> &fhat,
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
const DeviceTensor<5, const real_t> &qpdc,
const DeviceTensor<1, const real_t> &itod,
const int &q1d,
const int &dimension,
const bool &use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 1)
{
MFEM_FOREACH_THREAD_DIRECT(q, x, q1d)
{
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
}
}
else if (dimension == 2)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
const int q = qx + q1d * qy;
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
}
}
}
else if (dimension == 3)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
}
}
}
}
else
{
MFEM_ABORT_KERNEL("unsupported dimension");
}
}
else
{
const int num_qp = qpdc.GetShape()[4];
MFEM_FOREACH_THREAD_DIRECT(q, x, num_qp)
{
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
}
}
}
template <typename qfunc_t, typename args_ts, size_t num_args>
MFEM_HOST_DEVICE inline
void apply_kernel(
DeviceTensor<1, real_t> &f_qp,
const qfunc_t &qfunc,
args_ts &args,
const std::array<DeviceTensor<2>, num_args> &u,
int qp)
{
process_qf_args(u, args, qp);
process_qf_result(f_qp, get<0>(apply(qfunc, args)));
}
template <typename qfunc_t, typename arg_ts, size_t num_args>
MFEM_HOST_DEVICE inline
void apply_kernel_native_dual(
DeviceTensor<1, real_t> &f_qp,
const qfunc_t &qfunc,
arg_ts &args,
const std::array<DeviceTensor<2>, num_args> &u,
const std::array<DeviceTensor<2>, num_args> &v,
const int &qp_idx)
{
process_qf_args(u, v, args, qp_idx);
auto r = get<0>(apply(qfunc, args));
process_derivative_from_native_dual(f_qp, r);
}
#ifdef MFEM_USE_ENZYME
template <typename func_t, typename... arg_ts>
MFEM_HOST_DEVICE inline
auto qfunction_wrapper(const func_t &f, arg_ts &&...args)
{
return f(args...);
}
// Version for active function arguments only
//
// This is an Enzyme regression and can be removed in later versions.
template <typename qfunc_t, typename arg_ts, std::size_t... Is,
typename inactive_arg_ts>
MFEM_HOST_DEVICE inline
auto fwddiff_apply_enzyme_indexed(qfunc_t &qfunc, arg_ts &&args,
arg_ts &&shadow_args,
std::index_sequence<Is...>,
inactive_arg_ts &&inactive_args,
std::index_sequence<>)
{
using qf_return_t = typename create_function_signature<
decltype(&qfunc_t::operator())>::type::return_t;
return __enzyme_fwddiff<qf_return_t>(
qfunction_wrapper<qfunc_t, decltype(get<Is>(args))...>, enzyme_const,
(void *)&qfunc, enzyme_dup, &get<Is>(args)..., enzyme_interleave,
&get<Is>(shadow_args)...);
}
// Interleave function arguments for enzyme
template <typename qfunc_t, typename arg_ts, std::size_t... Is,
typename inactive_arg_ts, std::size_t... Js>
MFEM_HOST_DEVICE inline
auto fwddiff_apply_enzyme_indexed(qfunc_t &qfunc, arg_ts &&args,
arg_ts &&shadow_args,
std::index_sequence<Is...>,
inactive_arg_ts &&inactive_args,
std::index_sequence<Js...>)
{
using qf_return_t = typename create_function_signature<
decltype(&qfunc_t::operator())>::type::return_t;
return __enzyme_fwddiff<qf_return_t>(
qfunction_wrapper<qfunc_t, decltype(get<Is>(args))...,
decltype(get<Js>(inactive_args))...>,
enzyme_const, (void *)&qfunc, enzyme_dup, &get<Is>(args)...,
enzyme_const, &get<Js>(inactive_args)..., enzyme_interleave,
&get<Is>(shadow_args)...);
}
template <typename qfunc_t, typename arg_ts, typename inactive_arg_ts>
MFEM_HOST_DEVICE inline
auto fwddiff_apply_enzyme(qfunc_t &qfunc, arg_ts &&args,
arg_ts &&shadow_args,
inactive_arg_ts &&inactive_args)
{
auto arg_indices = std::make_index_sequence<
tuple_size<std::remove_reference_t<arg_ts>>::value> {};
auto inactive_arg_indices = std::make_index_sequence<
tuple_size<std::remove_reference_t<inactive_arg_ts>>::value> {};
return fwddiff_apply_enzyme_indexed(qfunc, args, shadow_args, arg_indices,
inactive_args, inactive_arg_indices);
}
template <typename qfunc_t, typename arg_ts, size_t num_args>
MFEM_HOST_DEVICE inline
void apply_kernel_fwddiff_enzyme(
DeviceTensor<1, real_t> &f_qp,
qfunc_t &qfunc,
arg_ts &args,
arg_ts &shadow_args,
const std::array<DeviceTensor<2>, num_args> &u,
const std::array<DeviceTensor<2>, num_args> &v,
int qp_idx)
{
process_qf_args(u, args, qp_idx);
process_qf_args(v, shadow_args, qp_idx);
process_qf_result(f_qp,
get<0>(fwddiff_apply_enzyme(qfunc, args, shadow_args, tuple<> {})));
}
#endif // MFEM_USE_ENZYME
} // namespace mfem::future
-346
View File
@@ -1,346 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
#include "util.hpp"
#include "../../linalg/tensor.hpp"
namespace mfem::future
{
template <typename T0, typename T1, typename T2>
MFEM_HOST_DEVICE
void process_qf_arg(const T0 &, const T1 &, T2 &)
{
static_assert(dfem::always_false<T0, T1, T2>,
"process_qf_arg not implemented for arg type");
}
template <typename T>
MFEM_HOST_DEVICE
void process_qf_arg(
const DeviceTensor<1, T> &u,
const DeviceTensor<1, T> &v,
T &arg)
{
arg = u(0);
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
tensor<dual<T, T>, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i).value = u((i * n) + j);
}
}
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
dual<T, T> &arg)
{
arg.value = u(0);
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
const DeviceTensor<1> &v,
dual<T, T> &arg)
{
arg.value = u(0);
arg.gradient = v(0);
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
const DeviceTensor<1> &v,
tensor<dual<T, T>, n> &arg)
{
for (int i = 0; i < n; i++)
{
arg(i).value = u(i);
arg(i).gradient = v(i);
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
const DeviceTensor<1> &v,
tensor<dual<T, T>, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i).value = u((i * n) + j);
arg(j, i).gradient = v((i * n) + j);
}
}
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<dual<T, T>, n> &x)
{
for (size_t i = 0; i < n; i++)
{
r(i) = x(i).value;
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<dual<T, T>, n, m> &x)
{
for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < m; j++)
{
r(i + n * j) = x(i, j).value;
}
}
}
template <typename arg_type>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<2> &u,
const DeviceTensor<2> &v,
arg_type &arg,
const int &qp)
{
const auto u_qp = Reshape(&u(0, qp), u.GetShape()[0]);
const auto v_qp = Reshape(&v(0, qp), v.GetShape()[0]);
process_qf_arg(u_qp, v_qp, arg);
}
template <size_t num_fields, typename qf_args>
MFEM_HOST_DEVICE inline
void process_qf_args(
const std::array<DeviceTensor<2>, num_fields> &u,
const std::array<DeviceTensor<2>, num_fields> &v,
qf_args &args,
const int &qp)
{
for_constexpr<tuple_size<qf_args>::value>([&](auto i)
{
process_qf_arg(u[i], v[i], get<i>(args), qp);
});
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_derivative_from_native_dual(
DeviceTensor<1, T> &r,
const tensor<dual<T, T>, n, m> &x)
{
for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < m; j++)
{
r(i + n * j) = x(i, j).gradient;
}
}
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_derivative_from_native_dual(
DeviceTensor<1, T> &r,
const tensor<dual<T, T>, n> &x)
{
for (size_t i = 0; i < n; i++)
{
r(i) = x(i).gradient;
}
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_derivative_from_native_dual(
DeviceTensor<1, T> &r,
const dual<T, T> &x)
{
r(0) = x.gradient;
}
template <typename T0, typename T1>
MFEM_HOST_DEVICE inline
void process_qf_arg(const T0 &, T1 &)
{
static_assert(dfem::always_false<T0, T1>,
"process_qf_arg not implemented for arg type");
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
T &arg)
{
arg = u(0);
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
tensor<T> &arg)
{
arg(0) = u(0);
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
tensor<T, n> &arg)
{
for (int i = 0; i < n; i++)
{
arg(i) = u(i);
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
tensor<T, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i) = u((i * n) + j);
}
}
}
template <typename arg_type>
MFEM_HOST_DEVICE inline
void process_qf_arg(const DeviceTensor<2> &u, arg_type &arg, int qp)
{
const auto u_qp = Reshape(&u(0, qp), u.GetShape()[0]);
process_qf_arg(u_qp, arg);
}
template <size_t num_fields, typename qf_args>
MFEM_HOST_DEVICE inline
void process_qf_args(
const std::array<DeviceTensor<2>, num_fields> &u,
qf_args &args,
const int &qp)
{
for_constexpr<tuple_size<qf_args>::value>([&](auto i)
{
process_qf_arg(u[i], get<i>(args), qp);
});
}
template <typename T0, typename T1>
MFEM_HOST_DEVICE inline
Vector process_qf_result(T0, T1)
{
static_assert(dfem::always_false<T0, T1>,
"process_qf_result not implemented for result type");
return Vector{};
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const T &x)
{
r(0) = x;
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1> &r,
const dual<T, T> &x)
{
r(0) = x.value;
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<T> &x)
{
r(0) = x(0);
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<T, n> &x)
{
for (size_t i = 0; i < n; i++)
{
r(i) = x(i);
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<T, n, m> &x)
{
for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < m; j++)
{
r(i + n * j) = x(i, j);
}
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
const DeviceTensor<1, T> &v,
tensor<T, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i) = u((i * n) + j);
}
}
}
} // namespace mfem::future
+638 -738
View File
File diff suppressed because it is too large Load Diff
+1017 -787
View File
File diff suppressed because it is too large Load Diff
+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"
+12 -8
View File
@@ -48,7 +48,11 @@ namespace mfem
#define MFEM_REGISTER_KERNELS(KernelName, KernelType, ...) \
MFEM_EXPAND(MFEM_EXPAND(MFEM_REGISTER_KERNELS_N(__VA_ARGS__,2,1,)) \
(KernelName,KernelType,__VA_ARGS__))
(MFEM_EXPORT, KernelName, KernelType, __VA_ARGS__))
#define MFEM_REGISTER_KERNELS_HEADER_ONLY(KernelName, KernelType, ...) \
MFEM_EXPAND(MFEM_EXPAND(MFEM_REGISTER_KERNELS_N(__VA_ARGS__,2,1,)) \
(, KernelName, KernelType, __VA_ARGS__))
#define MFEM_REGISTER_KERNELS_N(_1, _2, N, ...) MFEM_REGISTER_KERNELS_##N
@@ -58,19 +62,19 @@ namespace mfem
// Version of MFEM_REGISTER_KERNELS without any "optional" (non-dispatch)
// parameters.
#define MFEM_REGISTER_KERNELS_1(KernelName, KernelType, Params) \
MFEM_REGISTER_KERNELS_(KernelName, KernelType, Params, (), Params)
#define MFEM_REGISTER_KERNELS_1(ExportMacro, KernelName, KernelType, Params) \
MFEM_REGISTER_KERNELS_(ExportMacro, KernelName, KernelType, Params, (), Params)
// Version of MFEM_REGISTER_KERNELS without any optional (non-dispatch)
// parameters (e.g. NBZ).
#define MFEM_REGISTER_KERNELS_2(KernelName, KernelType, Params, OptParams) \
MFEM_REGISTER_KERNELS_(KernelName, KernelType, Params, OptParams, \
#define MFEM_REGISTER_KERNELS_2(ExportMacro, KernelName, KernelType, Params, OptParams) \
MFEM_REGISTER_KERNELS_(ExportMacro, KernelName, KernelType, Params, OptParams, \
(MFEM_PARAM_LIST Params, MFEM_PARAM_LIST OptParams))
// P1 are the parameters, P2 are the optional (non-dispatch parameters), and P3
// is the concatenation of P1 and P2. We need to pass it as a separate argument
// to avoid a trailing comma in the case that P2 is empty.
#define MFEM_REGISTER_KERNELS_(KernelName, KernelType, P1, P2, P3) \
#define MFEM_REGISTER_KERNELS_(ExportMacro, KernelName, KernelType, P1, P2, P3) \
class KernelName \
: public ::mfem::KernelDispatchTable< \
KernelName, KernelType, \
@@ -80,8 +84,8 @@ namespace mfem
const char *kernel_name = MFEM_KERNEL_NAME(KernelName); \
using KernelSignature = KernelType; \
template <MFEM_PARAM_LIST P3> static KernelSignature Kernel(); \
static MFEM_EXPORT KernelSignature Fallback(MFEM_PARAM_LIST P1); \
static MFEM_EXPORT KernelName &Get() { \
static ExportMacro KernelSignature Fallback(MFEM_PARAM_LIST P1); \
static ExportMacro KernelName &Get() { \
static KernelName table; \
return table; \
} \
+1828 -21
View File
File diff suppressed because it is too large Load Diff
+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. */
+21
View File
@@ -79,6 +79,27 @@ public:
Vector::operator=(orig);
}
/** @brief Construct a QuadratureFunction on the given
VectorQuadratureSpace, @a vqspace.
After construction, the QuadratureFunction does not need the
VectorQuadratureSpace object. Instead, it uses directly its underlying
QuadratureSpaceBase object. */
QuadratureFunction(VectorQuadratureSpace &vqspace)
: QuadratureFunction(*vqspace.GetSpace(), vqspace.GetVDim())
{ }
/** @brief Construct a QuadratureFunction on the given
VectorQuadratureSpace, @a vqspace, with the given MemoryType, @a mt, used
for the underlying Vector object.
After construction, the QuadratureFunction does not need the
VectorQuadratureSpace object. Instead, it uses directly its underlying
QuadratureSpaceBase object. */
QuadratureFunction(VectorQuadratureSpace &vqspace, MemoryType mt)
: QuadratureFunction(*vqspace.GetSpace(), mt, vqspace.GetVDim())
{ }
/// Read a QuadratureFunction from the stream @a in.
/** The QuadratureFunction assumes ownership of the read QuadratureSpace. */
QuadratureFunction(Mesh *mesh, std::istream &in);
+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
+304
View File
@@ -0,0 +1,304 @@
// 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
/// \cond DO_NOT_DOCUMENT
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(""); }
}
/// \endcond DO_NOT_DOCUMENT
} // 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
+741
View File
@@ -0,0 +1,741 @@
// 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_, // q_der
real_t *e_, // e_vec
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[1][MQ1*MQ1*MQ1];
MFEM_SHARED real_t sm1[1][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(dz,qy,qx) = 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(dz,qy,qx);
}
DDQ(dz,dy,qx) = 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(dz,dy,qx);
}
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
/// \cond DO_NOT_DOCUMENT
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(""); }
}
/// \endcond DO_NOT_DOCUMENT
} // 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
+65 -3
View File
@@ -27,7 +27,11 @@ enum class QSpaceOffsetStorage
/// Abstract base class for QuadratureSpace and FaceQuadratureSpace.
/** This class represents the storage layout for QuadratureFunction%s, that may
be defined either on mesh elements or mesh faces. */
be defined either on mesh elements or mesh faces.
This class represents the layout for a single scalar value at each
quadrature point. Use class VectorQuadratureSpace to represent a space with
multiple (vector) values at each quadrature point. */
class QuadratureSpaceBase
{
protected:
@@ -158,8 +162,62 @@ public:
virtual ~QuadratureSpaceBase() { }
};
/// Vector version of the scalar class QuadratureSpaceBase.
class VectorQuadratureSpace
{
protected:
/// Points to an external object provided during construction. Not owned.
QuadratureSpaceBase *qspace;
/// Vector dimension.
int vdim;
public:
/** @brief Construct a VectorQuadratureSpace on the given
QuadratureSpaceBase, @a qspace_, with the given vector dimension,
@a vdim_.
The VectorQuadratureSpace does not assume ownership of the
QuadratureSpaceBase, @a qspace_. */
VectorQuadratureSpace(QuadratureSpaceBase &qspace_, int vdim_)
: qspace(&qspace_),
vdim(vdim_)
{ }
/// Copy constructor: default.
VectorQuadratureSpace(const VectorQuadratureSpace &) = default;
/// Copy assignment: default.
VectorQuadratureSpace &operator=(const VectorQuadratureSpace &) = default;
/// Move construction is not allowed.
VectorQuadratureSpace(VectorQuadratureSpace &&) = delete;
/// Move assignment is not allowed.
VectorQuadratureSpace &operator=(VectorQuadratureSpace &&) = delete;
/// Destructor: default.
~VectorQuadratureSpace() = default;
/// Get the associated scalar QuadratureSpaceBase object.
QuadratureSpaceBase *GetSpace() { return qspace; }
/// Get the associated scalar QuadratureSpaceBase object (const version).
const QuadratureSpaceBase *GetSpace() const { return qspace; }
/// Get the vector dimension.
int GetVDim() const { return vdim; }
/** @brief Get the total size (on this MPI-rank in parallel) of the
VectorQuadratureSpace. */
int GetVSize() const { return qspace->GetSize() * vdim; }
};
/// Class representing the storage layout of a QuadratureFunction.
/** Multiple QuadratureFunction%s can share the same QuadratureSpace. */
/** Multiple QuadratureFunction%s can share the same QuadratureSpace.
This class represents the layout for a single scalar value at each
quadrature point. Use class VectorQuadratureSpace to represent a space with
multiple (vector) values at each quadrature point. */
class QuadratureSpace : public QuadratureSpaceBase
{
protected:
@@ -209,7 +267,11 @@ public:
/// Class representing the storage layout of a FaceQuadratureFunction.
/** FaceQuadratureSpace is defined on either the interior or boundary faces
of a mesh, depending on the provided FaceType. */
of a mesh, depending on the provided FaceType.
This class represents the layout for a single scalar value at each
quadrature point. Use class VectorQuadratureSpace to represent a space with
multiple (vector) values at each quadrature point. */
class FaceQuadratureSpace : public QuadratureSpaceBase
{
FaceType face_type; ///< Is the space defined on interior or boundary faces?
+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));
};
}
+1
View File
@@ -27,6 +27,7 @@ extern int enzyme_dupnoneed;
extern int enzyme_out;
extern int enzyme_const;
extern int enzyme_interleave;
extern int enzyme_runtime_activity;
#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
#define MFEM_DEVICE_EXTERN_STMT(name) extern __device__ int name;
+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
+21
View File
@@ -65,6 +65,27 @@ struct is_dual_number<dual<value_type, gradient_type> >
static constexpr bool value = true; ///< whether or not type T is a dual number
};
/** @brief class for checking if a type is a nested dual number or not */
template <typename T>
struct is_nested_dual_number
{
static constexpr bool value = false; ///< whether or not type T is a nested dual number
};
template <typename value_type, typename gradient_type>
struct is_nested_dual_number<dual<dual<value_type, gradient_type>,
dual<value_type, gradient_type> > >
{
static constexpr bool value = true; ///< whether or not type T is a nested dual number
};
template <typename V, typename G>
struct is_nested_dual_number<dual<V, G>>
{
static constexpr bool value =
is_dual_number<V>::value || is_dual_number<G>::value;
};
/** @brief addition of a dual number and a non-dual number */
template <typename other_type, typename value_type, typename gradient_type,
typename = typename std::enable_if<
+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
+240
View File
@@ -0,0 +1,240 @@
// 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*,const 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; }
/** @brief Construct a MultiVector referencing multiple const Vectors given
as arguments. Individual blocks are read-only; non-const operator[]
will assert. */
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<const VectorTypes&,const Vector&>...>, bool> = true>
MultiVector(const VectorTypes &...vs) { MakeRef(vs...); }
/** @brief Update the @a i-th MultiVector block to reference the given
const Vector @a v. The block becomes read-only. */
inline void MakeRef(int i, const Vector &v) { blocks[i] = &v; }
/** @brief Update the MultiVector to reference multiple const Vectors given
as arguments. Individual blocks are read-only; non-const operator[]
will assert. */
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<const VectorTypes&,const Vector&>...>, bool> = true>
inline void MakeRef(const VectorTypes &...vs);
};
// Inline and template methods
inline Vector &MultiVector::operator[](int i)
{
auto &bi = blocks[i];
MFEM_ASSERT(bi.index() != 2, "Non-const access to a const Vector block");
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) :
(bi.index() == 1) ? *std::get<1>(bi) :
/**/ *std::get<2>(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];
}
}
}
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<const VectorTypes&,const Vector&>...>, bool>>
inline void MultiVector::MakeRef(const VectorTypes &...vs)
{
blocks.resize(sizeof...(vs));
if constexpr (sizeof...(vs) > 0)
{
const std::array vs_p{&static_cast<const Vector&>(vs)...};
for (std::size_t i = 0; i < sizeof...(vs); i++)
{
blocks[i] = vs_p[i];
}
}
}
} // namespace mfem
#endif // MFEM_MULTIVECTOR_HPP
+1
View File
@@ -13,6 +13,7 @@
#define MFEM_OPERATOR
#include "vector.hpp"
#include "multivector.hpp"
namespace mfem
{
+156 -125
View File
@@ -17,9 +17,9 @@
#pragma once
#include "../general/backends.hpp"
#include "dual.hpp"
#include <limits>
#include <utility>
#include <type_traits> // for std::false_type
namespace mfem
@@ -27,140 +27,130 @@ namespace mfem
namespace future
{
template <typename T, int... n>
template <typename T, int... Dims>
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 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 val) : values(val) {}
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 T& operator()(int) { return values; }
MFEM_HOST_DEVICE const T& operator()(int) const { return values; }
MFEM_HOST_DEVICE operator T() const { return values; }
MFEM_HOST_DEVICE tensor& operator=(T val) { values = val; return *this; }
T values;
};
template < typename T, int n0 >
struct tensor<T, n0>
template <typename T, int N0>
struct tensor<T, N0>
{
using type = T;
static constexpr int ndim = 1;
static constexpr int first_dim = n0;
static constexpr int ndim = 1;
static constexpr int first_dim = N0;
MFEM_HOST_DEVICE T& operator[](int i) { return values[i]; }
MFEM_HOST_DEVICE const T& operator[](int i) const { return values[i]; }
MFEM_HOST_DEVICE T& operator()(int i) { return values[i]; }
MFEM_HOST_DEVICE const T& operator()(int i) const { return values[i]; }
T values[n0];
T values[N0];
};
template < typename T >
template <typename T>
struct tensor<T, 0>
{
using type = T;
static constexpr int ndim = 1;
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 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; }
T values;
};
template < typename T, int n0, int n1 >
struct tensor<T, n0, n1>
template <typename T, int N0, int N1, int... Rest>
struct tensor<T, N0, N1, Rest...>
{
using type = T;
static constexpr int ndim = 2;
static constexpr int first_dim = n0;
MFEM_HOST_DEVICE tensor< T, n1 >& operator[](int i) { return values[i]; }
MFEM_HOST_DEVICE const tensor< T, n1 >& operator[](int i) const { return values[i]; }
MFEM_HOST_DEVICE tensor< T, n1 >& operator()(int i) { return values[i]; }
MFEM_HOST_DEVICE const tensor< T, n1 >& operator()(int i) const { return values[i]; }
MFEM_HOST_DEVICE T& operator()(int i, int j) { return values[i][j]; }
MFEM_HOST_DEVICE const T& operator()(int i, int j) const { return values[i][j]; }
tensor < T, n1 > values[n0];
};
using sub_tensor = tensor<T, N1, Rest...>;
static constexpr int ndim = 2 + sizeof...(Rest);
static constexpr int first_dim = N0;
template < typename T, int n1 >
struct tensor<T, 0, n1>
{
using type = T;
static constexpr int ndim = 2;
static constexpr int first_dim = 0;
MFEM_HOST_DEVICE tensor< T, n1 >& operator[](int /*unused*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1 >& operator[](int /*unused*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n1 >& operator()(int /*unused*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1 >& operator()(int /*unused*/) const { return values; }
MFEM_HOST_DEVICE T& operator()(int /*unused*/, int j) { return values[j]; }
MFEM_HOST_DEVICE const T& operator()(int /*unused*/, int j) const { return values[j]; }
tensor < T, n1 > values;
};
static constexpr bool is_zero_dim = (N0 == 0);
static constexpr int storage_size = is_zero_dim ? 1 : N0;
using storage_type =
std::conditional_t<is_zero_dim, sub_tensor, sub_tensor[storage_size]>;
storage_type values;
template < typename T, int n0, int n1, int n2 >
struct tensor<T, n0, n1, n2>
{
using type = T;
static constexpr int ndim = 3;
static constexpr int first_dim = n0;
MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator[](int i) { return values[i]; }
MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator[](int i) const { return values[i]; }
MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator()(int i) { return values[i]; }
MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator()(int i) const { return values[i]; }
MFEM_HOST_DEVICE tensor< T, n2 >& operator()(int i, int j) { return values[i][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[i][j][k]; }
MFEM_HOST_DEVICE const T& operator()(int i, int j, int k) const { return values[i][j][k]; }
tensor < T, n1, n2 > values[n0];
};
MFEM_HOST_DEVICE sub_tensor& operator[](int i)
{
if constexpr (is_zero_dim)
{
static_cast<void>(i);
return values;
}
else { return values[i]; }
}
template < typename T, int n0, int n1, int n2, int n3 >
struct tensor<T, n0, n1, n2, n3>
{
using type = T;
static constexpr int ndim = 4;
static constexpr int first_dim = n0;
MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator[](int i) { return values[i]; }
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator[](int i) const { return values[i]; }
MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator()(int i) { return values[i]; }
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator()(int i) const { return values[i]; }
MFEM_HOST_DEVICE tensor< T, n2, n3 >& operator()(int i, int j) { return values[i][j]; }
MFEM_HOST_DEVICE const tensor< T, n2, n3 >& operator()(int i, int j) const { return values[i][j]; }
MFEM_HOST_DEVICE tensor< T, n3 >& operator()(int i, int j, int k) { return values[i][j][k]; }
MFEM_HOST_DEVICE const tensor< T, n3 >& operator()(int i, int j, int k) const { return values[i][j][k]; }
MFEM_HOST_DEVICE T& operator()(int i, int j, int k, int l) { return values[i][j][k][l]; }
MFEM_HOST_DEVICE const T& operator()(int i, int j, int k, int l) const { return values[i][j][k][l]; }
tensor < T, n1, n2, n3 > values[n0];
};
MFEM_HOST_DEVICE const sub_tensor& operator[](int i) const
{
if constexpr (is_zero_dim)
{
static_cast<void>(i);
return values;
}
else { return values[i]; }
}
template < typename T, int n0, int n1, int n2, int n3, int n4 >
struct tensor<T, n0, n1, n2, n3, n4>
{
using type = T;
static constexpr int ndim = 5;
static constexpr int first_dim = n0;
MFEM_HOST_DEVICE tensor< T, n1, n2, n3, n4 >& operator[](int i) { return values[i]; }
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3, n4 >& operator[](int i) const { return values[i]; }
MFEM_HOST_DEVICE tensor< T, n1, n2, n3, n4 >& operator()(int i) { return values[i]; }
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3, n4 >& operator()(int i) const { return values[i]; }
MFEM_HOST_DEVICE tensor< T, n2, n3, n4 >& operator()(int i, int j) { return values[i][j]; }
MFEM_HOST_DEVICE const tensor< T, n2, n3, n4 >& operator()(int i,
int j) const { return values[i][j]; }
MFEM_HOST_DEVICE tensor< T, n3, n4>& operator()(int i, int j, int k) { return values[i][j][k]; }
MFEM_HOST_DEVICE const tensor< T, n3, n4>& operator()(int i, int j,
int k) const { return values[i][j][k]; }
MFEM_HOST_DEVICE tensor< T, n4 >& operator()(int i, int j, int k, int l) { return values[i][j][k][l]; }
MFEM_HOST_DEVICE const tensor< T, n4 >& operator()(int i, int j, int k,
int l) const { return values[i][j][k][l]; }
MFEM_HOST_DEVICE T& operator()(int i, int j, int k, int l, int m) { return values[i][j][k][l][m]; }
MFEM_HOST_DEVICE const T& operator()(int i, int j, int k, int l, int m) const { return values[i][j][k][l][m]; }
tensor < T, n1, n2, n3, n4 > values[n0];
MFEM_HOST_DEVICE sub_tensor& operator()(int i)
{
return (*this)[i];
}
MFEM_HOST_DEVICE const sub_tensor& operator()(int i) const
{
return (*this)[i];
}
template <typename... Is>
MFEM_HOST_DEVICE auto& operator()(int i, int j, Is... rest)
{
if constexpr (sizeof...(rest) == 0)
{
return (*this)[i][j];
}
else
{
return (*this)[i](j, rest...);
}
}
template <typename... Is>
MFEM_HOST_DEVICE const auto& operator()(int i, int j, Is... rest) const
{
if constexpr (sizeof...(rest) == 0)
{
return (*this)[i][j];
}
else
{
return (*this)[i](j, rest...);
}
}
};
/**
@@ -270,10 +260,10 @@ MFEM_HOST_DEVICE constexpr zero operator/(zero, T /*other*/)
return zero{};
}
/** @brief `zero` plus `zero` is `zero */
/** @brief `zero` plus `zero` is `zero` */
MFEM_HOST_DEVICE constexpr zero operator+=(zero, zero) { return zero{}; }
/** @brief `zero` minus `zero` is `zero */
/** @brief `zero` minus `zero` is `zero` */
MFEM_HOST_DEVICE constexpr zero operator-=(zero, zero) { return zero{}; }
/** @brief let `zero` be accessed like a tuple */
@@ -305,13 +295,10 @@ MFEM_HOST_DEVICE zero dot(zero, const T&)
* @tparam n2 The second dimension
*/
template <typename T, int n1, int n2 = 1>
using reduced_tensor = typename std::conditional<
(n1 == 1 && n2 == 1), T,
typename std::conditional<n1 == 1, tensor<T, n2>,
typename std::conditional<n2 == 1, tensor<T, n1>, tensor<T, n1, n2>
>::type
>::type
>::type;
using reduced_tensor =
std::conditional_t<(n1 == 1 && n2 == 1), T,
std::conditional_t<(n1 == 1), tensor<T, n2>,
std::conditional_t<(n2 == 1), tensor<T, n1>, tensor<T, n1, n2>>>>;
/**
* @brief Creates a tensor of requested dimension by subsequent calls to a functor
@@ -462,7 +449,7 @@ tensor<T, n> get_col(tensor<T, m, n> A, int j)
/// @overload
template <typename T> MFEM_HOST_DEVICE
tensor<T, 1> get_col(tensor<T, 1, 1> A, int j)
tensor<T, 1> get_col(tensor<T, 1, 1> A, [[maybe_unused]] int j)
{
return tensor<T, 1> {A[0][0]};
}
@@ -541,9 +528,16 @@ 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;
}
@@ -563,9 +557,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;
}
@@ -729,9 +730,9 @@ auto outer(S A, T B) -> decltype(A * B)
}
template <typename T, int n, int m> MFEM_HOST_DEVICE
tensor<T, n + m> flatten(tensor<T, n, m> A)
tensor<T, n * m> flatten(tensor<T, n, m> A)
{
tensor<T, n + m> B{};
tensor<T, n * m> B{};
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
@@ -1218,6 +1219,28 @@ decltype(S {} * T{})
return AB;
}
// tensor<T> (rank-0 scalar wrapper) acts as a scalar in multiplication
template <typename S, typename T, int... m> MFEM_HOST_DEVICE
auto operator*(const tensor<S, m...>& A, const tensor<T>& scale) ->
tensor<decltype(S {} * T{}), m...>
{
return A * static_cast<T>(scale);
}
template <typename S, typename T, int... m> MFEM_HOST_DEVICE
auto operator*(const tensor<T>& scale, const tensor<S, m...>& A) ->
tensor<decltype(T {} * S{}), m...>
{
return static_cast<T>(scale) * A;
}
template <typename S, typename T> MFEM_HOST_DEVICE
auto operator*(const tensor<S>& A, const tensor<T>& B) ->
tensor<decltype(S {} * T{})>
{
return tensor<decltype(S{} * T{})>{A.values * B.values};
}
/**
* @brief this is a shorthand for dot(A, B)
*/
@@ -1507,7 +1530,7 @@ void GetScalingFactor(const T &d_max, T &mult)
}
template <typename T> MFEM_HOST_DEVICE
T calcsv(const tensor<T, 1, 1> A, const int i)
T calcsv(const tensor<T, 1, 1> A, [[maybe_unused]] const int i)
{
return A[0][0];
}
@@ -1822,12 +1845,20 @@ typename std::enable_if<(n > 3), tensor<T, n, n>>::type
* apply Gauss elimination directly on the dual number types
*
* TODO: compare performance of this hardcoded implementation to just using inv() directly
* TODO: restricted to rank-2 tensors, but could be generalized to higher rank tensors if needed
*
* believe it was broken, get_value(a) would have just worked on a single dual number. Now we extract it for a rank-2 tensor
*/
template <typename value_type, typename gradient_type, int n> MFEM_HOST_DEVICE
/* template <typename value_type, typename gradient_type, int n> MFEM_HOST_DEVICE
dual<value_type, gradient_type> inv(
tensor<dual<value_type, gradient_type>, n, n> A)
const tensor<dual<value_type, gradient_type>, n, n> &A)
{
auto invA = inv(get_value(A));
auto valueA = make_tensor<n, n>([&](int i, int j)
{
return A[i][j].value;
});
auto invA = inv(valueA);
return make_tensor<n, n>([&](int i, int j)
{
auto value = invA[i][j];
@@ -1841,7 +1872,7 @@ dual<value_type, gradient_type> inv(
}
return dual<value_type, gradient_type> {value, gradient};
});
}
} */
/**
* @brief recursively serialize the entries in a tensor to an output stream.
+386
View File
@@ -0,0 +1,386 @@
// 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::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>
#ifdef MFEM_USE_ENZYME
__attribute__((annotate("enzyme_inactive")))
#endif
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));
});
}
}
namespace detail
{
template <typename Fn, typename Tuple, std::size_t... Is>
inline constexpr decltype(auto) apply_impl(Fn&& f, Tuple&& t,
std::index_sequence<Is...>)
{
using std::get;
return std::forward<Fn>(f)(get<Is>(std::forward<Tuple>(t))...);
}
} // namespace detail
/// Extend std::apply to work with 0-size arrays and mfem::future::tuple.
template <typename Fn, typename Tuple>
inline constexpr decltype(auto) apply(Fn&& f, Tuple&& t)
{
constexpr auto size = std::tuple_size_v<std::remove_reference_t<Tuple>>;
if constexpr (size == 0)
{ return std::forward<Fn>(f)(); }
return detail::apply_impl(std::forward<Fn>(f), std::forward<Tuple>(t),
std::make_index_sequence<size> {});
}
/// 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:
using scalar_type = scalar_t;
using tensor_type = tensor<std::remove_cv_t<scalar_t>,tensor_sizes...>;
static constexpr auto tensor_dims = sizeof...(tensor_sizes);
static constexpr auto total_dims = ndims + tensor_dims;
static constexpr auto tensor_sizes_array() noexcept
{
return std::array<std::size_t, tensor_dims> {tensor_sizes...};
}
private:
scalar_t *data; /// Not owned
public:
std::array<std::size_t,ndims> dyn_sizes;
mutable 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.
MFEM_HOST_DEVICE std::size_t size(int k = 0) const { return dyn_sizes[k]; }
/// Returns the product of all sizes of the dynamic dimensions.
MFEM_HOST_DEVICE 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. */
MFEM_HOST_DEVICE
void set_layout(std::array<std::size_t,rank()+tensor_rank()> perm) const
{
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. */
MFEM_HOST_DEVICE
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. */
MFEM_HOST_DEVICE
std::size_t get_static_offset(
const std::array<std::size_t,tensor_rank()> &js) const
{
std::size_t static_offset = 0;
if constexpr (tensor_dims > 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. */
MFEM_HOST_DEVICE
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> MFEM_HOST_DEVICE
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. */
MFEM_HOST_DEVICE
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> MFEM_HOST_DEVICE
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. */
MFEM_HOST_DEVICE
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. */
MFEM_HOST_DEVICE
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. */
MFEM_HOST_DEVICE
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 For 0D (scalar) tensor arrays, allow direct binding as a
scalar reference so that @c array(i) can be passed to functions
expecting a @c scalar_t& in/out parameter. */
template <bool Enable = (sizeof...(tensor_sizes) == 0),
std::enable_if_t<Enable, int> = 0>
operator scalar_t &()
{
return *offset_data;
}
};
/** @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. */
MFEM_HOST_DEVICE
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> MFEM_HOST_DEVICE
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
#endif // MFEM_TENSOR_ARRAYS_HPP
+2 -1
View File
@@ -451,7 +451,8 @@ DIRS = general linalg linalg/batched linalg/simd mesh mesh/submesh fem \
fem/ceed/integrators/diffusion fem/ceed/integrators/nlconvection \
fem/ceed/interface fem/ceed/solvers fem/eltrans fem/fe fem/gslib \
fem/integ fem/lor fem/moonolith fem/qinterp fem/tmop fem/dfem \
fem/tmop/assemble fem/tmop/metrics fem/tmop/mult fem/tmop/tools
fem/dfem/backends fem/dfem/backends/local_qf fem/dfem/backends/global_qf\
fem/tmop/assemble fem/tmop/metrics fem/tmop/mult fem/tmop/tools
ifeq ($(MFEM_USE_MOONOLITH),YES)
MFEM_CXXFLAGS += $(MOONOLITH_CXX_FLAGS)
+1 -1
View File
@@ -317,7 +317,7 @@ protected:
void LoadSharedEntities(std::istream &input);
/// If the mesh is curved, make sure 'Nodes' is ParGridFunction.
/** Note that this method is not related to the public 'Mesh::EnsureNodes`.*/
/** Note that this method is not related to the public 'Mesh::EnsureNodes'.*/
void EnsureParNodes();
/// Internal function used in ParMesh::MakeRefined (and related constructor)
+18
View File
@@ -14,11 +14,29 @@ if (MFEM_USE_MPI)
MAIN dfem-minimal-surface.cpp
LIBRARIES mfem)
add_mfem_miniapp(dfem-hyperelasticity-energy
MAIN dfem-hyperelasticity_energy.cpp
LIBRARIES mfem)
add_mfem_miniapp(dfem-hyperelasticity-stress
MAIN dfem-hyperelasticity_stress.cpp
LIBRARIES mfem)
# Add parallel tests.
if (MFEM_ENABLE_TESTING)
add_test(NAME dfem-minimal-surface=${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:dfem-minimal-surface> -der 0 -o 1 -r 2 -no-vis
${MPIEXEC_POSTFLAGS})
add_test(NAME dfem-hyperelasticity-energy=${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:dfem-hyperelasticity-energy> -o 1 -rs 0 -no-vis
${MPIEXEC_POSTFLAGS})
add_test(NAME dfem-hyperelasticity-stress=${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:dfem-hyperelasticity-stress> -o 1 -rs 0 -no-vis
${MPIEXEC_POSTFLAGS})
endif()
endif()
@@ -0,0 +1,652 @@
// 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.
//
// --------------------------------------------
// Hyperelasticity with dFEM Second Derivatives
// --------------------------------------------
//
// Compile with: make dfem-hyperelasticity
//
// Sample runs: mpirun -np 4 dfem-hyperelasticity -o 1 -rs 0 -no-vis
// mpirun -np 4 dfem-hyperelasticity -mat linear-elastic -no-vis
// mpirun -np 4 dfem-hyperelasticity -mat mooney-rivlin -no-vis
// mpirun -np 4 dfem-hyperelasticity -mat holzapfel -no-vis
//
// Description: This miniapp solves a quasistatic solid mechanics problem on
// the 3D beam used by the Hooke miniapp. The material response is
// specified through a strain-energy density. The nonlinear
// residual is obtained with DifferentiableOperator::GetDerivative
// and Newton's Hessian-vector products are obtained with the new
// DifferentiableOperator::GetSecondDerivative functionality.
#include "mfem.hpp"
#include "../../fem/dfem/doperator.hpp"
#include "../../fem/dfem/backends/local_qf/prelude.hpp"
// Utils for output folder handling
#if __cplusplus >= 201703L
#include <filesystem>
namespace fs = std::filesystem;
#elif __cplusplus >= 201402L
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#error "C++14 or later is required for filesystem support."
#endif
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
#ifdef MFEM_USE_ENZYME
constexpr int dim = 3;
constexpr int Displacement = 0;
constexpr int Coords = 1;
constexpr int Energy = 2;
enum class MaterialType
{
NeoHookean,
LinearElastic,
MooneyRivlin,
Holzapfel
};
enum class PreconditionerType
{
None,
Diagonal
};
MaterialType ParseMaterial(const char *material)
{
const std::string name(material);
if (name == "neo-hookean" || name == "neohookean")
{
return MaterialType::NeoHookean;
}
if (name == "linear-elastic" || name == "linear")
{
return MaterialType::LinearElastic;
}
if (name == "mooney-rivlin" || name == "mooney" || name == "rivlin")
{
return MaterialType::MooneyRivlin;
}
if (name == "holzapfel" || name == "fiber" || name == "fiber-reinforced")
{
return MaterialType::Holzapfel;
}
MFEM_ABORT("Unknown material '" << name
<< "'. Available materials: neo-hookean, linear-elastic, "
<< "mooney-rivlin, holzapfel.");
return MaterialType::NeoHookean;
}
template <typename Material, typename dscalar_t>
struct HyperelasticEnergyQFunction
{
MFEM_HOST_DEVICE inline
auto operator()(const tensor<dscalar_t, dim, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
dscalar_t &energy) const
{
// The material supplies only psi, the strain-energy density at a point.
// The q-function adapter supplies the finite element measure: it maps
// reference gradients to physical gradients and multiplies psi by
// det(J) * w, i.e. the quadrature form of dx in Pi_int = int psi dx.
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto F = IdentityMatrix<dim>() + dudx;
energy = material().psi(F, dudx) * det(J) * w;
}
private:
MFEM_HOST_DEVICE inline
const Material& material() const
{
return static_cast<const Material&>(*this);
}
};
template <typename dscalar_t>
struct NeoHookeanEnergy :
HyperelasticEnergyQFunction<NeoHookeanEnergy<dscalar_t>, dscalar_t>
{
real_t D1 = 100.0;
real_t C1 = 50.0;
// Hooke miniapp Neo-Hookean model:
// Ψ(F) = D1 (J - 1)^2 + C1 (J^(-2/3) I1 - dim),
// J = det(F), I1 = tr(FᵀF).
// This gives σ = 2 D1 (J - 1) I + 2 C1 J^(-5/3) dev(F Fᵀ)
// consistent with the Hooke miniapp
MFEM_HOST_DEVICE inline
dscalar_t psi(const tensor<dscalar_t, dim, dim> &F,
const tensor<dscalar_t, dim, dim> & /* dudx */) const
{
const auto C = transpose(F) * F;
const auto J = det(F);
const auto I1_bar = pow(J, -2.0_r / 3.0_r) * tr(C);
return D1 * (J - 1.0_r) * (J - 1.0_r)
+ C1 * (I1_bar - real_t(dim));
}
};
template <typename dscalar_t>
struct LinearElasticEnergy :
HyperelasticEnergyQFunction<LinearElasticEnergy<dscalar_t>, dscalar_t>
{
real_t lambda = 100.0;
real_t mu = 50.0;
// Ψ(ε) = λ/2 tr(ε)^2 + μ ε:ε, ε = sym(∇u)
MFEM_HOST_DEVICE inline
dscalar_t psi(const tensor<dscalar_t, dim, dim> & /* F */,
const tensor<dscalar_t, dim, dim> &dudx) const
{
const auto strain = sym(dudx);
const auto tr_strain = tr(strain);
return 0.5_r * lambda * tr_strain * tr_strain
+ mu * ddot(strain, strain);
}
};
template <typename dscalar_t>
struct MooneyRivlinEnergy :
HyperelasticEnergyQFunction<MooneyRivlinEnergy<dscalar_t>, dscalar_t>
{
real_t c1 = 25.0;
real_t c2 = 25.0;
real_t kappa = 100.0;
// Ψ(F) = c₁(Ī₁ - dim) + c₂(Ī₂ - dim) + κ/2 log(J)^2,
// J = det F, Ī₁ = J^(-2/3) I₁, Ī₂ = J^(-4/3) I₂
MFEM_HOST_DEVICE inline
dscalar_t psi(const tensor<dscalar_t, dim, dim> &F,
const tensor<dscalar_t, dim, dim> & /* dudx */) const
{
const auto C = transpose(F) * F;
const auto J = det(F);
const auto Jm23 = pow(J, -2.0_r / 3.0_r);
const auto I1 = tr(C);
const auto I2 = 0.5_r * (I1 * I1 - tr(C * C));
const auto I1_bar = Jm23 * I1;
const auto I2_bar = Jm23 * Jm23 * I2;
const auto log_J = log(J);
return c1 * (I1_bar - real_t(dim))
+ c2 * (I2_bar - real_t(dim))
+ 0.5_r * kappa * log_J * log_J;
}
};
template <typename dscalar_t>
struct HolzapfelEnergy :
HyperelasticEnergyQFunction<HolzapfelEnergy<dscalar_t>, dscalar_t>
{
real_t c = 50.0;
real_t kappa = 100.0;
real_t k1 = 10.0;
real_t k2 = 20.0;
//tensor<real_t, dim> a0 = {1.0, 0.0, 0.0};
tensor<real_t, dim> a0 = {0.7071067811865475, 0.7071067811865475, 0.0}; // Normalized fiber direction in the x-y plane at 45 degrees a = (1, 1, 0) / sqrt(2)
tensor<real_t, dim, dim> A = make_tensor<dim, dim>(
[&](int i, int j) { return a0(i) * a0(j); });
// Holzapfel-type transversely reinforced model with one fiber family a0:
// Ψ(F) = c/2 (Ī₁ - dim) + κ/2 log(J)^2
// + k1/(2 k2) (exp(k2 (Ī₄ - 1)^2) - 1),
// where Ī₁ = J^(-2/3) tr(C), I₄ = A : C = a0.C.a0
// A = a0 ⊗ a0 is the fiber direction tensor.
// We use the full I4 invariant, to avoid auxetic behavior
//
// In this case we assume that the fiber direction is oriented 45 degrees in the x-y plane, i.e. a0 = (1, 1, 0)/sqrt(2).
// For a more general case one could start from an external fiber "field", and provide it as an input
// to the q-function, and then compute A = a0 ⊗ a0 at each quadrature point.
// This would require a different q-function signature including the fiber direction as well.
MFEM_HOST_DEVICE inline
dscalar_t psi(const tensor<dscalar_t, dim, dim> &F,
const tensor<dscalar_t, dim, dim> & /* dudx */) const
{
// Kinematic quantities
const auto C = transpose(F) * F;
const auto J = det(F);
const auto Jm23 = pow(J, -2.0_r / 3.0_r);
// Strain invariants
const auto I1_bar = Jm23 * tr(C);
const auto I4 = ddot(C, A);
const auto fiber_strain = I4 - 1.0_r;
const auto log_J = log(J);
// Strain energy density components
const auto psi_vol = 0.5_r * kappa * log_J * log_J;
const auto psi_iso = 0.5_r * c * (I1_bar - real_t(dim));
const auto psi_aniso = (k1 / (2.0_r * k2)) * (exp(k2 * fiber_strain *
fiber_strain) - 1.0_r);
// NOTE: in practice the anisotropic term should contribute only in tension, i.e. when I4 > 1, or fiber_strain > 0.
// mathematically this would introduce a non-smoothness in the energy functional that needs to taken care of.
return psi_vol + psi_iso + psi_aniso;
}
};
class HyperelasticOperator : public Operator
{
// Matrix-free Hessian-vector product used by Newton's method. This wraps the
// functional second-derivative interface and applies the same essential-dof
// treatment as the Hooke elasticity Jacobian operator.
// The wrapped DerivativeOperator returned by GetSecondDerivative computes
// the unconstrained second variation. This class adapts it to the Newton
// solve by zeroing constrained directions and restoring identity rows on
// essential true dofs.
class HessianOperator : public Operator
{
public:
HessianOperator(const HyperelasticOperator &oper, const Vector &state) :
Operator(oper.Height()),
oper(oper),
state(state),
z(oper.Height())
{
MultiVector X{state, oper.mesh_nodes_tdofs};
hessian = oper.internal_energy_dop->GetSecondDerivative(Displacement, X);
}
void Mult(const Vector &x, Vector &y) const override
{
// Essential directions are removed before applying the Hessian, then
// restored as identity rows so constrained dofs stay fixed in Newton.
z = x;
z.SetSubVector(oper.ess_tdofs, 0.0);
MultiVector Y{y};
hessian->Mult(z, Y);
auto d_y = y.ReadWrite();
const auto d_x = x.Read();
const auto d_dofs = oper.ess_tdofs.Read();
mfem::forall(oper.ess_tdofs.Size(), [=] MFEM_HOST_DEVICE (int i)
{
d_y[d_dofs[i]] = d_x[d_dofs[i]];
});
}
void AssembleDiagonal(Vector &diag) const override
{
hessian->AssembleDiagonal(diag);
auto d_diag = diag.ReadWrite();
const auto d_dofs = oper.ess_tdofs.Read();
mfem::forall(oper.ess_tdofs.Size(), [=] MFEM_HOST_DEVICE (int i)
{
d_diag[d_dofs[i]] = 1.0;
});
}
private:
const HyperelasticOperator &oper;
Vector state;
mutable Vector z;
std::shared_ptr<DerivativeOperator> hessian;
};
public:
// The nonlinear mechanics operator is defined from a scalar strain-energy
// density. Registering the q-function as a dFEM functional makes the first
// derivative available as the residual and the second derivative available
// as a matrix-free Hessian-vector product.
HyperelasticOperator(ParFiniteElementSpace &fes,
const IntegrationRule &ir,
MaterialType material) :
Operator(fes.GetTrueVSize()),
fes(fes),
ir(ir),
qspace(*fes.GetParMesh(), ir),
qspace_vec(qspace, 1),
q(qspace_vec),
material(material)
{
auto &mesh_nodes =
*static_cast<ParGridFunction *>(fes.GetParMesh()->GetNodes());
mesh_nodes_fes = mesh_nodes.ParFESpace();
mesh_nodes.GetTrueDofs(mesh_nodes_tdofs);
const std::vector<FieldDescriptor> inputs =
{
{Displacement, &fes},
{Coords, mesh_nodes_fes}
};
const std::vector<FieldDescriptor> outputs =
{
{Energy, &qspace_vec}
};
internal_energy_dop = std::make_shared<DifferentiableOperator>(
inputs, outputs, *fes.GetParMesh());
Array<int> all_domain_attr;
if (fes.GetMesh()->attributes.Size() > 0)
{
all_domain_attr.SetSize(fes.GetMesh()->attributes.Max());
all_domain_attr = 1;
}
auto derivatives = std::integer_sequence<size_t, Displacement> {};
switch (material)
{
case MaterialType::NeoHookean:
{
// Default finite-strain material. The functional registration tells
// dFEM to build the first and second variations from this energy.
// NOTE: the registration for functional will likely change in the future.
NeoHookeanEnergy<dscalar_t> energy;
internal_energy_dop->AddDomainIntegrator<LocalQFBackend, true>(
energy,
Inputs<Gradient<Displacement>, Gradient<Coords>, Weight> {},
Outputs<Identity<Energy>> {},
ir, all_domain_attr, derivatives);
break;
}
case MaterialType::LinearElastic:
{
// Linear elastic material, also expressed as an energy so it uses
// exactly the same GetDerivative/GetSecondDerivative machinery.
LinearElasticEnergy<dscalar_t> energy;
internal_energy_dop->AddDomainIntegrator<LocalQFBackend, true>(
energy,
Inputs<Gradient<Displacement>, Gradient<Coords>, Weight> {},
Outputs<Identity<Energy>> {},
ir, all_domain_attr, derivatives);
break;
}
case MaterialType::MooneyRivlin:
{
// Compressible Mooney-Rivlin material with an isochoric invariant
// split and logarithmic volumetric penalty.
MooneyRivlinEnergy<dscalar_t> energy;
internal_energy_dop->AddDomainIntegrator<LocalQFBackend, true>(
energy,
Inputs<Gradient<Displacement>, Gradient<Coords>, Weight> {},
Outputs<Identity<Energy>> {},
ir, all_domain_attr, derivatives);
break;
}
case MaterialType::Holzapfel:
{
// Fiber-reinforced Holzapfel-type material with fibers aligned to
// the beam axis. The functional registration lets dFEM derive both
// the residual and the Hessian-vector product from the energy.
HolzapfelEnergy<dscalar_t> energy;
internal_energy_dop->AddDomainIntegrator<LocalQFBackend, true>(
energy,
Inputs<Gradient<Displacement>, Gradient<Coords>, Weight> {},
Outputs<Identity<Energy>> {},
ir, all_domain_attr, derivatives);
break;
}
}
// The first variation of a functional is exposed as a stateless
// derivative operator: it does not capture a linearization state here.
// Instead, the current displacement is passed to gradient->Mult(X, Y),
// so this wrapper can be cached and reused for every residual evaluation.
gradient = internal_energy_dop->GetDerivative(Displacement);
}
void SetEssentialAttributes(const Array<int> &ess_bdr)
{
fes.GetEssentialTrueDofs(ess_bdr, ess_tdofs);
}
void SetPrescribedDisplacementAttributes(const Array<int> &disp_bdr)
{
fes.GetEssentialTrueDofs(disp_bdr, prescribed_tdofs);
}
const Array<int>& GetPrescribedDisplacementTDofs() const
{
return prescribed_tdofs;
}
void Mult(const Vector &x, Vector &y) const override
{
// Residual R(u) = dE/du. For functional integrators, GetDerivative
// returns the gradient action directly, including the pointwise reverse
// seed for the summed energy.
MultiVector X{x, mesh_nodes_tdofs};
MultiVector Y{y};
gradient->Mult(X, Y);
y.SetSubVector(ess_tdofs, 0.0);
}
Operator& GetGradient(const Vector &x) const override
{
// Newton asks for the gradient of the nonlinear residual. Since the
// residual is the energy gradient, this is the Hessian of the energy.
hessian = std::make_shared<HessianOperator>(*this, x);
return *hessian;
}
private:
ParFiniteElementSpace &fes;
ParFiniteElementSpace *mesh_nodes_fes = nullptr;
const IntegrationRule &ir;
QuadratureSpace qspace;
VectorQuadratureSpace qspace_vec;
QuadratureFunction q;
MaterialType material;
Vector mesh_nodes_tdofs;
Array<int> ess_tdofs;
Array<int> prescribed_tdofs;
// Variational notation used by this mechanics operator:
// internal_energy_dop: Pi_int(u) = int_Omega psi(u) dx, where psi is the strain-energy density
// gradient: first variation, d Pi_int / du
// hessian: second variation at the current Newton state
std::shared_ptr<DifferentiableOperator> internal_energy_dop;
std::shared_ptr<DerivativeOperator> gradient;
mutable std::shared_ptr<HessianOperator> hessian;
};
#endif // MFEM_USE_ENZYME
int main(int argc, char *argv[])
{
Mpi::Init(argc, argv);
const int num_procs = Mpi::WorldSize();
const int myid = Mpi::WorldRank();
Hypre::Init();
#ifndef MFEM_USE_ENZYME
if (Mpi::Root())
{
mfem::out << "This miniapp requires MFEM_USE_ENZYME=YES because it uses "
<< "dFEM functional second derivatives.\n";
}
return 0;
#else
int order = 1;
const char *device_config = "cpu";
int serial_refinement_levels = 0;
real_t cg_tol = 1e-1;
const char *material_name = "neo-hookean";
int prec_type = static_cast<int>(PreconditionerType::None);
bool visualization = true;
bool paraview = false;
int visport = 19916;
const char *outfolder = "./Output";
OptionsParser args(argc, argv);
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree).");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&serial_refinement_levels, "-rs", "--ref-serial",
"Number of uniform refinements on the serial mesh.");
args.AddOption(&material_name, "-mat", "--material",
"Material: neo-hookean, linear-elastic, mooney-rivlin, or holzapfel.");
args.AddOption(&prec_type, "-pc", "--preconditioner",
"Preconditioner: 0=none, 1=diagonal.");
args.AddOption(&cg_tol, "-tol", "--cg-tol",
"Relative tolerance for the CG solver.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&paraview, "-pv", "--paraview", "-no-pv",
"--no-paraview",
"Enable or disable ParaView DataCollection output.");
args.AddOption(&visport, "-p", "--send-port", "Socket for GLVis.");
args.AddOption(&outfolder, "-of", "--output-folder",
"Output folder for ParaView DataCollection files.");
args.ParseCheck();
const MaterialType material = ParseMaterial(material_name);
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
Mesh mesh =
Mesh::MakeCartesian3D(8, 2, 2, Element::HEXAHEDRON, 8.0, 1.0, 1.0);
if (mesh.Dimension() != dim)
{
MFEM_ABORT("This example only works in 3D.");
}
mesh.EnsureNodes();
for (int l = 0; l < serial_refinement_levels; l++)
{
mesh.UniformRefinement();
}
ParMesh pmesh(MPI_COMM_WORLD, mesh);
mesh.Clear();
pmesh.EnsureNodes();
H1_FECollection fec(order, dim);
ParFiniteElementSpace fes(&pmesh, &fec, dim, Ordering::byNODES);
// Problem size
const int size = fes.GlobalTrueVSize();
if (Mpi::Root())
{
mfem::out << "#dofs: " << size << std::endl;
}
const IntegrationRule &ir =
IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * order + 1);
HyperelasticOperator elasticity_op(fes, ir, material);
if (pmesh.bdr_attributes.Size())
{
Array<int> ess_attr(pmesh.bdr_attributes.Max());
ess_attr = 0;
ess_attr[4] = 1;
ess_attr[2] = 1;
elasticity_op.SetEssentialAttributes(ess_attr);
Array<int> displaced_attr(pmesh.bdr_attributes.Max());
displaced_attr = 0;
displaced_attr[2] = 1;
elasticity_op.SetPrescribedDisplacementAttributes(displaced_attr);
}
ParGridFunction U_gf(&fes);
U_gf = 0.0;
Vector U;
U_gf.GetTrueDofs(U);
U.SetSubVector(elasticity_op.GetPrescribedDisplacementTDofs(), 1.0e-2);
CGSolver cg(MPI_COMM_WORLD);
cg.SetRelTol(cg_tol);
cg.SetMaxIter(10000);
cg.SetPrintLevel(2);
std::unique_ptr<Solver> pc;
switch (static_cast<PreconditionerType>(prec_type))
{
case PreconditionerType::None:
break;
case PreconditionerType::Diagonal:
pc = std::make_unique<OperatorJacobiSmoother>();
cg.SetPreconditioner(*pc);
break;
default:
MFEM_ABORT("Unknown preconditioner type: " << prec_type);
}
NewtonSolver newton(MPI_COMM_WORLD);
newton.SetSolver(cg);
newton.SetOperator(elasticity_op);
#ifdef MFEM_USE_SINGLE
newton.SetRelTol(1e-4);
#elif defined MFEM_USE_DOUBLE
newton.SetRelTol(1e-6);
#else
MFEM_ABORT("Floating point type undefined");
#endif
newton.SetMaxIter(10);
newton.SetPrintLevel(1);
Vector zero;
newton.Mult(zero, U);
U_gf.Distribute(U);
if (visualization)
{
char vishost[] = "localhost";
socketstream sol_sock(vishost, visport);
sol_sock << "parallel " << num_procs << " " << myid << "\n";
sol_sock.precision(8);
sol_sock << "solution\n" << pmesh << U_gf << std::flush;
}
if (paraview)
{
if (Mpi::Root())
{
fs::create_directories(outfolder);
}
// Create a ParaView data collection
ParaViewDataCollection pd("dfem-hyperelasticity", &pmesh);
pd.SetPrefixPath(outfolder);
pd.RegisterField("displacement", &U_gf);
pd.SetDataFormat(VTKFormat::BINARY);
if (order > 1)
{
pd.SetHighOrderOutput(true);
pd.SetLevelsOfDetail(order);
}
pd.SetCycle(0);
pd.SetTime(0.0);
pd.Save();
}
return 0;
#endif // MFEM_USE_ENZYME
}
@@ -0,0 +1,460 @@
// 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.
//
// --------------------------------------------
// Hyperelasticity with dFEM Stress Residuals
// ------------------------------------------
//
// Compile with: make dfem-hyperelasticity-stress
//
// Sample runs: mpirun -np 4 dfem-hyperelasticity-stress -o 1 -rs 0 -no-vis
//
// Description: This miniapp solves a quasistatic solid mechanics problem on
// the 3D beam used by the Hooke miniapp. The material response is
// specified through a stress-based formulation, by providing the
// first Piola-Kirchhoff stress P(F).
// The jacobian is obtained with DifferentiableOperator::GetDerivative
// and provided to Newton.
#include "mfem.hpp"
#include "../../fem/dfem/doperator.hpp"
#include "../../fem/dfem/backends/local_qf/prelude.hpp"
// Utils for output folder handling
#if __cplusplus >= 201703L
#include <filesystem>
namespace fs = std::filesystem;
#elif __cplusplus >= 201402L
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#error "C++14 or later is required for filesystem support."
#endif
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
#ifdef MFEM_USE_ENZYME
constexpr int dim = 3;
constexpr int Displacement = 0;
constexpr int Coords = 1;
enum class MaterialType
{
NeoHookean
};
enum class PreconditionerType
{
None,
Diagonal
};
MaterialType ParseMaterial(const char *material)
{
const std::string name(material);
if (name == "neo-hookean" || name == "neohookean")
{
return MaterialType::NeoHookean;
}
MFEM_ABORT("Unknown material '" << name
<< "'. Available materials: neo-hookean");
return MaterialType::NeoHookean;
}
template <typename Material, typename dscalar_t>
struct HyperelasticStressQFunction
{
MFEM_HOST_DEVICE inline
void operator()(const tensor<dscalar_t, dim, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
tensor<dscalar_t, dim, dim> &dvdxi) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto F = IdentityMatrix<dim>() + dudx;
const auto P = material().PK1(F, dudx);
dvdxi = P * transpose(invJ) * det(J) * w;
}
private:
MFEM_HOST_DEVICE inline
const Material& material() const
{
return static_cast<const Material&>(*this);
}
};
template <typename dscalar_t>
struct NeoHookeanStress :
HyperelasticStressQFunction<NeoHookeanStress<dscalar_t>, dscalar_t>
{
real_t D1 = 100.0;
real_t C1 = 50.0;
// Hooke miniapp Neo-Hookean model in first Piola form:
// Ψ(F) = D1 (J - 1)^2 + C1 (J^(-2/3) I1 - dim),
// J = det(F), I1 = tr(FᵀF).
// P(F) = ∂Ψ/∂F = J σ F^{-T}, where
// σ = 2 D1 (J - 1) I + 2 C1 J^(-5/3) dev(F Fᵀ),
// the same stress formulation used in the Hooke miniapp.
MFEM_HOST_DEVICE inline
auto PK1(const tensor<dscalar_t, dim, dim> &F,
const tensor<dscalar_t, dim, dim> & /* dudx */) const
{
const auto J = det(F);
const auto FinvT = inv(transpose(F));
const auto I1 = tr(transpose(F) * F);
return 2.0_r * D1 * J * (J - 1.0_r) * FinvT
+ 2.0_r * C1 * pow(J, -2.0_r / 3.0_r)
* (F - (I1 / 3.0_r) * FinvT);
}
};
class HyperelasticOperator : public Operator
{
// Matrix-free Jacobian-vector product used by Newton's method. This wraps the
// nonlinear residual operator and requests the first variation with respect to the displacement field (Jacobian = dR/dU).
class JacobianOperator : public Operator
{
public:
JacobianOperator(const HyperelasticOperator &oper, const Vector &state) :
Operator(oper.Height()),
oper(oper),
state(state),
z(oper.Height())
{
MultiVector X{state, oper.mesh_nodes_tdofs};
jacobian = oper.residual_dop->GetDerivative(Displacement, X);
}
void Mult(const Vector &x, Vector &y) const override
{
// Essential directions are removed before applying the Jacobian, then
// restored as identity rows so constrained dofs stay fixed in Newton.
z = x;
z.SetSubVector(oper.ess_tdofs, 0.0);
MultiVector Y{y};
jacobian->Mult(z, Y);
auto d_y = y.ReadWrite();
const auto d_x = x.Read();
const auto d_dofs = oper.ess_tdofs.Read();
mfem::forall(oper.ess_tdofs.Size(), [=] MFEM_HOST_DEVICE (int i)
{
d_y[d_dofs[i]] = d_x[d_dofs[i]];
});
}
void AssembleDiagonal(Vector &diag) const override
{
jacobian->AssembleDiagonal(diag);
auto d_diag = diag.ReadWrite();
const auto d_dofs = oper.ess_tdofs.Read();
mfem::forall(oper.ess_tdofs.Size(), [=] MFEM_HOST_DEVICE (int i)
{
d_diag[d_dofs[i]] = 1.0;
});
}
private:
const HyperelasticOperator &oper;
Vector state;
mutable Vector z;
std::shared_ptr<DerivativeOperator> jacobian;
};
public:
// The nonlinear mechanics operator is defined from the first
// Piola-Kirchhoff stress P(F), which defines the nonlinear residual R(u).
// DifferentiableOperator::GetDerivative provides the first variation of the
// residual with respect to the displacement field, i.e. the Jacobian dR/dU.
HyperelasticOperator(ParFiniteElementSpace &fes,
const IntegrationRule &ir,
MaterialType material) :
Operator(fes.GetTrueVSize()),
fes(fes),
ir(ir),
material(material)
{
auto &mesh_nodes =
*static_cast<ParGridFunction *>(fes.GetParMesh()->GetNodes());
mesh_nodes_fes = mesh_nodes.ParFESpace();
mesh_nodes.GetTrueDofs(mesh_nodes_tdofs);
const std::vector<FieldDescriptor> inputs =
{
{Displacement, &fes},
{Coords, mesh_nodes_fes}
};
const std::vector<FieldDescriptor> outputs =
{
{Displacement, &fes}
};
residual_dop = std::make_shared<DifferentiableOperator>(
inputs, outputs, *fes.GetParMesh());
Array<int> all_domain_attr;
if (fes.GetMesh()->attributes.Size() > 0)
{
all_domain_attr.SetSize(fes.GetMesh()->attributes.Max());
all_domain_attr = 1;
}
auto derivatives = std::integer_sequence<size_t, Displacement> {};
switch (material)
{
case MaterialType::NeoHookean:
{
// Default finite-strain material. This is registered as a standard dFEM integrator, not a functional.
// The first variation will represent the Jacobian-vector product for the nonlinear residual.
NeoHookeanStress<dscalar_t> stress;
residual_dop->AddDomainIntegrator<LocalQFBackend>(
stress,
Inputs<Gradient<Displacement>, Gradient<Coords>, Weight> {},
Outputs<Gradient<Displacement>> {},
ir, all_domain_attr, derivatives);
break;
}
}
}
void SetEssentialAttributes(const Array<int> &ess_bdr)
{
fes.GetEssentialTrueDofs(ess_bdr, ess_tdofs);
}
void SetPrescribedDisplacementAttributes(const Array<int> &disp_bdr)
{
fes.GetEssentialTrueDofs(disp_bdr, prescribed_tdofs);
}
const Array<int>& GetPrescribedDisplacementTDofs() const
{
return prescribed_tdofs;
}
void Mult(const Vector &x, Vector &y) const override
{
// Stress formulation residual R(u), assembled directly from P(F).
MultiVector X{x, mesh_nodes_tdofs};
MultiVector Y{y};
residual_dop->Mult(X, Y);
y.SetSubVector(ess_tdofs, 0.0);
}
Operator& GetGradient(const Vector &x) const override
{
// Newton asks for the gradient of the nonlinear residual: dR/dU.
jacobian = std::make_shared<JacobianOperator>(*this, x);
return *jacobian;
}
private:
ParFiniteElementSpace &fes;
ParFiniteElementSpace *mesh_nodes_fes = nullptr;
const IntegrationRule &ir;
MaterialType material;
Vector mesh_nodes_tdofs;
Array<int> ess_tdofs;
Array<int> prescribed_tdofs;
// Variational notation used by this mechanics operator:
// residual_dop: R(u) = P(F(u)) : dF/du
// jacobian first variation of the residual: dR/du = dP/dF : dF/du
std::shared_ptr<DifferentiableOperator> residual_dop;
mutable std::shared_ptr<JacobianOperator> jacobian;
};
#endif // MFEM_USE_ENZYME
int main(int argc, char *argv[])
{
Mpi::Init(argc, argv);
const int num_procs = Mpi::WorldSize();
const int myid = Mpi::WorldRank();
Hypre::Init();
#ifndef MFEM_USE_ENZYME
if (Mpi::Root())
{
mfem::out << "This miniapp requires MFEM_USE_ENZYME=YES because it uses "
<< "dFEM automatic differentiation.\n";
}
return 0;
#else
int order = 1;
const char *device_config = "cpu";
int serial_refinement_levels = 0;
const char *material_name = "neo-hookean";
int prec_type = static_cast<int>(PreconditionerType::None);
bool visualization = true;
bool paraview = false;
int visport = 19916;
const char *outfolder = "./Output";
OptionsParser args(argc, argv);
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree).");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&serial_refinement_levels, "-rs", "--ref-serial",
"Number of uniform refinements on the serial mesh.");
args.AddOption(&material_name, "-mat", "--material",
"Material: neo-hookean.");
args.AddOption(&prec_type, "-pc", "--preconditioner",
"Preconditioner: 0=none, 1=diagonal.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&paraview, "-pv", "--paraview", "-no-pv",
"--no-paraview",
"Enable or disable ParaView DataCollection output.");
args.AddOption(&visport, "-p", "--send-port", "Socket for GLVis.");
args.AddOption(&outfolder, "-of", "--output-folder",
"Output folder for ParaView DataCollection files.");
args.ParseCheck();
const MaterialType material = ParseMaterial(material_name);
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
Mesh mesh =
Mesh::MakeCartesian3D(8, 2, 2, Element::HEXAHEDRON, 8.0, 1.0, 1.0);
if (mesh.Dimension() != dim)
{
MFEM_ABORT("This example only works in 3D.");
}
mesh.EnsureNodes();
for (int l = 0; l < serial_refinement_levels; l++)
{
mesh.UniformRefinement();
}
ParMesh pmesh(MPI_COMM_WORLD, mesh);
mesh.Clear();
pmesh.EnsureNodes();
H1_FECollection fec(order, dim);
ParFiniteElementSpace fes(&pmesh, &fec, dim, Ordering::byNODES);
const IntegrationRule &ir =
IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * order + 1);
HyperelasticOperator elasticity_op(fes, ir, material);
if (pmesh.bdr_attributes.Size())
{
Array<int> ess_attr(pmesh.bdr_attributes.Max());
ess_attr = 0;
ess_attr[4] = 1;
ess_attr[2] = 1;
elasticity_op.SetEssentialAttributes(ess_attr);
Array<int> displaced_attr(pmesh.bdr_attributes.Max());
displaced_attr = 0;
displaced_attr[2] = 1;
elasticity_op.SetPrescribedDisplacementAttributes(displaced_attr);
}
ParGridFunction U_gf(&fes);
U_gf = 0.0;
Vector U;
U_gf.GetTrueDofs(U);
U.SetSubVector(elasticity_op.GetPrescribedDisplacementTDofs(), 1.0e-2);
CGSolver cg(MPI_COMM_WORLD);
cg.SetRelTol(1e-1);
cg.SetMaxIter(10000);
cg.SetPrintLevel(2);
std::unique_ptr<Solver> pc;
switch (static_cast<PreconditionerType>(prec_type))
{
case PreconditionerType::None:
break;
case PreconditionerType::Diagonal:
pc = std::make_unique<OperatorJacobiSmoother>();
cg.SetPreconditioner(*pc);
break;
default:
MFEM_ABORT("Unknown preconditioner type: " << prec_type);
}
NewtonSolver newton(MPI_COMM_WORLD);
newton.SetSolver(cg);
newton.SetOperator(elasticity_op);
#ifdef MFEM_USE_SINGLE
newton.SetRelTol(1e-4);
#elif defined MFEM_USE_DOUBLE
newton.SetRelTol(1e-6);
#else
MFEM_ABORT("Floating point type undefined");
#endif
newton.SetMaxIter(10);
newton.SetPrintLevel(1);
Vector zero;
newton.Mult(zero, U);
U_gf.Distribute(U);
if (visualization)
{
char vishost[] = "localhost";
socketstream sol_sock(vishost, visport);
sol_sock << "parallel " << num_procs << " " << myid << "\n";
sol_sock.precision(8);
sol_sock << "solution\n" << pmesh << U_gf << std::flush;
}
if (paraview)
{
if (Mpi::Root())
{
fs::create_directories(outfolder);
}
// Create a ParaView data collection
ParaViewDataCollection pd("dfem-hyperelasticity", &pmesh);
pd.SetPrefixPath(outfolder);
pd.RegisterField("displacement", &U_gf);
pd.SetDataFormat(VTKFormat::BINARY);
if (order > 1)
{
pd.SetHighOrderOutput(true);
pd.SetLevelsOfDetail(order);
}
pd.SetCycle(0);
pd.SetTime(0.0);
pd.Save();
}
return 0;
#endif // MFEM_USE_ENZYME
}
+51 -52
View File
@@ -23,10 +23,8 @@
// mpirun -np 4 dfem-minimal-surface -der 2
//
// Device sample runs:
// mpirun -np 4 dfem-minimal-surface -der 0 -r 1 -o 2 -d cuda
// mpirun -np 4 dfem-minimal-surface -der 1 -r 1 -o 2 -d cuda
// * mpirun -np 4 dfem-minimal-surface -der 0 -r 1 -o 2 -d hip
// * mpirun -np 4 dfem-minimal-surface -der 1 -r 1 -o 2 -d hip
// mpirun -np 4 dfem-minimal-surface -der 0 -r 1 -o 2 -d gpu
// mpirun -np 4 dfem-minimal-surface -der 1 -r 1 -o 2 -d gpu
//
// Description: This example code demonstrates the use of MFEM to solve the
// minimal surface problem in 2D:
@@ -48,6 +46,8 @@
// visualization.
#include "mfem.hpp"
#include "../../fem/dfem/doperator.hpp"
#include "../../fem/dfem/backends/local_qf/prelude.hpp"
using namespace mfem;
@@ -116,11 +116,12 @@ public:
auto operator()(
const tensor<dscalar_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w) const
const real_t &w,
tensor<dscalar_t, dim> &dvdx) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
return tuple{coeff(dudx) * dudx * transpose(invJ) * det(J) * w};
dvdx = coeff(dudx) * dudx * transpose(invJ) * det(J) * w;
}
};
@@ -135,7 +136,8 @@ public:
const tensor<real_t, dim> &ddelta_udxi,
const tensor<real_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w) const
const real_t &w,
tensor<real_t, dim> &dvdx) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
@@ -145,7 +147,7 @@ public:
const auto term1 = c * ddelta_udx;
const auto term2 = c * c * c * dot(dudx, ddelta_udx) * dudx;
return tuple{(term1 - term2) * transpose(invJ) * det(J) * w};
dvdx = (term1 - term2) * transpose(invJ) * det(J) * w;
}
};
@@ -162,14 +164,9 @@ public:
z(minsurface->Height())
{
minsurface->u.SetFromTrueDofs(x);
auto mesh_nodes = static_cast<ParGridFunction*>
(minsurface->H1.GetParMesh()->GetNodes());
// One can retrieve the derivative of a DifferentiableOperator wrt a
// field variable if the derivative has been requested during the
// DifferentiableOperator::AddDomainIntegrator call.
dres_du = minsurface->res->GetDerivative(
SOLUTION_U, {&minsurface->u}, {mesh_nodes});
MultiVector X{x, minsurface->mesh_nodes_tdofs};
dres_du = minsurface->res->GetDerivative(SOLUTION_U, X);
}
void Mult(const Vector &x, Vector &y) const override
@@ -177,7 +174,8 @@ public:
z = x;
z.SetSubVector(minsurface->ess_tdofs, 0.0);
dres_du->Mult(z, y);
MultiVector Y{y};
dres_du->Mult(z, Y);
auto d_y = y.ReadWrite();
const auto d_x = x.Read();
@@ -216,22 +214,20 @@ public:
Array<int> all_domain_attr(minsurface->H1.GetMesh()->attributes.Max());
all_domain_attr = 1;
auto &mesh_nodes = *static_cast<ParGridFunction *>
(minsurface->H1.GetParMesh()->GetNodes());
auto &mesh_nodes_fes = *mesh_nodes.ParFESpace();
std::vector<FieldDescriptor> solutions =
{
{DIRECTION_U, &minsurface->H1}
};
std::vector<FieldDescriptor> parameters =
std::vector<FieldDescriptor> inputs =
{
{DIRECTION_U, &minsurface->H1},
{SOLUTION_U, &minsurface->H1},
{MESH_NODES, &mesh_nodes_fes}
{MESH_NODES, minsurface->mesh_nodes_fes}
};
std::vector<FieldDescriptor> outputs =
{
{SOLUTION_U, &minsurface->H1}
};
dres_du = std::make_shared<DifferentiableOperator>(
solutions, parameters, *minsurface->H1.GetParMesh());
inputs, outputs, *minsurface->H1.GetParMesh());
auto input_operators = tuple
{
@@ -247,12 +243,13 @@ public:
};
ManualDerivativeApply manual_derivative_apply;
dres_du->AddDomainIntegrator(manual_derivative_apply, input_operators,
output_operators, minsurface->ir,
all_domain_attr);
dres_du->AddDomainIntegrator<LocalQFBackend>(manual_derivative_apply,
input_operators,
output_operators, minsurface->ir,
all_domain_attr);
minsurface->u.SetFromTrueDofs(x);
dres_du->SetParameters({&minsurface->u, &mesh_nodes});
x0.SetSize(x.Size());
x0 = x;
}
void Mult(const Vector &x, Vector &y) const override
@@ -260,7 +257,9 @@ public:
z = x;
z.SetSubVector(minsurface->ess_tdofs, 0.0);
dres_du->Mult(z, y);
MultiVector X{z, x0, minsurface->mesh_nodes_tdofs};
MultiVector Y{y};
dres_du->Mult(X, Y);
auto d_y = y.HostReadWrite();
const auto d_x = x.HostRead();
@@ -272,10 +271,10 @@ public:
const MinimalSurface *minsurface = nullptr;
std::shared_ptr<DifferentiableOperator> dres_du;
Vector x0;
mutable Vector z;
};
public:
MinimalSurface(ParFiniteElementSpace &H1,
const IntegrationRule &ir,
@@ -291,7 +290,8 @@ public:
auto &mesh_nodes =
*static_cast<ParGridFunction *>(H1.GetParMesh()->GetNodes());
auto &mesh_nodes_fes = *mesh_nodes.ParFESpace();
mesh_nodes_fes = mesh_nodes.ParFESpace();
mesh_nodes.GetTrueDofs(mesh_nodes_tdofs);
// The following section is the heart of this example. It shows how to
// create and interact with the DifferentialOperator class.
@@ -299,14 +299,16 @@ public:
// The constructor of DifferentiableOperator takes two vectors of
// FieldDescriptors. A FieldDescriptor can be viewed as a a pair of an
// identifier (the field ID) and it's accompanying space.
std::vector<FieldDescriptor> solutions;
solutions.push_back(FieldDescriptor(SOLUTION_U, &H1));
std::vector<FieldDescriptor> parameters;
parameters.push_back(FieldDescriptor(MESH_NODES, &mesh_nodes_fes));
std::vector<FieldDescriptor> inputs;
inputs.emplace_back(SOLUTION_U, &H1);
inputs.emplace_back(MESH_NODES, mesh_nodes_fes);
std::vector<FieldDescriptor> outputs;
outputs.emplace_back(SOLUTION_U, &H1);
// Create the DifferentiableOperator on the desired mesh.
res = std::make_shared<DifferentiableOperator>(
solutions, parameters, *H1.GetParMesh());
inputs, outputs, *H1.GetParMesh());
// DifferentiableOperator::AddIntegrator consists mainly of multiple
// components. The input and output operators and the pointwise
@@ -353,16 +355,9 @@ public:
// formed integrator should be formed. This is necessary to specify at
// compile time in order to instantiate the correct functions.
auto derivatives = std::integer_sequence<size_t, SOLUTION_U> {};
res->AddDomainIntegrator(mf_apply_qf, input_operators, output_operators,
ir, all_domain_attr, derivatives);
// Before we are able to use DifferentiableOperator::Mult, we need to call
// DifferentiableOperator::SetParameters to set the parameters of the
// operator. Here, only the mesh node function is required. We do this
// here once, because we know that the nodes won't change. If they do,
// we'd have to call SetParameters before each call to Mult. This is done
// to be mathematically consistent with fixing paramaters.
res->SetParameters({&mesh_nodes});
res->AddDomainIntegrator<LocalQFBackend>(
mf_apply_qf, input_operators, output_operators,
ir, all_domain_attr, derivatives);
Array<int> ess_bdr(H1.GetParMesh()->bdr_attributes.Max());
ess_bdr = 1;
@@ -371,7 +366,9 @@ public:
void Mult(const Vector &x, Vector &y) const override
{
res->Mult(x, y);
MultiVector X{x, mesh_nodes_tdofs};
MultiVector Y{y};
res->Mult(X, Y);
y.SetSubVector(ess_tdofs, 0.0);
}
@@ -408,10 +405,11 @@ public:
}
private:
ParFiniteElementSpace &H1;
ParFiniteElementSpace &H1, *mesh_nodes_fes = nullptr;
const IntegrationRule &ir;
mutable ParGridFunction u;
Vector mesh_nodes_tdofs;
Array<int> ess_tdofs;
@@ -556,6 +554,7 @@ int main(int argc, char *argv[])
#else
// When Enzyme is not available, use the dual type for automatic
// differentiation
mfem::out << "Enzyme not available, using dual type for automatic differentiation." << std::endl;
using mfem::future::dual;
using dual_t = dual<real_t, real_t>;
minsurface = std::make_unique<MinimalSurface<dual_t>>(H1, *ir,
@@ -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
+18 -15
View File
@@ -35,12 +35,13 @@ template <int DIM> struct QFunction
MFEM_HOST_DEVICE inline auto operator()(const matd_t &dudxi,
const real_t &L, const real_t &M,
const matd_t &J,
const real_t &w) const
const real_t &w,
matd_t &rtn) const
{
const matd_t JxW = transpose(inv(J)) * det(J) * w;
constexpr auto I = mfem::future::IsotropicIdentity<DIM>();
const auto eps = mfem::future::sym(dudxi * mfem::future::inv(J));
return tuple{(L * tr(eps) * I + 2.0 * M * eps) * JxW};
rtn = (L * tr(eps) * I + 2.0 * M * eps) * JxW;
}
};
};
@@ -374,13 +375,17 @@ void IsoLinElasticSolver::Assemble()
#ifdef MFEM_USE_DOUBLE
// define the differentiable operator
dop = std::make_unique<mfem::future::DifferentiableOperator>(
std::vector<mfem::future::FieldDescriptor> {{ U, vfes }},
std::vector<mfem::future::FieldDescriptor>
std::vector<mfem::future::FieldDescriptor>
{
{ U, vfes },
{ LCoeff, &Lambda_ps},
{ MuCoeff, &Mu_ps},
{ Coords, mfes }
},
std::vector<mfem::future::FieldDescriptor>
{
{ U, vfes }
},
*pmesh);
// sample lambda on the integration points
@@ -389,25 +394,23 @@ void IsoLinElasticSolver::Assemble()
// sample mu on the integration points
Mu_cv = std::make_unique<CoefficientVector>(*mu, qs);
// set the parameters of the differentiable operator
dop->SetParameters({ Lambda_cv.get(), Mu_cv.get(), nodes });
// define the q-function for dimensions 2 and 3
const auto inputs =
mfem::future::tuple{ Gradient<U>{},
Identity<LCoeff>{}, Identity<MuCoeff>{},
Gradient<Coords>{},
Weight{} };
const auto output = mfem::future::tuple{ Gradient<U>{} };
auto inputs =
mfem::future::Inputs< Gradient<U>,
Identity<LCoeff>, Identity<MuCoeff>,
Gradient<Coords>,
Weight> {};
auto output = mfem::future::Outputs< Gradient<U>> {};
using Backend = mfem::future::LocalQFBackend;
if (2 == spaceDim)
{
typename QFunction<2>::Elasticity e2qf;
dop->AddDomainIntegrator(e2qf, inputs, output, ir, domain_attributes);
dop->AddDomainIntegrator<Backend>(e2qf, inputs, output, ir, domain_attributes);
}
else if (3 == spaceDim)
{
typename QFunction<3>::Elasticity e3qf;
dop->AddDomainIntegrator(e3qf, inputs, output, ir, domain_attributes);
dop->AddDomainIntegrator<Backend>(e3qf, inputs, output, ir, domain_attributes);
}
else { MFEM_ABORT("Space dimension not supported"); }
#else
+7
View File
@@ -14,6 +14,9 @@
#include "mfem.hpp"
#include "../../fem/dfem/doperator.hpp"
#include "../../fem/dfem/parameterspace.hpp"
using real_t = mfem::real_t;
///////////////////////////////////////////////////////////////////////////////
@@ -202,6 +205,10 @@ public:
{
dtq.nqpt = ir.GetNPoints();
}
const Operator* GetB() const override { return nullptr; }
const Operator* GetBt() const override { return nullptr; }
};
// creates a list with essential dofs
+2 -1
View File
@@ -32,7 +32,7 @@ function(add_benchmark name)
endif(MFEM_USE_CUDA)
add_executable(bench_${name} ${${NAME}_BENCH_SRCS})
target_link_libraries(bench_${name} mfem pthread)
target_link_libraries(bench_${name} PRIVATE mfem pthread)
add_dependencies(${MFEM_ALL_BENCHMARKS_TARGET_NAME} bench_${name})
add_test(NAME bench_${name}_cpu
@@ -51,6 +51,7 @@ endfunction(add_benchmark)
#-------------------------------------------------------------------------------
add_benchmark(assembly_levels)
add_benchmark(ceed)
add_benchmark(dfem)
add_benchmark(dg_amr)
add_benchmark(elasticity)
add_benchmark(tmop)
File diff suppressed because it is too large Load Diff
+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)
+175 -168
View File
@@ -37,8 +37,15 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
set(UNIT_TESTS_SRCS
dfem/test_diffusion.cpp
dfem/test_divergence.cpp
dfem/test_hyperelasticity.cpp
dfem/test_functional.cpp
dfem/test_jvp_vjp.cpp
dfem/test_lvector_interface.cpp
dfem/test_mass.cpp
dfem/test_multiple_inputs.cpp
dfem/test_multiple_outputs.cpp
dfem/test_second_derivative.cpp
dfem/test_tmop.cpp
general/test_array.cpp
general/test_scan.cpp
general/test_arrays_by_name.cpp
@@ -179,11 +186,11 @@ set(UNIT_TESTS_SRCS
# SERIAL CPU TESTS: unit_tests
#-----------------------------------------------------------
if (MFEM_USE_CUDA)
set_property(SOURCE unit_test_main.cpp ${UNIT_TESTS_SRCS}
set_property(SOURCE unit_test_main.cpp ${UNIT_TESTS_SRCS}
PROPERTY LANGUAGE CUDA)
endif()
if (MFEM_USE_HIP)
set_property(SOURCE unit_test_main.cpp ${UNIT_TESTS_SRCS}
set_property(SOURCE unit_test_main.cpp ${UNIT_TESTS_SRCS}
PROPERTY HIP_SOURCE_PROPERTY_FORMAT TRUE)
endif()
@@ -207,7 +214,7 @@ add_dependencies(unit_tests copy_miniapps_nurbs_data)
# make unit_tests
# ctest -R unit_tests [-V]
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME unit_tests COMMAND unit_tests)
add_test(NAME unit_tests COMMAND unit_tests)
endif()
#-----------------------------------------------------------
@@ -215,15 +222,15 @@ endif()
#-----------------------------------------------------------
# Create CUDA executable and test
if (MFEM_USE_CUDA)
# gpu_unit_tests
set(GPU_UNIT_TESTS_SRCS gpu_unit_test_main.cpp)
set_property(SOURCE ${GPU_UNIT_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
mfem_add_executable(gpu_unit_tests ${GPU_UNIT_TESTS_SRCS})
target_link_libraries(gpu_unit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} gpu_unit_tests)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME gpu_unit_tests COMMAND gpu_unit_tests)
endif()
# gpu_unit_tests
set(GPU_UNIT_TESTS_SRCS gpu_unit_test_main.cpp)
set_property(SOURCE ${GPU_UNIT_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
mfem_add_executable(gpu_unit_tests ${GPU_UNIT_TESTS_SRCS})
target_link_libraries(gpu_unit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} gpu_unit_tests)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME gpu_unit_tests COMMAND gpu_unit_tests)
endif()
endif()
#-----------------------------------------------------------
@@ -231,14 +238,14 @@ endif()
#-----------------------------------------------------------
# Create HIP 'gpu_unit_tests' executable and test
if (MFEM_USE_HIP)
# gpu_unit_tests
set(GPU_UNIT_TESTS_SRCS gpu_unit_test_main.cpp)
mfem_add_executable(gpu_unit_tests ${GPU_UNIT_TESTS_SRCS})
target_link_libraries(gpu_unit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} gpu_unit_tests)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME gpu_unit_tests COMMAND gpu_unit_tests)
endif()
# gpu_unit_tests
set(GPU_UNIT_TESTS_SRCS gpu_unit_test_main.cpp)
mfem_add_executable(gpu_unit_tests ${GPU_UNIT_TESTS_SRCS})
target_link_libraries(gpu_unit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} gpu_unit_tests)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME gpu_unit_tests COMMAND gpu_unit_tests)
endif()
endif()
#-----------------------------------------------------------
@@ -250,61 +257,61 @@ endif()
# All device unit tests are built into a separate executable, in order to be
# able to change the device.
function(add_serial_miniapp_test name test_uvm)
string(TOUPPER ${name} NAME)
string(TOUPPER ${name} NAME)
set(${NAME}_TESTS_SRCS miniapps/test_${name}.cpp)
if (MFEM_USE_CUDA)
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)
endif(MFEM_USE_HIP)
set(${NAME}_TESTS_SRCS miniapps/test_${name}.cpp)
if (MFEM_USE_CUDA)
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)
endif(MFEM_USE_HIP)
mfem_add_executable(${name}_tests_cpu ${${NAME}_TESTS_SRCS})
add_dependencies(${name}_tests_cpu copy_miniapps_meshing_data)
add_dependencies(${name}_tests_cpu ${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME})
target_compile_definitions(${name}_tests_cpu PUBLIC MFEM_${NAME}_DEVICE="cpu")
target_link_libraries(${name}_tests_cpu mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} ${name}_tests_cpu)
mfem_add_executable(${name}_tests_cpu ${${NAME}_TESTS_SRCS})
add_dependencies(${name}_tests_cpu copy_miniapps_meshing_data)
add_dependencies(${name}_tests_cpu ${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME})
target_compile_definitions(${name}_tests_cpu PUBLIC MFEM_${NAME}_DEVICE="cpu")
target_link_libraries(${name}_tests_cpu mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} ${name}_tests_cpu)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${name}_tests_cpu COMMAND ${name}_tests_cpu)
endif()
mfem_add_executable(${name}_tests_debug ${${NAME}_TESTS_SRCS})
add_dependencies(${name}_tests_debug copy_miniapps_meshing_data)
add_dependencies(${name}_tests_debug ${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME})
target_compile_definitions(${name}_tests_debug PUBLIC MFEM_${NAME}_DEVICE="debug")
target_link_libraries(${name}_tests_debug mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} ${name}_tests_debug)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${name}_tests_debug COMMAND ${name}_tests_debug)
endif()
if (MFEM_USE_CUDA OR MFEM_USE_HIP)
mfem_add_executable(${name}_tests_gpu ${${NAME}_TESTS_SRCS})
add_dependencies(${name}_tests_gpu copy_miniapps_meshing_data)
add_dependencies(${name}_tests_gpu ${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME})
target_compile_definitions(${name}_tests_gpu PUBLIC MFEM_${NAME}_DEVICE="gpu")
target_link_libraries(${name}_tests_gpu mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} ${name}_tests_gpu)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${name}_tests_cpu COMMAND ${name}_tests_cpu)
add_test(NAME ${name}_tests_gpu COMMAND ${name}_tests_gpu)
endif()
mfem_add_executable(${name}_tests_debug ${${NAME}_TESTS_SRCS})
add_dependencies(${name}_tests_debug copy_miniapps_meshing_data)
add_dependencies(${name}_tests_debug ${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME})
target_compile_definitions(${name}_tests_debug PUBLIC MFEM_${NAME}_DEVICE="debug")
target_link_libraries(${name}_tests_debug mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} ${name}_tests_debug)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${name}_tests_debug COMMAND ${name}_tests_debug)
endif()
if (MFEM_USE_CUDA OR MFEM_USE_HIP)
mfem_add_executable(${name}_tests_gpu ${${NAME}_TESTS_SRCS})
add_dependencies(${name}_tests_gpu copy_miniapps_meshing_data)
add_dependencies(${name}_tests_gpu ${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME})
target_compile_definitions(${name}_tests_gpu PUBLIC MFEM_${NAME}_DEVICE="gpu")
target_link_libraries(${name}_tests_gpu mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} ${name}_tests_gpu)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${name}_tests_gpu COMMAND ${name}_tests_gpu)
endif()
if (test_uvm)
mfem_add_executable(${name}_tests_gpu_uvm ${${NAME}_TESTS_SRCS})
add_dependencies(${name}_tests_gpu_uvm copy_miniapps_meshing_data)
add_dependencies(${name}_tests_gpu_uvm ${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME})
target_compile_definitions(${name}_tests_gpu_uvm PUBLIC
if (test_uvm)
mfem_add_executable(${name}_tests_gpu_uvm ${${NAME}_TESTS_SRCS})
add_dependencies(${name}_tests_gpu_uvm copy_miniapps_meshing_data)
add_dependencies(${name}_tests_gpu_uvm ${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME})
target_compile_definitions(${name}_tests_gpu_uvm PUBLIC
MFEM_${NAME}_DEVICE="gpu:uvm")
target_link_libraries(${name}_tests_gpu_uvm mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME}
target_link_libraries(${name}_tests_gpu_uvm mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME}
${name}_tests_gpu_uvm)
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${name}_tests_gpu_uvm COMMAND ${name}_tests_gpu_uvm)
endif()
endif()
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${name}_tests_gpu_uvm COMMAND ${name}_tests_gpu_uvm)
endif()
endif()
endif()
endfunction(add_serial_miniapp_test)
add_serial_miniapp_test(sedov ON) # UVM ON
@@ -317,25 +324,25 @@ add_serial_miniapp_test(tmop_pa OFF) # UVM OFF
#-----------------------------------------------------------
# Add 'ceed_tests' executable and test; add extra tests 'ceed_test_*'
if (MFEM_USE_CEED)
set(CEED_TESTS_SRCS
set(CEED_TESTS_SRCS
ceed/test_ceed.cpp
ceed/test_ceed_main.cpp)
if (MFEM_USE_CUDA)
set_property(SOURCE ${CEED_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
endif(MFEM_USE_CUDA)
mfem_add_executable(ceed_tests ${CEED_TESTS_SRCS})
target_link_libraries(ceed_tests mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} ceed_tests)
# Add CEED tests
add_test(NAME ceed_tests COMMAND ceed_tests)
if (MFEM_USE_CUDA)
add_test(NAME ceed_tests_cuda_ref
if (MFEM_USE_CUDA)
set_property(SOURCE ${CEED_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
endif(MFEM_USE_CUDA)
mfem_add_executable(ceed_tests ${CEED_TESTS_SRCS})
target_link_libraries(ceed_tests mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} ceed_tests)
# Add CEED tests
add_test(NAME ceed_tests COMMAND ceed_tests)
if (MFEM_USE_CUDA)
add_test(NAME ceed_tests_cuda_ref
COMMAND ceed_tests --device ceed-cuda:/gpu/cuda/ref)
add_test(NAME ceed_tests_cuda_shared
add_test(NAME ceed_tests_cuda_shared
COMMAND ceed_tests --device ceed-cuda:/gpu/cuda/shared)
add_test(NAME ceed_tests_cuda_gen
add_test(NAME ceed_tests_cuda_gen
COMMAND ceed_tests --device ceed-cuda:/gpu/cuda/gen)
endif()
endif()
endif()
#-----------------------------------------------------------
@@ -343,52 +350,52 @@ endif()
#-----------------------------------------------------------
# Define executables and tests
if (MFEM_USE_MPI)
# punit_tests
if (MFEM_USE_CUDA)
set_property(SOURCE punit_test_main.cpp PROPERTY LANGUAGE CUDA)
endif()
mfem_add_executable(punit_tests punit_test_main.cpp)
target_link_libraries(punit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} punit_tests)
foreach(np 1 ${MFEM_MPI_NP})
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME punit_tests_np=${np}
# punit_tests
if (MFEM_USE_CUDA)
set_property(SOURCE punit_test_main.cpp PROPERTY LANGUAGE CUDA)
endif()
mfem_add_executable(punit_tests punit_test_main.cpp)
target_link_libraries(punit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} punit_tests)
foreach(np 1 ${MFEM_MPI_NP})
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME punit_tests_np=${np}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${np}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:punit_tests>
${MPIEXEC_POSTFLAGS})
endif()
endif()
endforeach()
if (MFEM_USE_CUDA)
# pgpu_unit_tests
set(PGPU_UNIT_TESTS_SRCS pgpu_unit_test_main.cpp)
set_property(SOURCE ${PGPU_UNIT_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
mfem_add_executable(pgpu_unit_tests ${PGPU_UNIT_TESTS_SRCS})
target_link_libraries(pgpu_unit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} pgpu_unit_tests)
foreach(np 1 ${MFEM_MPI_NP})
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME pgpu_unit_tests_np=${np}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${np}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:pgpu_unit_tests>
${MPIEXEC_POSTFLAGS})
endif()
endforeach()
if (MFEM_USE_CUDA)
# pgpu_unit_tests
set(PGPU_UNIT_TESTS_SRCS pgpu_unit_test_main.cpp)
set_property(SOURCE ${PGPU_UNIT_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
mfem_add_executable(pgpu_unit_tests ${PGPU_UNIT_TESTS_SRCS})
target_link_libraries(pgpu_unit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} pgpu_unit_tests)
foreach(np 1 ${MFEM_MPI_NP})
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME pgpu_unit_tests_np=${np}
endif()
if (MFEM_USE_HIP)
# pgpu_unit_tests
set(PGPU_UNIT_TESTS_SRCS pgpu_unit_test_main.cpp)
mfem_add_executable(pgpu_unit_tests ${PGPU_UNIT_TESTS_SRCS})
target_link_libraries(pgpu_unit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} pgpu_unit_tests)
foreach(np 1 ${MFEM_MPI_NP})
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME pgpu_unit_tests_np=${np}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${np}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:pgpu_unit_tests>
${MPIEXEC_POSTFLAGS})
endif()
endforeach()
endif()
if (MFEM_USE_HIP)
# pgpu_unit_tests
set(PGPU_UNIT_TESTS_SRCS pgpu_unit_test_main.cpp)
mfem_add_executable(pgpu_unit_tests ${PGPU_UNIT_TESTS_SRCS})
target_link_libraries(pgpu_unit_tests unit_tests_srcs)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} pgpu_unit_tests)
foreach(np 1 ${MFEM_MPI_NP})
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME pgpu_unit_tests_np=${np}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${np}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:pgpu_unit_tests>
${MPIEXEC_POSTFLAGS})
endif()
endforeach()
endif()
endif()
endforeach()
endif()
endif(MFEM_USE_MPI)
#-----------------------------------------------------------
@@ -398,72 +405,72 @@ endif(MFEM_USE_MPI)
#-----------------------------------------------------------
# Function to add one MPI executable for a test.
function(add_mpi_executable_test name dev)
string(TOUPPER ${name} NAME)
string(REPLACE "_" ":" DEV ${dev})
mfem_add_executable(p${name}_tests_${dev} ${PAR_${NAME}_TESTS_SRCS})
target_compile_definitions(p${name}_tests_${dev} PUBLIC MFEM_${NAME}_MPI=1)
target_compile_definitions(p${name}_tests_${dev} PUBLIC MFEM_${NAME}_DEVICE="${DEV}")
target_link_libraries(p${name}_tests_${dev} mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} p${name}_tests_${dev})
string(TOUPPER ${name} NAME)
string(REPLACE "_" ":" DEV ${dev})
mfem_add_executable(p${name}_tests_${dev} ${PAR_${NAME}_TESTS_SRCS})
target_compile_definitions(p${name}_tests_${dev} PUBLIC MFEM_${NAME}_MPI=1)
target_compile_definitions(p${name}_tests_${dev} PUBLIC MFEM_${NAME}_DEVICE="${DEV}")
target_link_libraries(p${name}_tests_${dev} mfem)
add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} p${name}_tests_${dev})
endfunction(add_mpi_executable_test)
# Function to add one test from the tests/unit/miniapp directory.
function(add_parallel_miniapp_test name HYPRE_MM)
string(TOUPPER ${name} NAME)
string(TOUPPER ${name} NAME)
function(add_mpi_unit_test DEV NP)
set(test_name p${name}_tests_${DEV})
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${test_name}_np=${NP}
function(add_mpi_unit_test DEV NP)
set(test_name p${name}_tests_${DEV})
if (MFEM_USE_DOUBLE) # otherwise returns MFEM_SKIP_RETURN_VALUE
add_test(NAME ${test_name}_np=${NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:${test_name}>
${MPIEXEC_POSTFLAGS})
endif()
endfunction()
set(PAR_${NAME}_TESTS_SRCS miniapps/test_${name}.cpp)
if (MFEM_USE_CUDA)
set_property(SOURCE ${PAR_${NAME}_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
endif()
endfunction()
set(backends cpu)
if (HYPRE_MM)
# psedov_tests_debug_* will return MFEM_SKIP_RETURN_VALUE when all of
# the following conditions are met:
# * MFEM_SEDOV_MPI is defined (true here)
# * MFEM_DEBUG is defined
# * MFEM_SEDOV_DEVICE is "debug" (the case added here)
# * HypreUsingGPU() is true; this is the same as: HYPRE_USING_GPU is
# defined and MFEM_HYPRE_VERSION < 23100 (if the version is >= 23100,
# the code will switch to HYPRE running on CPU).
# We check these conditions here to skip the "debug" backend and avoid
# the ctest failure.
if (NOT ((${name} STREQUAL "sedov") AND MFEM_DEBUG AND
set(PAR_${NAME}_TESTS_SRCS miniapps/test_${name}.cpp)
if (MFEM_USE_CUDA)
set_property(SOURCE ${PAR_${NAME}_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
endif()
set(backends cpu)
if (HYPRE_MM)
# psedov_tests_debug_* will return MFEM_SKIP_RETURN_VALUE when all of
# the following conditions are met:
# * MFEM_SEDOV_MPI is defined (true here)
# * MFEM_DEBUG is defined
# * MFEM_SEDOV_DEVICE is "debug" (the case added here)
# * HypreUsingGPU() is true; this is the same as: HYPRE_USING_GPU is
# defined and MFEM_HYPRE_VERSION < 23100 (if the version is >= 23100,
# the code will switch to HYPRE running on CPU).
# We check these conditions here to skip the "debug" backend and avoid
# the ctest failure.
if (NOT ((${name} STREQUAL "sedov") AND MFEM_DEBUG AND
(HYPRE_USING_CUDA OR HYPRE_USING_HIP) AND
(${MFEM_HYPRE_VERSION} LESS "23100")))
list(APPEND backends debug)
endif()
list(APPEND backends debug)
endif()
if (MFEM_USE_CUDA OR MFEM_USE_HIP)
list(APPEND backends gpu)
if (HYPRE_MM)
list(APPEND backends gpu_uvm)
endif()
endif()
if (MFEM_USE_CUDA OR MFEM_USE_HIP)
list(APPEND backends gpu)
if (HYPRE_MM)
list(APPEND backends gpu_uvm)
endif()
endif()
set(MPI_NPS 1 ${MFEM_MPI_NP})
foreach(dev ${backends})
add_mpi_executable_test(${name} ${dev})
foreach(np ${MPI_NPS})
add_mpi_unit_test(${dev} ${np})
endforeach()
set(MPI_NPS 1 ${MFEM_MPI_NP})
foreach(dev ${backends})
add_mpi_executable_test(${name} ${dev})
foreach(np ${MPI_NPS})
add_mpi_unit_test(${dev} ${np})
endforeach()
endforeach()
endfunction(add_parallel_miniapp_test)
# Additional MPI unit tests
if (MFEM_USE_MPI)
add_parallel_miniapp_test(sedov TRUE)
add_parallel_miniapp_test(tmop_pa FALSE)
add_parallel_miniapp_test(sedov TRUE)
add_parallel_miniapp_test(tmop_pa FALSE)
endif(MFEM_USE_MPI)
#-----------------------------------------------------------
@@ -472,10 +479,10 @@ endif(MFEM_USE_MPI)
#-----------------------------------------------------------
set(DEBUG_DEVICE_SRCS miniapps/test_debug_device.cpp)
if (MFEM_USE_CUDA)
set_property(SOURCE ${DEBUG_DEVICE_SRCS} PROPERTY LANGUAGE CUDA)
set_property(SOURCE ${DEBUG_DEVICE_SRCS} PROPERTY LANGUAGE CUDA)
endif()
if (MFEM_USE_HIP)
set_property(SOURCE ${DEBUG_DEVICE_SRCS}
set_property(SOURCE ${DEBUG_DEVICE_SRCS}
PROPERTY HIP_SOURCE_PROPERTY_FORMAT TRUE)
endif()
mfem_add_executable(debug_device_tests ${DEBUG_DEVICE_SRCS})
+412 -227
View File
@@ -10,65 +10,92 @@
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "../linalg/test_same_matrices.hpp"
#include "mfem.hpp"
#include <utility>
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../linalg/test_same_matrices.hpp"
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using mfem::future::dual;
using dscalar_t = dual<real_t, real_t>;
#endif
using DOperator = DifferentiableOperator;
template <int DIM> struct Diffusion
// ────────────────────────────────────────────────────────────────────────────
template <int DIM>
struct Diffusion
{
using dvecd_t = tensor<dscalar_t, DIM>;
using matd_t = tensor<real_t, DIM, DIM>;
struct MFApply
{
MFEM_HOST_DEVICE inline auto operator()(const dvecd_t &dudxi,
const real_t &rho,
const matd_t &J,
const real_t &w) const
MFEM_HOST_DEVICE inline auto operator()(
const dvecd_t &dudxi,
const matd_t &J,
const real_t &w,
dvecd_t &dvdxi) const
{
const auto invJ = inv(J), TinJ = transpose(invJ);
return tuple{ (dudxi * invJ) * TinJ * det(J) * w * rho };
const auto invJ = inv(J);
const auto invJt = transpose(invJ);
dvdxi = (dudxi * invJ) * invJt * det(J) * w;
}
};
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
MFEM_HOST_DEVICE inline auto operator()(
const matd_t &J,
const real_t &w,
matd_t &qdata) const
{
return tuple{ inv(J) * transpose(inv(J)) * det(J) * w * rho };
qdata = inv(J) * transpose(inv(J)) * det(J) * w;
}
};
struct PAApply
{
MFEM_HOST_DEVICE inline auto operator()(const dvecd_t &dudxi,
const matd_t &q) const
MFEM_HOST_DEVICE inline auto operator()(
const dvecd_t &dudxi,
const matd_t &qdata,
dvecd_t &dvdxi) const
{
return tuple{ q * dudxi };
dvdxi = qdata * dudxi;
};
};
};
template <int DIM>
// ────────────────────────────────────────────────────────────────────────────
template <int DIM> struct VectorDiffusion
{
using dmatd_t = tensor<dscalar_t, DIM, DIM>;
using matd_t = tensor<real_t, DIM, DIM>;
struct MFApply
{
MFEM_HOST_DEVICE inline auto operator()(
const dmatd_t &dudxi,
const matd_t &J,
const real_t &w,
dmatd_t &dvdxi) const
{
const auto invJ = inv(J);
const auto invJt = transpose(invJ);
dvdxi = (dudxi * invJ) * invJt * det(J) * w;
}
};
};
// ────────────────────────────────────────────────────────────────────────────
template <int DIM, typename QFBackend = LocalQFBackend>
void diffusion(const char *filename, int p)
{
CAPTURE(filename, DIM, p);
@@ -79,9 +106,10 @@ void diffusion(const char *filename, int p)
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
@@ -89,248 +117,405 @@ void diffusion(const char *filename, int p)
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace pfes(&pmesh, &fec);
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
const int NE = pfes.GetNE(), d1d(p + 1), q = 2 * p;
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");
H1_FECollection fec(p, DIM);
ParGridFunction x(&pfes), y(&pfes), z(&pfes);
Vector X(pfes.GetTrueVSize()), Y(pfes.GetTrueVSize()), Z(pfes.GetTrueVSize());
static constexpr int U = 0, Coords = 1;
X.Randomize(1);
x.SetFromTrueDofs(X);
auto rho = [](const Vector &xyz)
SECTION("Scalar")
{
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);
ParFiniteElementSpace pfes(&pmesh, &fec);
ParBilinearForm blf_fa(&pfes);
blf_fa.AddDomainIntegrator(new DiffusionIntegrator(rho_coeff, ir));
blf_fa.SetAssemblyLevel(AssemblyLevel::FULL);
blf_fa.Assemble();
blf_fa.Finalize();
ParGridFunction x(&pfes), y(&pfes), z(&pfes);
Vector xtvec(pfes.GetTrueVSize()), ytvec(pfes.GetTrueVSize()),
ztvec(pfes.GetTrueVSize());
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, "");
xtvec.Randomize(1);
x.SetFromTrueDofs(xtvec);
UniformParameterSpace rho_ps(pmesh, *ir, 1);
ParBilinearForm blf_fa(&pfes);
blf_fa.AddDomainIntegrator(new DiffusionIntegrator(ir));
blf_fa.SetAssemblyLevel(AssemblyLevel::FULL);
blf_fa.Assemble();
blf_fa.Finalize();
static constexpr int U = 0, Coords = 1, Rho = 3;
const auto sol = std::vector{ FieldDescriptor{ U, &pfes } };
const auto in_fds = std::vector
{
FieldDescriptor{ U, &pfes },
FieldDescriptor{ Coords, mfes }
};
const auto out_fds = std::vector{ FieldDescriptor{ U, &pfes } };
SECTION("action")
{
DOperator dop_mf(sol, {{Rho, &rho_ps}, {Coords, mfes}}, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
dop_mf.SetParameters({ &rho_coeff_cv, nodes });
SECTION("Scalar Action")
{
DifferentiableOperator dop_mf(in_fds, out_fds, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
dop_mf.AddDomainIntegrator<QFBackend>(
mf_apply_qf,
Inputs<Gradient<U>, Gradient<Coords>, Weight> {},
Outputs<Gradient<U>> {},
*ir, all_domain_attr);
pfes.GetRestrictionMatrix()->Mult(x, X);
dop_mf.Mult(X, Z);
Vector nodestv;
nodes->GetTrueDofs(nodestv);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
pfes.GetRestrictionMatrix()->Mult(x, xtvec);
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());
MultiVector X{xtvec, nodestv};
MultiVector Z{ztvec};
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
dop_mf.Mult(X, Z);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, ytvec);
ytvec -= ztvec;
real_t norm_global = 0.0;
real_t norm_local = ytvec.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("Scalar Action Partial Assembly")
{
static constexpr int QData = 2;
QuadratureSpace qspace(pmesh, *ir);
VectorQuadratureSpace qspace_vec(qspace, DIM * DIM);
QuadratureFunction qd(qspace_vec);
DifferentiableOperator setupPAData(
{
{Coords, mfes}
},
{
{QData, &qspace_vec}
}, pmesh);
typename Diffusion<DIM>::PASetup pa_setup_qf;
setupPAData.AddDomainIntegrator<QFBackend>(
pa_setup_qf,
Inputs<Gradient<Coords>, Weight> {},
Outputs<Identity<QData>> {},
*ir, all_domain_attr);
{
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{nodestv};
MultiVector Y{qd};
setupPAData.Mult(X, Y);
}
DifferentiableOperator applyPAData(
{
{U, &pfes}, {QData, &qspace_vec}
},
{
{U, &pfes}
}, pmesh);
typename Diffusion<DIM>::PAApply pa_apply_qf;
applyPAData.AddDomainIntegrator<QFBackend>(
pa_apply_qf,
Inputs<Gradient<U>, Identity<QData>> {},
Outputs<Gradient<U>> {},
*ir, all_domain_attr);
{
pfes.GetRestrictionMatrix()->Mult(x, xtvec);
MultiVector X{xtvec, qd};
MultiVector Z{ztvec};
applyPAData.Mult(X, Z);
}
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, ytvec);
ytvec -= ztvec;
real_t norm_global = 0.0;
real_t norm_local = ytvec.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("Scalar Action Linearized")
{
DifferentiableOperator dop_mf(in_fds, out_fds, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
dop_mf.AddDomainIntegrator<QFBackend>(
mf_apply_qf,
Inputs<Gradient<U>, Gradient<Coords>, Weight> {},
Outputs<Gradient<U>> {},
*ir, all_domain_attr,
Derivatives<U> {});
pfes.GetRestrictionMatrix()->Mult(x, xtvec);
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{xtvec, nodestv};
MultiVector Z{ztvec};
auto ddop = dop_mf.GetDerivative(U, X);
// Randomize again s.t. the PA setup like cache can't
// trivially succeed by caching one direction only.
xtvec.Randomize(567);
x.SetFromTrueDofs(xtvec);
Vector dztvec(ztvec.Size());
MultiVector DZ{dztvec};
ddop->Mult(X[0], DZ);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, ytvec);
ytvec -= dztvec;
real_t norm_global = 0.0;
real_t norm_local = ytvec.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("Scalar SparseMatrix")
{
DifferentiableOperator dop_mf(in_fds, out_fds, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
dop_mf.AddDomainIntegrator<QFBackend>(
mf_apply_qf,
Inputs<Gradient<U>, Gradient<Coords>, Weight> {},
Outputs<Gradient<U>> {},
*ir, all_domain_attr,
Derivatives<U> {});
pfes.GetRestrictionMatrix()->Mult(x, xtvec);
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{xtvec, nodestv};
auto dRdU = dop_mf.GetDerivative(U, X);
SparseMatrix *A = nullptr;
dRdU->Assemble(A);
TestSameMatrices(*A, blf_fa.SpMat());
delete A;
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("Scalar Assemble Diagonal")
{
DifferentiableOperator dop_mf(in_fds, out_fds, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
dop_mf.AddDomainIntegrator<QFBackend>(
mf_apply_qf,
Inputs<Gradient<U>, Gradient<Coords>, Weight> {},
Outputs<Gradient<U>> {},
*ir, all_domain_attr,
Derivatives<U> {});
pfes.GetRestrictionMatrix()->Mult(x, xtvec);
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{xtvec, nodestv};
auto dRdU = dop_mf.GetDerivative(U, X);
Vector dfem_diagonal(pfes.GetTrueVSize());
dRdU->AssembleDiagonal(dfem_diagonal);
Vector mfem_diagonal(pfes.GetTrueVSize());
blf_fa.AssembleDiagonal(mfem_diagonal);
dfem_diagonal -= mfem_diagonal;
real_t norm_global = 0.0;
real_t norm_local = dfem_diagonal.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
SECTION("action partial assembly")
{
static constexpr int QData = 2;
UniformParameterSpace qd_ps(pmesh, *ir, DIM * DIM);
ParameterFunction qdata(qd_ps);
qdata.UseDevice(true);
DOperator dSetup(sol, {{Rho, &rho_ps}, {Coords, mfes}, {QData, &qd_ps}}, pmesh);
typename Diffusion<DIM>::PASetup pa_setup_qf;
dSetup.AddDomainIntegrator(
pa_setup_qf,
tuple{ Value<U>{}, Identity<Rho>{}, Gradient<Coords>{}, Weight{} },
tuple{ Identity<QData>{} }, *ir, all_domain_attr);
dSetup.SetParameters({ &rho_coeff_cv, nodes, &qdata });
pfes.GetRestrictionMatrix()->Mult(x, X);
dSetup.Mult(X, qdata);
DOperator dop_pa(sol, { { QData, &qd_ps } }, pmesh);
typename Diffusion<DIM>::PAApply pa_apply_qf;
dop_pa.AddDomainIntegrator(pa_apply_qf,
tuple{ Gradient<U>{}, Identity<QData>{} },
tuple{ Gradient<U>{} },
*ir, all_domain_attr);
dop_pa.SetParameters({ &qdata });
pfes.GetRestrictionMatrix()->Mult(x, X);
dop_pa.Mult(X, Z);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
real_t norm_global = 0.0;
real_t norm_local = Y.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("action linearized")
{
DOperator dop_mf(sol, {{Rho, &rho_ps}, {Coords, mfes}}, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
auto derivatives = std::integer_sequence<size_t, U> {};
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr, derivatives);
dop_mf.SetParameters({ &rho_coeff_cv, nodes });
auto dRdU = dop_mf.GetDerivative(U, {&x}, {&rho_coeff_cv, nodes});
pfes.GetRestrictionMatrix()->Mult(x, X);
dRdU->Mult(X, Z);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
real_t norm_global = 0.0;
real_t norm_local = Y.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("action vector")
SECTION("Vector")
{
ParFiniteElementSpace vpfes(&pmesh, &fec, DIM);
ParGridFunction vx(&vpfes), vy(&vpfes);
Vector vX(vpfes.GetTrueVSize()), vY(vpfes.GetTrueVSize()),
vZ(vpfes.GetTrueVSize());
vX.Randomize(1);
vx.SetFromTrueDofs(vX);
SECTION("Vector Diffusion Action")
{
const auto vsol = std::vector{ FieldDescriptor{ U, &vpfes } };
DOperator dop_mf(vsol, {{Coords, mfes}}, pmesh);
const auto mf_vector_diffusion_qf =
[] MFEM_HOST_DEVICE (const tensor<dscalar_t, DIM, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
vX.Randomize(1);
vx.SetFromTrueDofs(vX);
DifferentiableOperator dop_mf(
{
const auto invJ = inv(J), TinJ = transpose(invJ);
return tuple{ (dudxi * invJ) * TinJ * det(J) * w };
};
dop_mf.AddDomainIntegrator(mf_vector_diffusion_qf,
tuple{ Gradient<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} },
*ir, all_domain_attr);
dop_mf.SetParameters({ nodes });
vpfes.GetRestrictionMatrix()->Mult(vx, vX), dop_mf.Mult(vX, vZ);
}
{
ConstantCoefficient one(1.0);
{U, &vpfes},
{Coords, mfes},
},
{
{U, &vpfes}
}, pmesh);
typename VectorDiffusion<DIM>::MFApply mf_vector_diffusion_qf;
dop_mf.AddDomainIntegrator<QFBackend>(
mf_vector_diffusion_qf,
Inputs<Gradient<U>, Gradient<Coords>, Weight> {},
Outputs<Gradient<U>> {},
*ir, all_domain_attr);
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{vX, nodestv};
MultiVector Z{vZ};
dop_mf.Mult(X, Z);
ParBilinearForm vblf_fa(&vpfes);
vblf_fa.AddDomainIntegrator(new VectorDiffusionIntegrator(one, ir));
vblf_fa.AddDomainIntegrator(new VectorDiffusionIntegrator(ir));
vblf_fa.SetAssemblyLevel(AssemblyLevel::LEGACYFULL);
vblf_fa.Assemble();
vblf_fa.Finalize();
vblf_fa.Mult(vx, vy);
vpfes.GetProlongationMatrix()->MultTranspose(vy, vY);
}
vY -= vZ;
real_t norm_global = 0.0, norm_local = vY.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
// Account for ill conditioning of the RT mesh
if (std::string(filename).compare("../../data/rt-2d-q3.mesh") == 0)
{
REQUIRE(norm_global == MFEM_Approx(0.0, 5e-12, 5e-12));
}
else
{
vY -= vZ;
real_t norm_global = 0.0;
real_t norm_local = vY.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("spmat")
{
DOperator dop_mf(sol, {{Rho, &rho_ps}, {Coords, mfes}}, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
auto derivatives = std::integer_sequence<size_t, U> {};
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr, derivatives);
dop_mf.SetParameters({ &rho_coeff_cv, nodes });
auto dRdU = dop_mf.GetDerivative(U, {&x}, {&rho_coeff_cv, nodes});
SECTION("Vector Diffusion Action Linearized")
{
vX.Randomize(1);
vx.SetFromTrueDofs(vX);
SparseMatrix *A = nullptr;
dRdU->Assemble(A);
TestSameMatrices(*A, blf_fa.SpMat());
delete A;
DifferentiableOperator dop_mf(
{
{U, &vpfes},
{Coords, mfes},
},
{
{U, &vpfes}
}, pmesh);
typename VectorDiffusion<DIM>::MFApply mf_vector_diffusion_qf;
dop_mf.AddDomainIntegrator<QFBackend>(
mf_vector_diffusion_qf,
Inputs<Gradient<U>, Gradient<Coords>, Weight> {},
Outputs<Gradient<U>> {},
*ir, all_domain_attr,
Derivatives<U> {});
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{vX, nodestv};
const auto ddop = dop_mf.GetDerivative(U, X);
MultiVector Z{vZ};
ddop->Mult(vX, Z);
ParBilinearForm vblf_fa(&vpfes);
vblf_fa.AddDomainIntegrator(new VectorDiffusionIntegrator(ir));
vblf_fa.SetAssemblyLevel(AssemblyLevel::LEGACYFULL);
vblf_fa.Assemble();
vblf_fa.Finalize();
vblf_fa.Mult(vx, vy);
vpfes.GetProlongationMatrix()->MultTranspose(vy, vY);
vY -= vZ;
real_t norm_global = 0.0;
real_t norm_local = vY.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("Vector SparseMatrix")
{
vX.Randomize(1);
vx.SetFromTrueDofs(vX);
DifferentiableOperator dop_mf(
{
{U, &vpfes},
{Coords, mfes},
},
{
{U, &vpfes}
}, pmesh);
typename VectorDiffusion<DIM>::MFApply mf_vector_diffusion_qf;
dop_mf.AddDomainIntegrator<QFBackend>(
mf_vector_diffusion_qf,
Inputs<Gradient<U>, Gradient<Coords>, Weight> {},
Outputs<Gradient<U>> {},
*ir, all_domain_attr,
Derivatives<U> {});
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{vX, nodestv};
const auto ddop = dop_mf.GetDerivative(U, X);
MultiVector Z{vZ};
ddop->Mult(vX, Z);
ParBilinearForm vblf_fa(&vpfes);
vblf_fa.AddDomainIntegrator(new VectorDiffusionIntegrator(ir));
vblf_fa.SetAssemblyLevel(AssemblyLevel::LEGACYFULL);
vblf_fa.Assemble();
vblf_fa.Finalize();
SparseMatrix *A = nullptr;
ddop->Assemble(A);
TestSameMatrices(*A, vblf_fa.SpMat());
delete A;
MPI_Barrier(MPI_COMM_WORLD);
}
}
}
TEST_CASE("dFEM Diffusion", "[Parallel][dFEM][GPU]")
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM Diffusion 2D", "[Parallel][dFEM][GPU]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = GenAll({1}, {2, 3});
const auto meshs = { "../../data/inline-quad.mesh" };
const auto extra = { "../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/periodic-square.mesh"
};
diffusion<2>(GenAll(meshs, extra), p);
}
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
SECTION("2d")
{
const auto filename2d =
GENERATE(
"../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh"
);
diffusion<2>(filename2d, p);
}
SECTION("3d")
{
const auto filename3d =
GENERATE(
"../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
);
diffusion<3>(filename3d, p);
}
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM Diffusion 3D", "[Parallel][dFEM][GPU]")
{
const auto p = GenAll({1}, {2, 3});
const auto meshs = { "../../data/inline-hex.mesh" };
const auto extra = { "../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
};
diffusion<3>(GenAll(meshs, extra), p);
}
#endif // MFEM_USE_MPI
+122 -67
View File
@@ -10,18 +10,20 @@
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
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
@@ -50,69 +52,123 @@ void vectordivergence(const char *filename, int p)
ParFiniteElementSpace psfes(&pmesh, &fec);
ParFiniteElementSpace pvfes(&pmesh, &fec, DIM);
const int d1d(p + 1), q = 3 * p + 1;
const int q = 3 * p + 1;
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), q);
const int q1d(IntRules.Get(Geometry::SEGMENT, ir->GetOrder()).GetNPoints());
MFEM_VERIFY(d1d <= q1d, "q1d should be >= d1d");
ParGridFunction vx(&pvfes);
ParGridFunction sy(&psfes), sz(&psfes);
Vector vX(pvfes.GetTrueVSize());
Vector sY(psfes.GetTrueVSize()), sZ(psfes.GetTrueVSize());
ParGridFunction xv(&pvfes);
ParGridFunction ys(&psfes), sz(&psfes);
Vector Xv(pvfes.GetTrueVSize());
Vector Ys(psfes.GetTrueVSize()), Zs(psfes.GetTrueVSize());
vX.Randomize(1), vx.SetFromTrueDofs(vX);
Xv.Randomize(1), xv.SetFromTrueDofs(Xv);
MixedBilinearForm mblf_fa(&pvfes, &psfes);
mblf_fa.AddDomainIntegrator(new VectorDivergenceIntegrator);
mblf_fa.Assemble(), mblf_fa.Finalize();
mblf_fa.Mult(vx, sy);
mblf_fa.Assemble();
mblf_fa.Finalize();
mblf_fa.Mult(xv, ys);
MixedBilinearForm mblf_pa(&pvfes, &psfes);
mblf_pa.AddDomainIntegrator(new VectorDivergenceIntegrator);
mblf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
mblf_pa.Assemble();
mblf_pa.Mult(vx, sz);
sy -= sz;
REQUIRE(sy.Normlinf() == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
static constexpr int P = 0, V = 1, Coords = 2;
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const auto inputs = std::vector
{
static constexpr int P = 0, V = 1, Coords = 2;
ParFiniteElementSpace *mfes = nodes->ParFESpace();
FieldDescriptor{V, &pvfes},
FieldDescriptor{Coords, mfes}
};
const auto outputs = std::vector
{
FieldDescriptor{P, &psfes}
};
const auto solutions = std::vector{ FieldDescriptor{ P, &psfes } };
const auto parameters = std::vector
{
FieldDescriptor{ V, &pvfes },
FieldDescriptor{ Coords, mfes }
};
DifferentiableOperator dop_mf(inputs, outputs, pmesh);
DifferentiableOperator dop_mf(solutions, parameters, pmesh);
const auto mf_vector_divergence_qf =
[] MFEM_HOST_DEVICE(const tensor<dscalar_t, DIM, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w,
dscalar_t &v)
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
v = tr(dudx) * det(J) * w;
};
const auto mf_vector_divergence_qf =
[] MFEM_HOST_DEVICE(const tensor<dscalar_t, DIM, DIM> &dudxi,
const tensor<mfem::real_t, DIM, DIM> &J,
const real_t &w)
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
return tuple{ tr(dudx) * det(J) * w };
};
const auto derivatives = std::integer_sequence<size_t, V> {};
dop_mf.AddDomainIntegrator<LocalQFBackend>(
mf_vector_divergence_qf,
tuple{Gradient<V>{}, Gradient<Coords>{}, Weight{}},
tuple{Value<P>{}},
*ir, all_domain_attr, derivatives);
dop_mf.AddDomainIntegrator(mf_vector_divergence_qf,
tuple{ Gradient<V>{}, Gradient<Coords>{}, Weight{} },
tuple{ Value<P>{} },
*ir, all_domain_attr);
SECTION("Action")
{
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{Xv, nodestv};
MultiVector Z{Zs};
dop_mf.Mult(X, Z);
dop_mf.SetParameters({ &vx, nodes });
Vector unused(pvfes.GetTrueVSize());
dop_mf.Mult(unused, sZ);
mblf_fa.Mult(xv, ys);
psfes.GetProlongationMatrix()->MultTranspose(ys, Ys);
mblf_fa.Mult(vx, sy);
psfes.GetProlongationMatrix()->MultTranspose(sy, sY);
Ys -= Zs;
real_t norm_global = 0.0;
real_t norm_local = Ys.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);
}
sY -= sZ;
real_t norm_global = M_PI, norm_local = sY.Normlinf();
SECTION("Derivative Action")
{
Vector nodestv;
nodes->GetTrueDofs(nodestv);
MultiVector X{Xv, nodestv};
MultiVector Z{Zs};
auto dRdV = dop_mf.GetDerivative(V, X);
dRdV->Mult(X[0], Z);
mblf_fa.Mult(xv, ys);
psfes.GetProlongationMatrix()->MultTranspose(ys, Ys);
Ys -= Zs;
real_t norm_global = 0.0;
real_t norm_local = Ys.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("Derivative Transpose Action")
{
Vector nodestv;
nodes->GetTrueDofs(nodestv);
// Build cache with full primal state
MultiVector state{Xv, nodestv};
auto dRdV = dop_mf.GetDerivative(V, state);
// Direction in output (test) T-space: use Ys computed from mblf_fa.
psfes.GetProlongationMatrix()->MultTranspose(ys, Ys);
MultiVector direction{Ys};
// Result in derivative (trial) T-space.
Vector result_v(pvfes.GetTrueVSize());
result_v = 0.0;
MultiVector result{result_v};
dRdV->MultTranspose(direction, result);
psfes.GetProlongationMatrix()->Mult(Ys, ys);
mblf_fa.MultTranspose(ys, xv);
Vector ref_v(pvfes.GetTrueVSize());
pvfes.GetProlongationMatrix()->MultTranspose(xv, ref_v);
result_v -= ref_v;
real_t norm_global = 0.0;
real_t norm_local = result_v.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
@@ -120,32 +176,31 @@ void vectordivergence(const char *filename, int p)
}
}
TEST_CASE("dFEM VectorDivergence", "[Parallel][dFEM]")
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM VectorDivergence", "[Parallel][dFEM][GPU]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
const auto p = GenAll({1}, {2, 3});
SECTION("2D p=" + std::to_string(p))
{
const auto filename =
GENERATE("../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh");
vectordivergence<2>(filename, p);
const auto meshs = { "../../data/inline-quad.mesh" };
const auto extra = { "../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/periodic-square.mesh"
};
vectordivergence<2>(GenAll(meshs, extra), p);
}
SECTION("3D p=" + std::to_string(p))
{
const auto filename =
GENERATE("../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh");
vectordivergence<3>(filename, p);
const auto meshs = { "../../data/inline-hex.mesh" };
const auto extra = { "../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
};
vectordivergence<3>(GenAll(meshs, extra), p);
}
}
+279
View File
@@ -0,0 +1,279 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
using namespace mfem;
using namespace mfem::future;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using dscalar_t = dual<real_t, real_t>;
#endif
template <typename dscalar_t, int dim>
struct CubicH1Functional
{
static constexpr real_t alpha = 0.3;
static constexpr real_t beta = 0.7;
MFEM_HOST_DEVICE inline
auto operator()(const dscalar_t &u,
const tensor<dscalar_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
dscalar_t &f) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto dx = det(J) * w;
const auto z =
+ 0.5_r * u * u
+ 0.25_r * alpha * u * u * u * u
+ 0.5_r * beta * dot(dudx, dudx);
f = z * dx;
}
};
template <int dim>
class MyFunctional
{
static constexpr int U = 0, Coords = 1, Q = 2;
public:
MyFunctional(const ParFiniteElementSpace &fes,
const ParFiniteElementSpace &mfes,
const IntegrationRule &ir) :
comm(fes.GetComm()),
qspace(*fes.GetParMesh(), ir),
qspace_vec(qspace, 1),
q(qspace_vec)
{
const auto in_fds = std::vector
{
FieldDescriptor{U, &fes},
FieldDescriptor{Coords, &mfes}
};
const auto out_fds = std::vector
{
FieldDescriptor{Q, &qspace_vec}
};
const auto &mesh = *fes.GetParMesh();
Array<int> all_domain_attr;
if (mesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(mesh.attributes.Max());
all_domain_attr = 1;
}
dop = std::make_unique<DifferentiableOperator>(in_fds, out_fds, mesh);
CubicH1Functional<dscalar_t, dim> apply;
auto derivatives = std::integer_sequence<size_t, U> {};
dop->AddDomainIntegrator<LocalQFBackend>(
apply,
tuple{Value<U>{}, Gradient<U>{}, Gradient<Coords>{}, Weight{}},
tuple{Identity<Q>{}},
ir, all_domain_attr, derivatives);
mesh.GetNodes()->GetTrueDofs(coords);
}
real_t Eval(const Vector &u) const
{
real_t local = EvalLocal(u), global;
MPI_Allreduce(&local, &global, 1, MPITypeMap<real_t>::mpi_type, MPI_SUM, comm);
return global;
}
// Returns the directional derivative dJ/du · du.
real_t dJdu_dir(const Vector &u, const Vector &du) const
{
real_t local = dJdu_dir_local(u, du), global;
MPI_Allreduce(&local, &global, 1, MPITypeMap<real_t>::mpi_type, MPI_SUM, comm);
return global;
}
// Computes the full gradient \nabla J(u) in the trial space via J^T.
// Since Eval sums raw QF values (J = \sum f_q), the adjoint of the
// summation is a QF of ones.
void grad(const Vector &u, Vector &g) const
{
MultiVector X{u, coords};
q = 1.0;
MultiVector ones{q};
dop->GetDerivative(U, X)->MultTranspose(ones, g);
}
// Computes the full gradient via element-wise central differences.
void grad_fd(const Vector &u, Vector &g, real_t eps = 1e-5) const
{
const int local_size = u.Size();
// Global offset for this rank's DOFs and total DOF count.
int offset = 0, global_size = local_size;
MPI_Exscan(&local_size, &offset, 1, MPITypeMap<int>::mpi_type, MPI_SUM, comm);
MPI_Allreduce(MPI_IN_PLACE, &global_size, 1, MPITypeMap<int>::mpi_type, MPI_SUM,
comm);
g.SetSize(local_size);
Vector up(u), um(u);
// Loop over global DOF indices. Each rank perturbs only when gi falls in
// its local range [offset, offset+local_size); all ranks call Eval together.
for (int gi = 0; gi < global_size; ++gi)
{
const int li = gi - offset;
if (li >= 0 && li < local_size) { up(li) += eps; um(li) -= eps; }
const real_t Jp = Eval(up);
const real_t Jm = Eval(um);
if (li >= 0 && li < local_size)
{
g(li) = (Jp - Jm) / (2.0 * eps);
up(li) = u(li);
um(li) = u(li);
}
}
}
private:
real_t EvalLocal(const Vector &u) const
{
MultiVector X{u, coords};
MultiVector Y{q};
dop->Mult(X, Y);
return q.Sum();
}
real_t dJdu_dir_local(const Vector &u, const Vector &du) const
{
MultiVector X{u, coords};
MultiVector dY{q};
dop->GetDerivative(U, X)->Mult(du, dY);
return q.Sum();
}
MPI_Comm comm;
std::unique_ptr<DifferentiableOperator> dop;
QuadratureSpace qspace;
VectorQuadratureSpace qspace_vec;
mutable QuadratureFunction q;
Vector coords;
};
template <int DIM>
void functional(const char *filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const auto ir = IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
Vector u(fes.GetTrueVSize());
Vector du(fes.GetTrueVSize());
u.Randomize(5532);
du.Randomize(3251);
MyFunctional<DIM> functional(fes, *mfes, ir);
const real_t dJ_ad = functional.dJdu_dir(u, du);
Vector g(fes.GetTrueVSize());
functional.grad(u, g);
const real_t dJ_ad_grad = InnerProduct(pmesh.GetComm(), g, du);
real_t best_error_dir = infinity();
real_t best_error_grad = infinity();
for (real_t eps : {1e-3, 3e-4, 1e-4, 3e-5})
{
Vector up(u), um(u);
up.Add(eps, du);
um.Add(-eps, du);
const real_t Jp = functional.Eval(up);
const real_t Jm = functional.Eval(um);
const real_t dJ_fd = (Jp - Jm) / (2.0 * eps);
const real_t scale = std::max(real_t(1.0), std::abs(dJ_fd));
best_error_dir = std::min(best_error_dir,
std::abs(dJ_ad - dJ_fd) / scale);
best_error_grad = std::min(best_error_grad,
std::abs(dJ_ad_grad - dJ_fd) / scale);
}
REQUIRE(best_error_dir < 1e-7);
REQUIRE(best_error_grad < 1e-7);
// Must match entry-wise FD gradient
Vector g_fd;
functional.grad_fd(u, g_fd);
Vector diff(g);
diff -= g_fd;
const real_t scale = std::max(real_t(1.0), g_fd.Normlinf());
real_t local_norm = diff.Normlinf();
real_t global_norm;
MPI_Allreduce(&local_norm, &global_norm, 1, MPITypeMap<real_t>::mpi_type,
MPI_SUM, pmesh.GetComm());
REQUIRE(diff.Normlinf() / scale < 1e-5);
}
TEST_CASE("dFEM functional derivative action matches finite differences",
"[Parallel][dFEM][GPU]")
{
const auto p = GenAll({1}, {2, 3});
SECTION("2d")
{
const auto meshs = { "../../data/inline-quad.mesh" };
const auto extra = { "../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/periodic-square.mesh"
};
functional<2>(GenAll(meshs, extra), p);
}
SECTION("3d")
{
const auto meshs = { "../../data/inline-hex.mesh" };
const auto extra = { "../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
};
functional<3>(GenAll(meshs, extra), p);
}
}
#endif // MFEM_USE_MPI
+312
View File
@@ -0,0 +1,312 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
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 = 3;
constexpr int Displacement = 0;
constexpr int Coords = 1;
constexpr int Energy = 2;
template <typename dscalar_t>
struct NeoHookeanEnergy
{
real_t D1 = 100.0;
real_t C1 = 50.0;
MFEM_HOST_DEVICE inline
void operator()(const tensor<dscalar_t, dim, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
dscalar_t &energy) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto F = IdentityMatrix<dim>() + dudx;
const auto C = transpose(F) * F;
const auto JF = det(F);
const auto I1_bar = pow(JF, -2.0_r / 3.0_r) * tr(C);
const auto psi = D1 * (JF - 1.0_r) * (JF - 1.0_r)
+ C1 * (I1_bar - real_t(dim));
energy = psi * det(J) * w;
}
};
template <typename dscalar_t>
struct NeoHookeanStress
{
real_t D1 = 100.0;
real_t C1 = 50.0;
MFEM_HOST_DEVICE inline
void operator()(const tensor<dscalar_t, dim, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
tensor<dscalar_t, dim, dim> &dvdxi) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto F = IdentityMatrix<dim>() + dudx;
const auto JF = det(F);
const auto FinvT = transpose(inv(F));
const auto I1 = tr(transpose(F) * F);
const auto P = 2.0_r * D1 * JF * (JF - 1.0_r) * FinvT
+ 2.0_r * C1 * pow(JF, -2.0_r / 3.0_r)
* (F - (I1 / 3.0_r) * FinvT);
dvdxi = P * transpose(invJ) * det(J) * w;
}
};
class HyperelasticityProblem
{
public:
HyperelasticityProblem(ParFiniteElementSpace &fes,
const IntegrationRule &ir,
bool use_energy) :
fes(fes),
use_energy(use_energy),
qspace(*fes.GetParMesh(), ir),
qspace_vec(qspace, 1),
q(qspace_vec)
{
auto &mesh_nodes = *static_cast<ParGridFunction *>
(fes.GetParMesh()->GetNodes());
mesh_nodes_fes = mesh_nodes.ParFESpace();
mesh_nodes.GetTrueDofs(mesh_nodes_tdofs);
const std::vector<FieldDescriptor> inputs =
{
{Displacement, &fes},
{Coords, mesh_nodes_fes}
};
std::vector<FieldDescriptor> outputs;
if (use_energy)
{
outputs = std::vector<FieldDescriptor>
{
{Energy, &qspace_vec}
};
}
else
{
outputs = std::vector<FieldDescriptor>
{
{Displacement, &fes}
};
}
dop = std::make_shared<DifferentiableOperator>(inputs, outputs,
*fes.GetParMesh());
Array<int> all_domain_attr;
if (fes.GetMesh()->attributes.Size() > 0)
{
all_domain_attr.SetSize(fes.GetMesh()->attributes.Max());
all_domain_attr = 1;
}
auto derivatives = std::integer_sequence<size_t, Displacement> {};
if (use_energy)
{
NeoHookeanEnergy<dscalar_t> energy;
dop->AddDomainIntegrator<LocalQFBackend, true>(
energy,
Inputs<Gradient<Displacement>, Gradient<Coords>, Weight> {},
Outputs<Identity<Energy>> {},
ir, all_domain_attr, derivatives);
}
else
{
NeoHookeanStress<dscalar_t> stress;
dop->AddDomainIntegrator<LocalQFBackend>(
stress,
Inputs<Gradient<Displacement>, Gradient<Coords>, Weight> {},
Outputs<Gradient<Displacement>> {},
ir, all_domain_attr, derivatives);
}
}
void SetEssentialAttributes(const Array<int> &ess_bdr)
{
fes.GetEssentialTrueDofs(ess_bdr, ess_tdofs);
}
void SetPrescribedDisplacementAttributes(const Array<int> &disp_bdr)
{
fes.GetEssentialTrueDofs(disp_bdr, prescribed_tdofs);
}
const Array<int>& GetPrescribedDisplacementTDofs() const
{
return prescribed_tdofs;
}
const Array<int>& GetEssentialTDofs() const
{
return ess_tdofs;
}
void Residual(const Vector &x, Vector &r) const
{
MultiVector X{x, mesh_nodes_tdofs};
MultiVector R{r};
if (use_energy)
{
dop->GetDerivative(Displacement)->Mult(X, R);
}
else
{
dop->Mult(X, R);
}
r.SetSubVector(ess_tdofs, 0.0);
}
void GradientAction(const Vector &x, const Vector &dx, Vector &y) const
{
MultiVector X{x, mesh_nodes_tdofs};
std::shared_ptr<DerivativeOperator> derivative;
if (use_energy)
{
derivative = dop->GetSecondDerivative(Displacement, X);
}
else
{
derivative = dop->GetDerivative(Displacement, X);
}
Vector local_dx(dx);
local_dx.SetSubVector(ess_tdofs, 0.0);
MultiVector Y{y};
derivative->Mult(local_dx, Y);
y.SetSubVector(ess_tdofs, 0.0);
}
private:
ParFiniteElementSpace &fes;
bool use_energy = false;
ParFiniteElementSpace *mesh_nodes_fes = nullptr;
QuadratureSpace qspace;
VectorQuadratureSpace qspace_vec;
QuadratureFunction q;
Vector mesh_nodes_tdofs;
Array<int> ess_tdofs;
Array<int> prescribed_tdofs;
std::shared_ptr<DifferentiableOperator> dop;
};
struct HyperelasticityTestContext
{
HyperelasticityTestContext(bool use_energy)
{
Mesh mesh = Mesh::MakeCartesian3D(8, 2, 2, Element::HEXAHEDRON, 8.0, 1.0, 1.0);
mesh.EnsureNodes();
pmesh = std::make_unique<ParMesh>(MPI_COMM_WORLD, mesh);
mesh.Clear();
pmesh->EnsureNodes();
fec = std::make_unique<H1_FECollection>(1, dim);
fes = std::make_unique<ParFiniteElementSpace>(pmesh.get(), fec.get(), dim,
Ordering::byNODES);
const IntegrationRule &ir = IntRules.Get(pmesh->GetTypicalElementGeometry(), 3);
problem = std::make_unique<HyperelasticityProblem>(*fes, ir, use_energy);
Array<int> ess_attr(pmesh->bdr_attributes.Max());
ess_attr = 0;
ess_attr[4] = 1;
ess_attr[2] = 1;
problem->SetEssentialAttributes(ess_attr);
Array<int> disp_attr(pmesh->bdr_attributes.Max());
disp_attr = 0;
disp_attr[2] = 1;
problem->SetPrescribedDisplacementAttributes(disp_attr);
state.SetSize(fes->GetTrueVSize());
state.Randomize(11);
state -= 0.5;
state *= 1.0e-3;
state.SetSubVector(problem->GetEssentialTDofs(), 0.0);
state.SetSubVector(problem->GetPrescribedDisplacementTDofs(), 1.0e-2);
direction.SetSize(fes->GetTrueVSize());
direction.Randomize(17);
direction -= 0.5;
}
std::unique_ptr<ParMesh> pmesh;
std::unique_ptr<H1_FECollection> fec;
std::unique_ptr<ParFiniteElementSpace> fes;
std::unique_ptr<HyperelasticityProblem> problem;
Vector state;
Vector direction;
};
TEST_CASE("dfem neo-hookean energy and stress agree",
"[Parallel][dFEM][Hyperelasticity]")
{
HyperelasticityTestContext energy(true);
HyperelasticityTestContext stress(false);
REQUIRE(energy.state.Size() == stress.state.Size());
// Check residuals from energy and stress formulations should match.
Vector energy_residual(energy.state.Size());
Vector stress_residual(stress.state.Size());
energy.problem->Residual(energy.state, energy_residual);
stress.problem->Residual(stress.state, stress_residual);
Vector residual_diff(energy_residual);
residual_diff -= stress_residual;
REQUIRE(residual_diff.Norml2() < 1e-12);
// Check the energy Hessian action should match the stress Jacobian action.
Vector energy_action(energy.state.Size());
Vector stress_action(stress.state.Size());
energy.problem->GradientAction(energy.state, energy.direction, energy_action);
stress.problem->GradientAction(stress.state, stress.direction, stress_action);
REQUIRE(energy_action.Norml2() > 0.0);
REQUIRE(stress_action.Norml2() > 0.0);
Vector action_diff(energy_action);
action_diff -= stress_action;
REQUIRE(action_diff.Norml2() < 1e-10);
// Print the residuals, grad actions, for debugging purposes.
mfem::out << "Energy residual norm: " << energy_residual.Norml2() << std::endl;
mfem::out << "Stress residual norm: " << stress_residual.Norml2() << std::endl;
mfem::out << "Residual difference norm: " << residual_diff.Norml2() << std::endl;
mfem::out << "Energy action norm: " << energy_action.Norml2() << std::endl;
mfem::out << "Stress action norm: " << stress_action.Norml2() << std::endl;
mfem::out << "Action difference norm: " << action_diff.Norml2() << std::endl;
}
#endif
+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.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#ifndef _WIN32
#include "../../../fem/dfem/doperator.hpp"
#include "../../../linalg/tensor_arrays.hpp"
using namespace mfem;
using namespace mfem::future;
#ifdef MFEM_USE_ENZYME
using dreal_t = real_t;
#else
using dreal_t = dual<real_t, real_t>;
#endif
static constexpr int U = 0, V = 1, X = 2;
// ────────────────────────────────────────────────────────────────────────────
template<int DIM>
struct global_qf
{
void operator()(tensor_array<const dreal_t> &x,
tensor_array<const dreal_t> &y,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t> &w,
tensor_array<dreal_t> &z) const
{
mfem::forall(x.size(),
[=] MFEM_HOST_DEVICE(int q)
{
const dreal_t xq = x(q), yq = y(q);
z(q) = sin(xq) * cos(yq) * (xq + yq) * w(q) * det(J(q));
});
}
};
// ────────────────────────────────────────────────────────────────────────────
template<int DIM>
struct local_qf
{
inline MFEM_HOST_DEVICE void operator()(const dreal_t &x,
const dreal_t &y,
const tensor<real_t, DIM, DIM> &J,
const real_t &w,
dreal_t &z) const
{ z = sin(x) * cos(y) * (x + y) * w * det(J); }
};
// ────────────────────────────────────────────────────────────────────────────
template<int DIM>
static void VerifyJvpVjp(DifferentiableOperator &F,
ParFiniteElementSpace &fes,
ParGridFunction &nodes)
{
const auto nfes = nodes.ParFESpace();
const auto tvsize = fes.GetTrueVSize();
const auto ntvsize = nfes->GetTrueVSize();
const auto comm = fes.GetParMesh()->GetComm();
Vector X_bar(tvsize), Y_bar(tvsize), N_bar(ntvsize);
X_bar.Randomize(0x9e3779b9);
Y_bar.Randomize(0x9e3779b1);
nodes.GetTrueDofs(N_bar);
MultiVector state{ X_bar, Y_bar, N_bar };
Vector dX(tvsize), dY(tvsize), dZ(tvsize);
dX.Randomize(0x01000193);
dY.Randomize(0x1b873593);
Vector dU(tvsize), dV(tvsize);
MultiVector mdU{ dU }, mdV{ dV };
const auto dFu = F.GetDerivative(U, state);
const auto dFv = F.GetDerivative(V, state);
dFu->Mult(dX, mdU); // dU = (∂F/∂u) dX
dFv->Mult(dY, mdV); // dV = (∂F/∂v) dY
add(dU, dV, dZ);
Vector dX_star(tvsize), dY_star(tvsize), dZ_star(tvsize);
dZ_star.Randomize(0x7ed55d16);
MultiVector mdZ_star{ dZ_star }, mdX_star{ dX_star }, mdY_star{ dY_star };
dFu->MultTranspose(mdZ_star, mdX_star); // dX* = (∂F/∂u)^T dZ*
dFv->MultTranspose(mdZ_star, mdY_star); // dY* = (∂F/∂v)^T dZ*
// Tangent/cotangent consistency test:
// <dZ*, dZ> = <dX*, dX> + <dY*, dY>
REQUIRE(InnerProduct(comm, dZ_star, dZ) ==
MFEM_Approx(InnerProduct(comm, dX_star, dX) +
InnerProduct(comm, dY_star, dY)));
REQUIRE(InnerProduct(comm, dZ_star, dU) ==
MFEM_Approx(InnerProduct(comm, dX_star, dX)));
REQUIRE(InnerProduct(comm, dZ_star, dV) ==
MFEM_Approx(InnerProduct(comm, dY_star, dY)));
}
// ────────────────────────────────────────────────────────────────────────────
template<int DIM>
void TestJvpVjp(const char *filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
smesh.Clear();
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
const auto *nfes = nodes->ParFESpace();
const auto geom = pmesh.GetTypicalElementGeometry();
const auto ir = &IntRules.Get(geom, 2 * p + 1);
using vfds_t = std::vector<FieldDescriptor>;
const vfds_t in_fds = { { U, &fes }, { V, &fes }, { X, nfes } };
const vfds_t out_fds = { { U, &fes } };
DifferentiableOperator F(in_fds, out_fds, pmesh);
using IT = Inputs<Value<U>, Value<V>, Gradient<X>, Weight>;
using OT = Outputs<Value<U>>;
using DT = Derivatives<U, V>;
if constexpr (!mfem_use_gpu)
{
global_qf<DIM> q_gfn{};
F.AddDomainIntegrator<GlobalQFBackend>(
q_gfn, IT{}, OT{}, *ir, all_domain_attr, DT{});
}
using LQT = local_qf<DIM>;
local_qf<DIM> q_lfn{};
F.AddDomainIntegrator<LocalQFBackend>(
q_lfn, IT{}, OT{}, *ir, all_domain_attr, DT{});
AddLocalSpecializations<DIM, 3, LQT, IT, OT, DT>();
VerifyJvpVjp<DIM>(F, fes, *nodes);
}
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM JVP-VJP 2D", "[Parallel][dFEM][GPU]")
{
const auto p = GenAll({ 1 }, { 2, 3 });
const auto meshs = { "../../data/inline-quad.mesh" };
const auto extra = { "../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/periodic-square.mesh"
};
TestJvpVjp<2>(GenAll(meshs, extra), p);
}
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM JVP-VJP 3D", "[Parallel][dFEM][GPU]")
{
const auto p = GenAll({ 1 }, { 2, 3 });
const auto meshs = { "../../data/inline-hex.mesh" };
const auto extra = { "../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
};
TestJvpVjp<3>(GenAll(meshs, extra), p);
}
#endif // _WIN32
#endif // MFEM_USE_MPI
+61 -24
View File
@@ -10,37 +10,37 @@
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
constexpr int DIM = 3;
namespace kernels
{
template <int DIM>
struct MFApply
{
MFEM_HOST_DEVICE inline auto operator()(const tensor<real_t, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w) const
MFEM_HOST_DEVICE inline auto operator()(
const tensor<real_t, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w,
tensor<real_t, DIM> &dvdxi) const
{
const auto invJ = inv(J);
return tuple{ (dudxi * invJ) * transpose(invJ) * det(J) * w };
const auto invJt = transpose(invJ);
dvdxi = (dudxi * invJ) * invJt * det(J) * w;
}
};
}
TEST_CASE("DFEM L-Vector interface", "[Parallel][dFEM][GPU]")
template <int DIM, typename QFBackend = LocalQFBackend>
void l_vector_interface(const char *filename, int p)
{
constexpr int p = 2; // Polynomial order
constexpr int r = 1;
constexpr int q = 2 * p + r;
CAPTURE(filename, DIM, p);
const auto filename = GENERATE("../../data/fichera.mesh");
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
@@ -49,6 +49,9 @@ TEST_CASE("DFEM L-Vector interface", "[Parallel][dFEM][GPU]")
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
smesh.Clear();
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
const int q = 2 * p + 1;
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
@@ -75,18 +78,28 @@ TEST_CASE("DFEM L-Vector interface", "[Parallel][dFEM][GPU]")
static constexpr int U = 0, Coords = 1;
const auto solution = std::vector{FieldDescriptor{U, &pfes}};
DifferentiableOperator dop(solution, {{Coords, mfes}}, pmesh);
const auto in_fds = std::vector
{
FieldDescriptor{ U, &pfes },
FieldDescriptor{ Coords, mfes }
};
const auto out_fds = std::vector{ FieldDescriptor{ U, &pfes } };
kernels::MFApply mf_apply_qf;
dop.AddDomainIntegrator(mf_apply_qf,
tuple{Gradient<U>{}, Gradient<Coords>{}, Weight{}},
tuple{Gradient<U>{}}, *ir, all_domain_attr);
DifferentiableOperator dop(in_fds, out_fds, pmesh);
MFApply<DIM> mf_apply;
dop.AddDomainIntegrator<LocalQFBackend>(
mf_apply,
tuple{Gradient<U>{}, Gradient<Coords>{}, Weight{}},
tuple{Gradient<U>{}},
*ir, all_domain_attr);
// Use the L-vector interface to multiply
dop.SetMultLevel(DifferentiableOperator::MultLevel::LVECTOR);
dop.SetParameters({nodes});
dop.Mult(x, z);
MultiVector mx{x, *nodes};
MultiVector mz{z};
dop.Mult(mx, mz);
blf_fa.Mult(x, y);
@@ -94,4 +107,28 @@ TEST_CASE("DFEM L-Vector interface", "[Parallel][dFEM][GPU]")
REQUIRE(z.Normlinf() == MFEM_Approx(0.0));
}
#endif
TEST_CASE("dFEM L-Vector 2D", "[Parallel][dFEM][GPU]")
{
const auto p = GenAll({1}, {2, 3});
const auto meshs = { "../../data/inline-quad.mesh" };
const auto extra = { "../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/periodic-square.mesh"
};
l_vector_interface<2>(GenAll(meshs, extra), p);
}
TEST_CASE("dFEM L-Vector 3D", "[Parallel][dFEM][GPU]")
{
const auto p = GenAll({1}, {2, 3});
const auto meshs = { "../../data/inline-hex.mesh" };
const auto extra = { "../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
};
l_vector_interface<3>(GenAll(meshs, extra), p);
}
#endif // MFEM_USE_MPI
+461 -182
View File
@@ -10,160 +10,82 @@
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "../linalg/test_same_matrices.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../linalg/test_same_matrices.hpp"
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
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
// ────────────────────────────────────────────────────────────────────────────
template <int DIM> struct global_mf_mass_qf
{
void operator()(tensor_array<const dscalar_t> &u,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t> &w,
tensor_array<dscalar_t> &v) const
{
// Enzyme requires extra support to handle:
// mfem::forall(u.size(), [=] MFEM_HOST_DEVICE (int q)
for (size_t q = 0; q < u.size(); ++q)
{
v(q) = (dscalar_t)(u(q)) * w(q) * det(J(q));
}
}
};
template <int DIM> struct local_mf_mass_qf
{
inline MFEM_HOST_DEVICE
void operator()(const dscalar_t &u,
const tensor<real_t, DIM, DIM> &J,
const real_t &w,
dscalar_t &v) const
{
v = u * w * det(J);
}
};
// ────────────────────────────────────────────────────────────────────────────
template <int DIM> struct local_mf_vector_mass_qf
{
MFEM_HOST_DEVICE inline auto operator()(
const tensor<dscalar_t, DIM> &vu,
const tensor<real_t, DIM, DIM> &J,
const real_t &w,
tensor<dscalar_t, DIM> &vv) const
{
vv = vu * w * det(J);
}
};
// ────────────────────────────────────────────────────────────────────────────
template <int DIM>
void mass_action(const char *filename, int p)
{
constexpr int BDIM = DIM - 1;
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto* nodes = static_cast<ParGridFunction*>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
ParGridFunction x(&fes), y(&fes), z(&fes);
Vector X(fes.GetTrueVSize()), Y(fes.GetTrueVSize()), Z(fes.GetTrueVSize());
X.Randomize(1);
x.SetFromTrueDofs(X);
ConstantCoefficient one(1.0);
SECTION("domain")
{
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
ParBilinearForm blf(&fes);
blf.AddDomainIntegrator(new MassIntegrator(one, ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
fes.GetProlongationMatrix()->MultTranspose(y, Y);
static constexpr int U = 0, Coords = 1;
const auto sol = std::vector{ FieldDescriptor{ U, &fes } };
DifferentiableOperator dop(sol, {{Coords, nodes->ParFESpace()}}, pmesh);
const auto mf_mass_qf =
[] MFEM_HOST_DEVICE(const real_t &u,
const tensor<real_t, DIM, DIM> &J, const real_t &w)
{ return tuple{u * w * det(J)}; };
dop.AddDomainIntegrator(mf_mass_qf,
tuple{ Value<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Value<U>{} },
*ir, all_domain_attr);
dop.SetParameters({ nodes });
fes.GetRestrictionMatrix()->Mult(x, X);
dop.Mult(X, Z);
Y -= Z;
real_t norm_g, norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
// Test boundary
// This ensures that we're not trying to test on fully periodic meshes
if (!((std::string("../../data/periodic-square.mesh").compare(filename) == 0) ||
(std::string("../../data/periodic-cube.mesh").compare(filename) == 0)))
{
SECTION("boundary")
{
const auto *ir = &IntRules.Get(pmesh.GetTypicalFaceGeometry(), 2 * p);
Array<int> all_bdr_attr;
if (pmesh.bdr_attributes.Size() > 0)
{
all_bdr_attr.SetSize(pmesh.bdr_attributes.Max());
all_bdr_attr = 1;
}
ParBilinearForm blf(&fes);
blf.AddBoundaryIntegrator(new MassIntegrator(one, ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
fes.GetProlongationMatrix()->MultTranspose(y, Y);
static constexpr int U = 0, Coords = 1;
const auto sol = std::vector{FieldDescriptor{U, &fes}};
DifferentiableOperator dop(sol, {{Coords, nodes->ParFESpace()}}, pmesh);
const auto mf_mass_qf =
[] MFEM_HOST_DEVICE(const dscalar_t &u,
const tensor<real_t, DIM, BDIM> &J,
const real_t &w)
{
return tuple{u * weight(J) * w};
};
auto derivatives = std::integer_sequence<size_t, U> {};
dop.AddBoundaryIntegrator(mf_mass_qf,
tuple{ Value<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Value<U>{} },
*ir, all_bdr_attr, derivatives);
dop.SetParameters({nodes});
fes.GetRestrictionMatrix()->Mult(x, X);
dop.Mult(X, Z);
Y -= Z;
real_t norm_g, norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
auto dRdU = dop.GetDerivative(U, {&x}, {nodes});
dRdU->Mult(X, Z);
fes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
}
template <int DIM> void mass_mat_mixed(const char* filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
pmesh.EnsureNodes();
auto* nodes = static_cast<ParGridFunction*>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
@@ -172,51 +94,394 @@ template <int DIM> void mass_mat_mixed(const char* filename, int p)
all_domain_attr = 1;
}
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
H1_FECollection fec(p, DIM);
ParFiniteElementSpace pfes(&pmesh, &fec);
const int tvsize = pfes.GetTrueVSize();
ParGridFunction x(&pfes), y(&pfes), z(&pfes);
Vector X(tvsize), Y(tvsize), Z(tvsize), dZ(tvsize);
X.Randomize(1);
x.SetFromTrueDofs(X);
ConstantCoefficient one(1.0), zero(0.0);
ParBilinearForm blf(&pfes);
// Add two mass integrators as we use both local and global QF backends
// If GPU is enabled, we only add the local QF backend
blf.AddDomainIntegrator(new MassIntegrator(one, ir));
if constexpr(!mfem_use_gpu)
{
blf.AddDomainIntegrator(new MassIntegrator(one, ir));
}
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
static constexpr int U = 0, Coords = 1;
const auto in_fds = std::vector
{
FieldDescriptor{ U, &pfes },
FieldDescriptor{ Coords, mfes }
};
const auto out_fds = std::vector{ FieldDescriptor{ U, &pfes } };
Vector N;
nodes->GetTrueDofs(N);
using IT = Inputs<Value<U>, Gradient<Coords>, Weight>;
using OT = Outputs<Value<U>>;
using DT = Derivatives<U>;
SECTION("Action")
{
blf.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
DifferentiableOperator dop(in_fds, out_fds, pmesh);
local_mf_mass_qf<DIM> local_qfn;
dop.AddDomainIntegrator<LocalQFBackend>(
local_qfn, IT {}, OT {}, *ir, all_domain_attr);
if constexpr(!mfem_use_gpu)
{
global_mf_mass_qf<DIM> global_qfn;
dop.AddDomainIntegrator<GlobalQFBackend>(
global_qfn, IT {}, OT {}, *ir, all_domain_attr);
}
MultiVector MX{X, N}, MZ{Z};
dop.Mult(MX, MZ);
Y -= Z;
y.SetFromTrueDofs(Y);
REQUIRE(y.ComputeMaxError(zero) == MFEM_Approx(0.0));
}
SECTION("Action Linearized")
{
// Randomize again s.t. the setup cache cannot
// trivially succeed by caching one direction only.
X.Randomize(0x9e3779b9);
x.SetFromTrueDofs(X);
DifferentiableOperator dop(in_fds, out_fds, pmesh);
local_mf_mass_qf<DIM> local_qfn;
dop.AddDomainIntegrator<LocalQFBackend>(
local_qfn, IT {}, OT {}, *ir, all_domain_attr, DT {});
if constexpr(!mfem_use_gpu)
{
global_mf_mass_qf<DIM> global_qfn;
dop.AddDomainIntegrator<GlobalQFBackend>(
global_qfn, IT {}, OT {}, *ir, all_domain_attr, DT {});
}
MultiVector MX{X, N}, MZ{Z}, MdZ{dZ};
// without cache
{
blf.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
const bool no_cache = false;
auto ddop_nc = dop.GetDerivative(U, MX, no_cache);
ddop_nc->Mult(MX[0], MdZ);
Y -= dZ;
y.SetFromTrueDofs(Y);
REQUIRE(y.ComputeMaxError(zero) == MFEM_Approx(0.0));
}
// allow using the setup cache
{
blf.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
const bool use_cache = true;
auto ddop_wc = dop.GetDerivative(U, MX, use_cache);
ddop_wc->Mult(MX[0], MdZ);
Y -= dZ;
y.SetFromTrueDofs(Y);
REQUIRE(y.ComputeMaxError(zero) == MFEM_Approx(0.0));
}
}
SECTION("Assemble Diagonal")
{
DifferentiableOperator dop(in_fds, out_fds, pmesh);
local_mf_mass_qf<DIM> local_qfn;
dop.AddDomainIntegrator<LocalQFBackend>(
local_qfn, IT {}, OT {}, *ir, all_domain_attr, DT {});
if constexpr(!mfem_use_gpu)
{
global_mf_mass_qf<DIM> global_qfn;
dop.AddDomainIntegrator<GlobalQFBackend>(
global_qfn, IT {}, OT {}, *ir, all_domain_attr, DT {});
}
pfes.GetRestrictionMatrix()->Mult(x, X);
MultiVector MX{X, N};
auto dRdU = dop.GetDerivative(U, MX);
Vector dfem_D(tvsize), mfem_D(tvsize);
dRdU->AssembleDiagonal(dfem_D);
blf.AssembleDiagonal(mfem_D);
pfes.GetProlongationMatrix()->MultTranspose(mfem_D, Y);
Y -= dfem_D;
y.SetFromTrueDofs(Y);
REQUIRE(y.ComputeMaxError(zero) == MFEM_Approx(0.0));
}
// Test boundary
#if 0 // TODO: Boundary tests
// This ensures that we're not trying to test on fully periodic meshes
if (!((std::string("../../data/periodic-square.mesh").compare(filename) == 0) ||
(std::string("../../data/periodic-cube.mesh").compare(filename) == 0)))
{
constexpr int BDIM = DIM - 1;
SECTION("boundary")
{
Array<int> all_bdr_attr;
if (pmesh.bdr_attributes.Size() > 0)
{
all_bdr_attr.SetSize(pmesh.bdr_attributes.Max());
all_bdr_attr = 1;
}
ParBilinearForm blf(&pfes);
blf.AddBoundaryIntegrator(new MassIntegrator(one, ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
static constexpr int U = 0, Coords = 1;
const auto in_fds = std::vector<FieldDescriptor> {{ U, &pfes }, { Coords, mfes }};
const auto out_fds = std::vector<FieldDescriptor> {{ U, &pfes }};
DifferentiableOperator dop(in_fds, out_fds, pmesh);
const auto mf_mass_qf =
[] MFEM_HOST_DEVICE(const dscalar_t &u,
const tensor<real_t, DIM, BDIM> &J,
const real_t &w,
dscalar_t& v)
{
v = u * weight(J) * w;
};
auto derivatives = std::integer_sequence<size_t, U> {};
dop.AddBoundaryIntegrator<QFBackend>(mf_mass_qf,
tuple{ Value<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Value<U>{} },
*ir, all_bdr_attr, derivatives);
pfes.GetRestrictionMatrix()->Mult(x, X);
Vector N;
nodes->GetTrueDofs(N);
MultiVector MX{X, N}, MZ{Z};
dop.Mult(MX, MZ);
Y -= MZ[0];
real_t norm_g, norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
nodes->GetTrueDofs(N);
auto dRdU = dop.GetDerivative(U, MX);
dRdU->Mult(MX[0], MZ);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
#endif // TODO: Boundary tests
}
// ────────────────────────────────────────────────────────────────────────────
template <int DIM>
void vector_mass_action(const char *filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto* nodes = static_cast<ParGridFunction*>(pmesh.GetNodes());
smesh.Clear();
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
H1_FECollection fec(p, DIM);
ParFiniteElementSpace vpfes(&pmesh, &fec, DIM);
const int tvsize = vpfes.GetTrueVSize();
ParGridFunction x(&vpfes), y(&vpfes), z(&vpfes);
Vector X(tvsize), Y(tvsize), Z(tvsize), dZ(tvsize);
X.Randomize(1);
x.SetFromTrueDofs(X);
ConstantCoefficient one(1.0), zero(0.0);
ParBilinearForm blf(&vpfes);
blf.AddDomainIntegrator(new VectorMassIntegrator(one, ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
static constexpr int U = 0, Coords = 1;
const auto in_fds = std::vector
{
FieldDescriptor{ U, &vpfes },
FieldDescriptor{ Coords, mfes }
};
const auto out_fds = std::vector{ FieldDescriptor{ U, &vpfes } };
Vector N;
nodes->GetTrueDofs(N);
using IT = Inputs<Value<U>, Gradient<Coords>, Weight>;
using OT = Outputs<Value<U>>;
SECTION("Vector Mass Action")
{
blf.Mult(x, y);
vpfes.GetProlongationMatrix()->MultTranspose(y, Y);
DifferentiableOperator dop(in_fds, out_fds, pmesh);
local_mf_vector_mass_qf<DIM> local_qfn;
dop.AddDomainIntegrator<LocalQFBackend>(
local_qfn, IT {}, OT {}, *ir, all_domain_attr);
MultiVector MX{X, N}, MZ{Z};
dop.Mult(MX, MZ);
Y -= Z;
y.SetFromTrueDofs(Y);
REQUIRE(y.ComputeMaxError(zero) == MFEM_Approx(0.0));
}
}
// ────────────────────────────────────────────────────────────────────────────
template <int DIM>
void mass_mat_mixed(const char* filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto* nodes = static_cast<ParGridFunction*>(pmesh.GetNodes());
smesh.Clear();
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const auto* ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
H1_FECollection fec0(p, DIM);
H1_FECollection fec1(p + 1, DIM);
ParFiniteElementSpace fes0(&pmesh, &fec0);
ParFiniteElementSpace fes1(&pmesh, &fec1);
const auto* ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
ConstantCoefficient one(1.0);
ParMixedBilinearForm blf(&fes1, &fes0);
blf.AddDomainIntegrator(new MassIntegrator(one, ir));
blf.SetAssemblyLevel(AssemblyLevel::FULL);
blf.Assemble();
blf.Finalize();
blf.SpMat().Finalize();
static constexpr int U = 0, P = 1, Coords = 2;
const auto sol = std::vector{FieldDescriptor{U, &fes1}};
DifferentiableOperator dop(sol, {{P, &fes0}, {Coords, nodes->ParFESpace()}},
pmesh);
const auto mf_mass_qf = [] MFEM_HOST_DEVICE(
const dscalar_t& u,
const tensor<real_t, DIM, DIM>& J,
const real_t& w)
DifferentiableOperator dop(
{ { U, &fes1 }, { Coords, mfes } },
{ { P, &fes0 } }, pmesh);
local_mf_mass_qf<DIM> local_qfn;
dop.AddDomainIntegrator<LocalQFBackend>(
local_qfn,
Inputs<Value<U>, Gradient<Coords>, Weight> {},
Outputs<Value<P>> {},
*ir, all_domain_attr,
Derivatives<U> {});
ParGridFunction ugf(&fes1), pgf(&fes0);
ugf = 0.0, pgf = 0.0;
Vector xtvec(fes1.GetTrueVSize()), ytvec(fes0.GetTrueVSize());
Vector nodestv;
xtvec.Randomize(1);
ugf.SetFromTrueDofs(xtvec);
nodes->GetTrueDofs(nodestv);
fes1.GetRestrictionMatrix()->Mult(ugf, xtvec);
MultiVector X{xtvec, nodestv};
auto ddopdu = dop.GetDerivative(U, X, false);
SECTION("Action Linearized")
{
return tuple{u * w * det(J)};
};
xtvec.Randomize(567);
ugf.SetFromTrueDofs(xtvec);
auto derivatives = std::integer_sequence<size_t, U> {};
dop.AddDomainIntegrator(mf_mass_qf,
tuple{Value<U>{}, Gradient<Coords>{}, Weight{}},
tuple{Value<P>{}},
*ir, all_domain_attr, derivatives);
Vector dztvec(fes0.GetTrueVSize());
MultiVector DZ{dztvec};
ddopdu->Mult(X[0], DZ);
ParGridFunction ugf(&fes1);
ugf = 0.0;
blf.Mult(ugf, pgf);
fes0.GetProlongationMatrix()->MultTranspose(pgf, ytvec);
ParGridFunction pgf(&fes0);
pgf = 0.0;
ytvec -= dztvec;
dop.SetParameters({&pgf, nodes});
real_t norm_global = 0.0;
real_t norm_local = ytvec.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
auto ddopdu = dop.GetDerivative(U, {&ugf}, {&pgf, nodes});
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("spmat")
SECTION("MFEM SparseMatrix")
{
SparseMatrix *A;
ddopdu->Assemble(A);
@@ -224,7 +489,8 @@ template <int DIM> void mass_mat_mixed(const char* filename, int p)
delete A;
}
SECTION("hypre parallel mat")
// TODO Hypre parallel matrix
if constexpr(false && !mfem_use_gpu)
{
HypreParMatrix *Amfem = blf.ParallelAssemble();
@@ -236,39 +502,52 @@ template <int DIM> void mass_mat_mixed(const char* filename, int p)
}
}
// no GPU tag to avoid failing 'hypre parallel mat' section
TEST_CASE("dFEM Mass", "[Parallel][dFEM]")
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM Mass 2D", "[Parallel][dFEM][GPU]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = GenAll({1}, {2, 3});
const auto meshs = { "../../data/inline-quad.mesh" };
const auto extra = { "../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/periodic-square.mesh"
};
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
SECTION("2d")
SECTION("Scalar Mass")
{
const auto filename2d =
GENERATE(
"../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh"
);
mass_action<2>(filename2d, p);
mass_mat_mixed<2>(filename2d, p);
mass_action<2>(GenAll(meshs, extra), p);
}
SECTION("3d")
SECTION("Scalar Mass Mixed")
{
const auto filename3d =
GENERATE(
"../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
);
mass_action<3>(filename3d, p);
mass_mat_mixed<3>(filename3d, p);
mass_mat_mixed<2>(GenAll(meshs, extra), p);
}
SECTION("Vector Mass")
{
vector_mass_action<2>(GenAll(meshs, extra), p);
}
}
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM Mass 3D", "[Parallel][dFEM][GPU]")
{
const auto p = GenAll({1}, {2, 3});
const auto meshs = { "../../data/inline-hex.mesh" };
const auto extra = { "../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh"
};
SECTION("Scalar Mass")
{
mass_action<3>(GenAll(meshs, extra), p);
}
SECTION("Scalar Mass Mixed")
{
mass_mat_mixed<3>(GenAll(meshs, extra), p);
}
SECTION("Vector Mass")
{
vector_mass_action<3>(GenAll(meshs, extra), p);
}
}
+213
View File
@@ -0,0 +1,213 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
using namespace mfem;
using namespace mfem::future;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using dscalar_t = dual<real_t, real_t>;
#endif
// ────────────────────────────────────────────────────────────────────────────
template<int DIM, typename ValSeq, typename GradSeq>
struct multiply_inputs_qf;
template<int DIM, std::size_t... Vs, std::size_t... Gs>
struct multiply_inputs_qf<DIM, std::index_sequence<Vs...>,
std::index_sequence<Gs...>>
{
template<std::size_t>
using value_t = const dscalar_t &;
template<std::size_t>
using gradient_t = const tensor<dscalar_t, DIM> &;
inline MFEM_HOST_DEVICE void operator()(
const tensor<real_t, DIM, DIM> &J,
value_t<Vs>... vs,
gradient_t<Gs>... gs,
const real_t &w,
dscalar_t &v) const
{
v = (vs * ...) * (real_t{1} + (w - w) * (gs(0) + ...)) * w * det(J);
}
};
template<int DIM, std::size_t Nv, std::size_t Ng>
using multiply_inputs_qf_t =
multiply_inputs_qf<DIM,
std::make_index_sequence<Nv>,
std::make_index_sequence<Ng>>;
// ────────────────────────────────────────────────────────────────────────────
template<int I>
using Values = Value<I>;
template<int I>
using Gradients = Gradient<I>;
template<int... Vs, int... Gs>
constexpr auto make_inputs(std::integer_sequence<int, Vs...>,
std::integer_sequence<int, Gs...>)
{
return Inputs<Gradient<0>, Values<1 + Vs>...,
Gradients<1 + sizeof...(Vs) + Gs>..., Weight> {};
}
// ────────────────────────────────────────────────────────────────────────────
struct InputsTestContext
{
ParMesh &pmesh;
ParFiniteElementSpace &pfes;
ParFiniteElementSpace *mfes;
const IntegrationRule &ir;
Array<int> &all_domain_attr;
const Vector &Y_ref;
Vector &N_vec;
Vector &input;
int tvsize;
};
// ────────────────────────────────────────────────────────────────────────────
template<int DIM, std::size_t Nv, std::size_t Ng>
void test_nv_ng_inputs(const InputsTestContext &ctx)
{
constexpr std::size_t Ni = Nv + Ng;
// inputs & output
std::vector<FieldDescriptor> in_fds;
in_fds.reserve(Ni + 1);
in_fds.emplace_back(0, ctx.mfes); // Coords
for (std::size_t i = 1; i <= Ni; ++i)
{
in_fds.emplace_back(i, &ctx.pfes);
}
const auto out_fds = std::vector{ FieldDescriptor{ 1, &ctx.pfes } };
// Prepare the MultiVector inputs
Array<int> mx_sizes(Ni + 1);
mx_sizes[0] = ctx.N_vec.Size(); // Coords
for (std::size_t i = 1; i <= Ni; ++i) { mx_sizes[i] = ctx.tvsize; }
MultiVector MX;
MX.SetSizes(mx_sizes);
MX.MakeRef(0, ctx.N_vec);
for (std::size_t i = 1; i <= Ni; ++i)
{
MX.MakeRef(static_cast<int>(i), ctx.input);
}
using IT = decltype(make_inputs(
std::make_integer_sequence<int, Nv> {},
std::make_integer_sequence<int, Ng> {}));
using OT = Outputs<Value<1>>;
multiply_inputs_qf_t<DIM, Nv, Ng> qfn;
DifferentiableOperator dop(in_fds, out_fds, ctx.pmesh);
dop.AddDomainIntegrator<LocalQFBackend>(
qfn, IT{}, OT{}, ctx.ir, ctx.all_domain_attr);
Vector Z(ctx.tvsize);
MultiVector MZ{ Z };
dop.Mult(MX, MZ);
Vector Y_diff(ctx.Y_ref);
Y_diff -= Z;
ParGridFunction y(&ctx.pfes);
ConstantCoefficient zero(0.0);
y.SetFromTrueDofs(Y_diff);
REQUIRE(y.ComputeMaxError(zero) == MFEM_Approx(0.0));
}
// ────────────────────────────────────────────────────────────────────────────
void test_multiple_inputs(int p)
{
static constexpr int DIM = 2;
CAPTURE(DIM, p);
Mesh smesh("../../data/inline-quad.mesh");
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
smesh.Clear();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const auto &ir = IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
H1_FECollection fec(p, DIM);
ParFiniteElementSpace pfes(&pmesh, &fec);
const int tvsize = pfes.GetTrueVSize();
ParGridFunction x(&pfes), y(&pfes);
Vector X_ref(tvsize), Y_ref(tvsize);
X_ref = 1.0;
x.SetFromTrueDofs(X_ref);
ConstantCoefficient one(1.0);
ParBilinearForm blf(&pfes);
blf.AddDomainIntegrator(new MassIntegrator(one, &ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y_ref);
Vector N_vec;
nodes->GetTrueDofs(N_vec);
Vector input(tvsize);
input = 1.0;
const InputsTestContext ctx
{
pmesh, pfes, mfes, ir, all_domain_attr, Y_ref, N_vec, input, tvsize
};
// with current dFEM tuple: max 9 = J + 3 + 3 + weights + output
#ifndef _WIN32
test_nv_ng_inputs<DIM, 3, 3>(ctx);
#else
// avoiding 'number of sections exceeded object file format limit' error
test_nv_ng_inputs<DIM, 2, 2>(ctx);
#endif // _WIN32
}
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM Inputs", "[Parallel][dFEM][GPU]")
{
const auto p = GenAll({1}, {2, 3, 8});
test_multiple_inputs(p);
}
#endif // MFEM_USE_MPI
+472
View File
@@ -0,0 +1,472 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include <memory>
#include "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
#include "../../../linalg/tensor_arrays.hpp"
using namespace mfem;
using namespace mfem::future;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using dscalar_t = dual<real_t, real_t>;
#endif
constexpr int DIM = 2;
class DummyParameterSpace : public ParameterSpace
{
public:
class Bimpl : public Operator
{
void Mult(const Vector &x, Vector &y) const override
{
for (int i = 0; i < y.Size(); i++)
{
y(i) = x(0);
}
}
};
class Btimpl : public Operator
{
void Mult(const Vector &x, Vector &y) const override
{
y(0) = x(0);
}
};
DummyParameterSpace() : ParameterSpace(1) {}
int GetTrueVSize() const override
{
return 1;
}
int GetVSize() const override
{
return 1;
}
const Operator* GetB() const override
{
if (!B)
{
B = std::make_unique<Bimpl>();
}
return B.get();
}
const Operator* GetBt() const override
{
if (!Bt)
{
Bt = std::make_unique<Btimpl>();
}
return Bt.get();
}
};
/*struct mass_global_qf
{
void operator()(
tensor_array<const dscalar_t> &u,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t> &w,
tensor_array<dscalar_t> &out1,
tensor_array<dscalar_t> &out2) const
{
mfem::forall(u.size(), [=] MFEM_HOST_DEVICE (int q)
{
const auto v = u(q) * det(J(q)) * w(q);
out1(q) = v;
out2(q) = v;
});
}
};*/
struct mass_diffusion_global_qf
{
void operator()(
tensor_array<const dscalar_t> &u,
tensor_array<const dscalar_t, DIM> &dudxi,
tensor_array<const real_t, DIM, DIM> &J,
[[maybe_unused]] tensor_array<const real_t, DIM, DIM> &qdata,
tensor_array<const real_t> &w,
[[maybe_unused]] tensor_array<const real_t> &dummy_parameter,
tensor_array<dscalar_t> &out1,
tensor_array<dscalar_t, DIM> &out2,
tensor_array<real_t, DIM, DIM> &out3) const
{
mfem::forall(u.size(), [=] MFEM_HOST_DEVICE (int q)
{
const auto invJq = inv(J(q));
const auto detJq = det(J(q));
const real_t weight = detJq * w(q);
out1(q) = u(q) * weight;
out2(q) = (dudxi(q) * invJq) * transpose(invJq) * (detJq * w(q));
out3(q) = J(q);
});
}
};
struct mass_local_qf
{
inline MFEM_HOST_DEVICE
void operator()(
const dscalar_t &u,
const tensor<real_t, DIM, DIM> &J,
const real_t &w,
dscalar_t &out1,
dscalar_t &out2) const
{
const auto v = u * det(J) * w;
out1 = v;
out2 = v;
}
};
struct mass_diffusion_local_qf
{
inline MFEM_HOST_DEVICE
void operator()(
const real_t &u,
const tensor<real_t, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w,
real_t &out1,
tensor<real_t, DIM> &out2) const
{
const auto invJ = inv(J);
const auto detJ = det(J);
out1 = u * detJ * w;
out2 = (dudxi * invJ) * transpose(invJ) * (detJ * w);
}
};
TEST_CASE("dFEM Multiple Outputs", "[Parallel][dFEM][GPU]")
{
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);
VectorQuadratureSpace vqs(qs, DIM * DIM);
QuadratureFunction qdata(vqs);
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());
static constexpr int U = 0, COORDINATES = 1, V = 2, S = 3, L = 4;
{
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);
const std::vector<FieldDescriptor> in_fds
{
{U, &fes},
{COORDINATES, nodes->ParFESpace()},
{S, &vqs},
{L, &dps}
};
const std::vector<FieldDescriptor> out_fds
{
{V, &fes},
{S, &vqs}
};
DifferentiableOperator dop(in_fds, out_fds, pmesh);
dop.SetQLayouts({{Value<U>{}, {1, 0}}}, {});
auto derivatives = Derivatives<U> {};
auto mass_diffusion_qfunc = mass_diffusion_global_qf{};
dop.AddDomainIntegrator(
mass_diffusion_qfunc,
Inputs<Value<U>, Gradient<U>, Gradient<COORDINATES>, Identity<S>, Weight, Value<L>> {},
Outputs<Value<V>, Gradient<V>, Identity<S>> {},
*ir, all_domain_attr, derivatives);
fes.GetRestrictionMatrix()->Mult(x, xtvec);
dop.Mult(X, Z);
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];
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);
}
{
static constexpr int W = 0;
ParBilinearForm blf(&fes);
blf.AddDomainIntegrator(new MassIntegrator(ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
fes.GetProlongationMatrix()->MultTranspose(y, ytvecmfem);
const std::vector<FieldDescriptor> in_fds
{
{U, &fes},
{COORDINATES, nodes->ParFESpace()},
};
const std::vector<FieldDescriptor> out_fds
{
{V, &fes},
{W, &fes},
};
DifferentiableOperator dop(in_fds, out_fds, pmesh);
auto mass_qfunclocal = mass_local_qf{};
dop.AddDomainIntegrator<LocalQFBackend>(
mass_qfunclocal,
tuple{Value<U>{}, Gradient<COORDINATES>{}, Weight{}},
tuple{Value<V>{}, Value<W>{}},
*ir, all_domain_attr);
Vector nodestv;
nodes->GetTrueDofs(nodestv);
fes.GetRestrictionMatrix()->Mult(x, xtvec);
Vector ztvec(xtvec.Size());
Vector zztvec(xtvec.Size());
MultiVector X{xtvec, nodestv};
MultiVector Z{ztvec, zztvec};
dop.Mult(X, Z);
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));
Vector Y1(ytvecmfem);
Y1 -= Z[1];
norm_l = Y1.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);
}
{
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);
const std::vector<FieldDescriptor> in_fds
{
{U, &fes},
{COORDINATES, nodes->ParFESpace()},
};
const std::vector<FieldDescriptor> out_fds
{
{V, &fes},
};
DifferentiableOperator dop(in_fds, out_fds, pmesh);
auto mass_diffusion_qfunclocal = mass_diffusion_local_qf{};
dop.AddDomainIntegrator<LocalQFBackend>(
mass_diffusion_qfunclocal,
tuple{Value<U>{}, Gradient<U>{}, Gradient<COORDINATES>{}, Weight{}},
tuple{Value<V>{}, Gradient<V>{}},
*ir, all_domain_attr);
Vector nodestv;
nodes->GetTrueDofs(nodestv);
fes.GetRestrictionMatrix()->Mult(x, xtvec);
Vector ztvec(xtvec.Size());
MultiVector X{xtvec, nodestv};
MultiVector Z{ztvec};
dop.Mult(X, Z);
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
+430
View File
@@ -0,0 +1,430 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "mfem.hpp"
//#ifdef MFEM_USE_ENZYME
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
#include "../../../fem/dfem/backends/local_qf/revdiff_transformer.hpp"
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using mfem::future::dual;
using dscalar_t = dual<real_t, real_t>;
#endif
namespace second_derivative_test
{
template <typename dscalar_t, int dim>
struct MinimalSurfaceEnergyFunctional
{
MFEM_HOST_DEVICE inline __attribute__((always_inline))
auto operator()(const dscalar_t &u,
const tensor<dscalar_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
dscalar_t &f /* dfdu, dfddudxi */
) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto dx = det(J) * w;
const auto E = sqrt(1.0_r + sqnorm(dudx));
f = E * dx;
}
};
template <typename dscalar_t, int dim>
struct MinimalSurfaceEnergy
{
MFEM_HOST_DEVICE inline __attribute__((always_inline))
auto operator()(const tensor<dscalar_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
dscalar_t &f) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto dx = det(J) * w;
const auto E = sqrt(1.0_r + sqnorm(dudx));
f = E * dx;
}
};
template <typename dscalar_t, int dim>
struct MinimalSurfaceResidual
{
MFEM_HOST_DEVICE inline __attribute__((always_inline))
auto operator()(const tensor<dscalar_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
tensor<dscalar_t, dim> &dvdx) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto dx = det(J) * w;
dvdx = dudx / (sqrt(1.0_r + sqnorm(dudx))) * transpose(invJ) * dx;
}
};
// Hand-coded action of the second derivative of the minimal surface energy,
// i.e. the Hessian-vector product integrand d^2 J(u)[delta_u, v].
template <typename dscalar_t, int dim>
struct MinimalSurfaceHessianAction
{
MFEM_HOST_DEVICE inline __attribute__((always_inline))
auto operator()(const tensor<real_t, dim> &ddelta_udxi,
const tensor<dscalar_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w,
tensor<real_t, dim> &dvdx) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto ddelta_udx = ddelta_udxi * invJ;
const auto dx = det(J) * w;
const auto c = 1.0_r / sqrt(1.0_r + sqnorm(dudx));
const auto term1 = c * ddelta_udx;
const auto term2 = c * c * c * dot(dudx, ddelta_udx) * dudx;
dvdx = (term1 - term2) * transpose(invJ) * dx;
}
};
template <int dim>
class MyFunctional
{
static constexpr int U = 0, Coords = 1, Q = 2, DirU = 3;
public:
MyFunctional(const ParFiniteElementSpace &fes,
const ParFiniteElementSpace &mfes,
const IntegrationRule &ir) :
comm(fes.GetComm()),
mesh(*mfes.GetParMesh()),
qspace(*fes.GetParMesh(), ir),
qspace_vec(qspace, 1),
q(qspace_vec)
{
const auto &pmesh = *fes.GetParMesh();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
// Energy
{
const auto in = std::vector
{
FieldDescriptor{U, &fes},
FieldDescriptor{Coords, &mfes}
};
const auto out = std::vector
{
FieldDescriptor{Q, &qspace_vec}
};
functional_dop = std::make_unique<DifferentiableOperator>(in, out, mesh);
MinimalSurfaceEnergyFunctional<dscalar_t, dim> energy;
auto derivatives = std::integer_sequence<size_t, U> {};
auto second_derivatives = std::integer_sequence<size_t, U> {};
functional_dop->AddDomainIntegrator<LocalQFBackend, true>(
energy,
Inputs<Value<U>, Gradient<U>, Gradient<Coords>, Weight> {},
Outputs<Identity<Q>> {}, /* Value<U>, Gradient<U> */
ir, all_domain_attr, derivatives /* , second_derivatives */);
}
// Manually computed residual
{
const auto in = std::vector
{
FieldDescriptor{U, &fes},
FieldDescriptor{Coords, &mfes}
};
const auto out = std::vector
{
FieldDescriptor{U, &fes}
};
residual_dop = std::make_unique<DifferentiableOperator>(in, out, pmesh);
MinimalSurfaceResidual<dscalar_t, dim> residual;
auto derivatives = std::integer_sequence<size_t, U> {};
residual_dop->AddDomainIntegrator<LocalQFBackend>(
residual,
tuple{Gradient<U>{}, Gradient<Coords>{}, Weight{}},
tuple{Gradient<U>{}},
ir, all_domain_attr, derivatives);
}
// Differentiated energy representing the residual
{
const auto in = std::vector
{
FieldDescriptor{U, &fes},
FieldDescriptor{Coords, &mfes}
};
const auto out = std::vector
{
FieldDescriptor{U, &fes}
};
dfunctional_dop = std::make_unique<DifferentiableOperator>(in, out, mesh);
// Differentiate output f (argument 3) with respect to dudxi
// (argument 0).
RevDiff<MinimalSurfaceEnergy<dscalar_t, dim>, tuple<Active, Const, Const>, tuple<Active>, RevDiffDualMode::Derivative>
fd;
auto derivatives = std::integer_sequence<size_t, U> {};
dfunctional_dop->AddDomainIntegrator<LocalQFBackend>(
fd,
tuple{Gradient<U>{}, Gradient<Coords>{}, Weight{}},
tuple{Gradient<U>{}},
ir, all_domain_attr, derivatives);
}
// Hand-coded Hessian action with the direction as an explicit field
{
const auto in = std::vector
{
FieldDescriptor{DirU, &fes},
FieldDescriptor{U, &fes},
FieldDescriptor{Coords, &mfes}
};
const auto out = std::vector
{
FieldDescriptor{U, &fes}
};
hessian_dop = std::make_unique<DifferentiableOperator>(in, out, mesh);
MinimalSurfaceHessianAction<real_t, dim> hessian_action;
hessian_dop->AddDomainIntegrator<LocalQFBackend>(
hessian_action,
tuple{Gradient<DirU>{}, Gradient<U>{}, Gradient<Coords>{}, Weight{}},
tuple{Gradient<U>{}},
ir, all_domain_attr);
}
mesh.GetNodes()->GetTrueDofs(coords);
}
void gradient_exact(const Vector &u, Vector &g) const
{
MultiVector X{u, coords};
MultiVector Y{g};
residual_dop->Mult(X, Y);
}
void gradient(const Vector &u, Vector &g) const
{
MultiVector X{u, coords};
MultiVector Y{g};
functional_dop->GetDerivative(U)->Mult(X, Y);
}
// Hessian-vector product H(u) v with the hand-coded second derivative.
void hvp_exact(const Vector &u, const Vector &v, Vector &Hv) const
{
MultiVector X{v, u, coords};
MultiVector Y{Hv};
hessian_dop->Mult(X, Y);
}
// H(u) v as the derivative of the hand-coded residual (single AD).
void hvp_dresidual(const Vector &u, const Vector &v, Vector &Hv) const
{
MultiVector X{u, coords};
MultiVector Y{Hv};
residual_dop->GetDerivative(U, X)->Mult(v, Y);
}
// H(u) v as the derivative of the differentiated energy
// (forward-over-reverse AD).
void hvp(const Vector &u, const Vector &v, Vector &Hv) const
{
MultiVector X{u, coords};
MultiVector Y{Hv};
dfunctional_dop->GetDerivative(U, X)->Mult(v, Y);
}
// H(u) v from the functional's second-derivative interface.
void hvp_functional(const Vector &u, const Vector &v, Vector &Hv) const
{
MultiVector X{u, coords};
MultiVector Y{Hv};
functional_dop->GetSecondDerivative(U, X)->Mult(v, Y);
}
private:
MPI_Comm comm;
ParMesh &mesh;
std::unique_ptr<DifferentiableOperator> functional_dop;
std::unique_ptr<DifferentiableOperator> dfunctional_dop;
std::unique_ptr<DifferentiableOperator> residual_dop;
std::unique_ptr<DifferentiableOperator> hessian_dop;
QuadratureSpace qspace;
VectorQuadratureSpace qspace_vec;
mutable QuadratureFunction q;
Vector coords;
};
template <int DIM>
void second_derivative(const char *filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
ParFiniteElementSpace *mfes = nodes->ParFESpace();
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
const IntegrationRule &ir =
IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
ParGridFunction u_gf(&fes);
FunctionCoefficient u_coeff(
[](const auto &x)
{
return 2_r * M_PI * x[0] * x[0] * 2_r * M_PI * x[1] * x[1];
});
u_gf.ProjectCoefficient(u_coeff);
Vector u(fes.GetTrueVSize());
u_gf.GetTrueDofs(u);
MyFunctional<DIM> functional(fes, *mfes, ir);
Vector exact_g(fes.GetTrueVSize());
functional.gradient_exact(u, exact_g);
Vector g(fes.GetTrueVSize());
functional.gradient(u, g);
Vector diff(g);
diff -= exact_g;
REQUIRE(diff.Norml2() < 1e-12);
// Direction for the Hessian-vector product
ParGridFunction v_gf(&fes);
FunctionCoefficient v_coeff(
[](const auto &x)
{
return sin(M_PI * x[0]) * cos(M_PI * x[1]) + 0.5_r * x[0] * x[1];
});
v_gf.ProjectCoefficient(v_coeff);
Vector v(fes.GetTrueVSize());
v_gf.GetTrueDofs(v);
Vector exact_Hv(fes.GetTrueVSize());
functional.hvp_exact(u, v, exact_Hv);
Vector Hv_dres(fes.GetTrueVSize());
functional.hvp_dresidual(u, v, Hv_dres);
diff = Hv_dres;
diff -= exact_Hv;
REQUIRE(MFEM_Approx(diff.Norml2()) == 0.0);
Vector Hv(fes.GetTrueVSize());
functional.hvp(u, v, Hv);
diff = Hv;
diff -= exact_Hv;
REQUIRE(MFEM_Approx(diff.Norml2()) == 0.0);
Vector Hv_functional(fes.GetTrueVSize());
functional.hvp_functional(u, v, Hv_functional);
diff = Hv_functional;
diff -= exact_Hv;
REQUIRE(MFEM_Approx(diff.Norml2()) == 0.0);
mfem::out << "Hessian-vector (functional) product norm: " << Hv_functional.Norml2() << std::endl;
mfem::out << "Hessian-vector (hand-coded residual) product norm: " << Hv_dres.Norml2() << std::endl;
mfem::out << "Hessian-vector (forward over reverse) product norm: " << Hv.Norml2() << std::endl;
mfem::out << "Exact Hessian-vector product norm: " << exact_Hv.Norml2() << std::endl;
// std::cout << "Gradient using FwdDiff<f>:\n";
// pretty_print(g);
// std::cout << "Handcoded gradient of f(u):\n";
// pretty_print(exact_g);
// std::cout << "Handcoded H(u) v:\n";
// pretty_print(exact_Hv);
// std::cout << "H(u) v using the derivative of the handcoded residual:\n";
// pretty_print(Hv_dres);
// std::cout << "H(u) v using the derivative of FwdDiff<f>:\n";
// pretty_print(Hv);
}
} // namespace second_derivative_test
TEST_CASE("dFEM functional second derivative action matches mfem",
"[Parallel][dFEM][second-derivative]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 1 : GENERATE(1, 2, 3);
SECTION("2d")
{
const auto f =
GENERATE(
// "../../data/star.mesh",
// "../../data/star-q3.mesh",
// "../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh"
// "../../data/periodic-square.mesh"
);
second_derivative_test::second_derivative<2>(f, p);
}
// SECTION("3d")
// {
// const auto f =
// GENERATE(
// "../../data/fichera-q3.mesh",
// "../../data/inline-hex.mesh",
// "../../data/toroid-hex.mesh",
// "../../data/periodic-cube.mesh"
// );
// second_derivative_test::second_derivative<3>(f, p);
// }
}
#endif // MFEM_USE_MPI
//#endif // MFEM_USE_ENZYME
+173
View File
@@ -0,0 +1,173 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
#include "../../../fem/dfem/doperator.hpp"
#include "../../../fem/dfem/backends/local_qf/prelude.hpp"
using namespace mfem;
using namespace mfem::future;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using dscalar_t = dual<real_t, real_t>;
#endif
// ────────────────────────────────────────────────────────────────────────────
namespace
{
static constexpr int DIM = 2;
struct VectorValueCopy
{
MFEM_HOST_DEVICE inline
void operator()(const tensor<real_t, DIM> &x,
tensor<real_t, DIM> &y) const
{
y(0) = x(0);
y(1) = x(1);
}
};
struct VectorMassCopy
{
MFEM_HOST_DEVICE inline
void operator()(const tensor<real_t, DIM> &x,
const tensor<real_t, DIM, DIM> &J,
const real_t &w,
tensor<real_t, DIM> &y) const
{
y = x * w * det(J);
}
};
}
// ────────────────────────────────────────────────────────────────────────────
void test_vqspace_identity_copy(int order)
{
Mesh serial_mesh =
Mesh::MakeCartesian2D(1, 1, Element::QUADRILATERAL, true, 1.0, 1.0);
ParMesh pmesh(MPI_COMM_WORLD, serial_mesh);
const IntegrationRule &ir = IntRules.Get(Geometry::SQUARE, 2 * order + 1);
QuadratureSpace qspace(pmesh, ir);
VectorQuadratureSpace vqspace(qspace, DIM);
const auto qvsize = vqspace.GetVSize();
Array<int> domain_attr(pmesh.attributes.Max());
domain_attr = 1;
static constexpr int U = 0, V = 1;
const std::vector fdi { FieldDescriptor{U, &vqspace} };
const std::vector fdo { FieldDescriptor{V, &vqspace} };
DifferentiableOperator dop(fdi, fdo, pmesh);
VectorValueCopy qfunc;
dop.AddDomainIntegrator<LocalQFBackend>(
qfunc,
Inputs<Identity<U>> {},
Outputs<Identity<V>> {},
ir, domain_attr);
Vector vX(qvsize), vY(qvsize);
vX.Randomize(0x9e3779b9);
vY = 0.0;
MultiVector Xmv{vX}, Ymv{vY};
dop.Mult(Xmv, Ymv);
Vector diff(vY);
diff -= vX;
REQUIRE(diff.Normlinf() == MFEM_Approx(0.0));
}
// ────────────────────────────────────────────────────────────────────────────
void test_value_vector_mass(int order)
{
Mesh serial_mesh =
Mesh::MakeCartesian2D(1, 1, Element::QUADRILATERAL, true, 1.0, 1.0);
ParMesh pmesh(MPI_COMM_WORLD, serial_mesh);
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
const auto *nfes = nodes->ParFESpace();
H1_FECollection fec(order, DIM);
ParFiniteElementSpace fes(&pmesh, &fec, DIM, Ordering::byVDIM);
const auto tvsize = fes.GetTrueVSize();
Array<int> domain_attr(pmesh.attributes.Max());
domain_attr = 1;
const IntegrationRule &ir = IntRules.Get(Geometry::SQUARE, 2 * order + 1);
ConstantCoefficient one(1.0), zero(0.0);
ParGridFunction input_gf(&fes);
ParGridFunction reference_gf(&fes);
ParGridFunction error_gf(&fes);
Vector vX(tvsize), vY_ref(tvsize), vY(tvsize), N(nfes->GetTrueVSize());
vX.Randomize(0x9e3779b9);
input_gf.SetFromTrueDofs(vX);
nodes->GetTrueDofs(N);
vY = 0.0;
ParBilinearForm blf(&fes);
blf.AddDomainIntegrator(new VectorMassIntegrator(one, &ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(input_gf, reference_gf);
fes.GetProlongationMatrix()->MultTranspose(reference_gf, vY_ref);
static constexpr int U = 0, V = 1, Coords = 2;
const std::vector fdi { FieldDescriptor{U, &fes}, FieldDescriptor{Coords, nfes} };
const std::vector fdo { FieldDescriptor{V, &fes} };
DifferentiableOperator dop(fdi, fdo, pmesh);
VectorMassCopy qfunc;
dop.AddDomainIntegrator<LocalQFBackend>(
qfunc,
Inputs<Value<U>, Gradient<Coords>, Weight> {},
Outputs<Value<V>> {},
ir, domain_attr);
MultiVector Xmv{vX, N}, Ymv{vY};
dop.Mult(Xmv, Ymv);
vY_ref -= vY;
error_gf.SetFromTrueDofs(vY_ref);
REQUIRE(error_gf.ComputeMaxError(zero) == MFEM_Approx(0.0));
}
// ────────────────────────────────────────────────────────────────────────────
TEST_CASE("dFEM TMOP", "[Parallel][dFEM][GPU]")
{
const auto p = GenAll({1}, {2, 3});
SECTION("VectorQuadratureSpace identity copy")
{
test_vqspace_identity_copy(p);
}
SECTION("Value vector mass matches PA MFEM")
{
test_value_vector_mass(p);
}
}
#endif // MFEM_USE_MPI
+4 -4
View File
@@ -158,7 +158,7 @@ TEST_CASE("Laplacian Eigenvalues",
#endif // MFEM_USE_LAPACK
#ifdef MFEM_USE_MPI
#
TEST_CASE("Laplacian Eigenvalues in Parallel",
"[H1_FECollection]"
"[GridFunction]"
@@ -245,6 +245,9 @@ TEST_CASE("Laplacian Eigenvalues in Parallel",
lobpcg.Solve();
lobpcg.GetEigenvalues(eigenvalues);
delete A;
delete M;
Array<int> exact_eigs(&eigs[7 * (dim - 1)], 7);
real_t max_err = 0.0;
@@ -257,9 +260,6 @@ TEST_CASE("Laplacian Eigenvalues in Parallel",
REQUIRE(err < 5.0);
}
CAPTURE(mt, max_err);
delete A;
delete M;
}
}
+23
View File
@@ -36,4 +36,27 @@ inline Approx MFEM_Approx(double val,
return Approx(val).margin(abs_tol).epsilon(rel_tol);
}
/** @brief Generate values with extra if '--all' option is provided,
otherwise just generate from base. */
struct GenAllFn
{
template <typename Base, typename Extra>
auto operator()(const Base &base, const Extra &extra) const
{
return !launch_all_non_regression_tests
? GENERATE_COPY(from_range(base))
: GENERATE_COPY(from_range(base), from_range(extra));
}
auto operator()(const std::initializer_list<int> &base,
const std::initializer_list<int> &extra) const
{
return !launch_all_non_regression_tests
? GENERATE_REF(from_range(base))
: GENERATE_REF(from_range(base), from_range(extra));
}
};
inline constexpr GenAllFn GenAll {};
#endif