Compare commits

...
Author SHA1 Message Date
Toni-ko 6162df1913 Updates to year on copyright statement on makefiles 2025-08-14 10:19:01 -07:00
Toni-ko 1679967687 Merge with imex-conv-diff-dg implementation of darcy_heat_transfer_ex using imex-conv-diff-dg 2025-08-14 10:15:06 -07:00
Toni-ko b4d6635e0f Merge branch 'imex-conv-diff-dg' into heat_transfer_opt 2025-08-12 11:22:47 -07:00
blaz e5d4917c65 fix memory leaks 2025-08-11 20:23:32 -07:00
blaz 258d8c7dec Merge branch 'master' into imex-conv-diff-dg 2025-08-11 19:13:04 -07:00
Toni-ko e3ce7f2de4 resolving an error 2025-08-11 18:06:20 -07:00
Toni-ko a579e7f6c1 attempting to resolve failing check 2025-08-11 18:02:07 -07:00
Toni-ko e65eae9a59 typo 2025-08-11 17:52:12 -07:00
Toni-ko 93c45b205b code style update 2025-08-11 17:11:35 -07:00
Toni-ko 8805e6cfcf Added Sample Runs. Updated comments. Code cleanup. 2025-08-11 16:22:38 -07:00
Tzanio Kolev 0466ee74f2 Merge pull request #4622 from mfem/hypre-gpu-tweaks
HYPRE and GPU related tweaks
2025-08-09 10:28:25 -07:00
Tzanio Kolev 50b8873f20 Merge pull request #4923 from mfem/hughcars/cuda-hip-compiler-checks
Check for CUDA or HIP compilers
2025-08-09 10:27:54 -07:00
Toni-ko 305dd2e17c example 41 using lor preconditioner 2025-08-08 13:42:17 -07:00
Veselin Dobrev cd7dfe2188 Merge pull request #4973 from mfem/macos-llvm-ci
switch to llvm 20 for CI
2025-08-07 17:55:50 -07:00
Toni-ko 4bc378f84f Merge remote-tracking branch 'origin/lor_dg_preconditioner' into imex-conv-diff-dg 2025-08-07 17:55:21 -07:00
Toni-ko 2ccd455706 Included preconditioner 2025-08-07 15:41:11 -07:00
Andrew Ho 1bf00d3761 switch to llvm20 for CI 2025-08-07 15:18:36 -07:00
Toni-ko f319c14ecc Parallel Version of example 41 works 2025-08-06 09:41:41 -07:00
Veselin Dobrev 47e9b29c0f Restore the MFEM_UNROLL definition -- it is only used in CUDA device code.
Tweak a comment and restore some formatting.
2025-08-05 16:27:15 -07:00
Veselin Dobrev bd7281bdfa Merge pull request #4960 from mfem/fix-warnings-algoim
Algoim compilation warnings
2025-08-05 12:05:40 -07:00
Hugh Carson 6df8ae80cb PR feedback 2025-08-05 11:17:12 -04:00
Tzanio Kolev 6dfbe25c07 Merge pull request #4955 from mfem/clang-perf-flags
In `miniapps/performance/makefile`, adjust the flags for Clang
2025-08-04 07:44:47 -07:00
Tzanio Kolev 02edb95a31 Merge pull request #4957 from farscape-project/conduit
Guarantee data on host when writing conduit files
2025-08-02 09:27:45 -07:00
Toni-ko bb467f97da First draft of the parallel version of ex41. Still some debugging to do. 2025-08-01 16:24:44 -07:00
Toni-ko 479433c871 Added a higher order IMEX Scheme - fixed some typos 2025-07-31 14:25:40 -07:00
Toni-ko 4269b06149 Changed SplitODESolver so that it inherits from ODESolver. Added an RK2 IMEX Method. 2025-07-30 15:27:47 -07:00
Will Pazner 597cba8d37 Merge pull request #4954 from mfem/ruby-60
Extend time allocation for testing on ruby from 45 to 60 minutes
2025-07-29 12:49:00 -07:00
Veselin Dobrev 075a160187 Merge pull request #4827 from mfem/feature/guthrey1/transform_lambda
Allow capturing lambdas for mesh::Transform
2025-07-29 09:34:22 -07:00
Veselin Dobrev 6d8a96a8fb Merge branch 'master' into hypre-gpu-tweaks 2025-07-29 09:08:02 -07:00
Toni-ko b8660af425 Implements SplitTimeDependentOperator class along with supporting methods. Implements an IMEX scheme for ex41. Solutions to ex41 look reasonable. 2025-07-28 16:55:23 -07:00
Vladimir Z Tomov 8d9579057f Simplified to std:vector. 2025-07-28 15:55:21 -07:00
Vladimir Z Tomov 76d65f5866 Fixed compilation warnings when Algoim is on. 2025-07-28 15:39:50 -07:00
Tzanio Kolev 720fb72372 Merge pull request #4884 from adam-sim-dev/gitignore
Fix gitignore for Windows
2025-07-26 10:45:44 -07:00
Nuno Nobre bbde976378 Guarantee data on host when writing conduit files 2025-07-26 01:31:22 +01:00
Will Pazner dc13e67e87 Add placeholder for example 41, DG convection-diffusion
Currently just a cleaned-up version of ex9. Will add diffusion and
IMEX time integration.
2025-07-25 11:24:16 -07:00
Tzanio Kolev 58ad1c79cd Merge pull request #4673 from mfem/cmake-replace-library-files-with-targets
Support fetching of third party libraries using CMake's ExternalProject
2025-07-24 12:23:03 -07:00
Toni-ko ec5460ac7f some bug fixes, included a mesh to use for testing 2025-07-23 18:19:35 -07:00
Veselin Dobrev 447cd0f500 In miniapps/performance/makefile, adjust the flags for Clang 2025-07-22 22:14:35 -07:00
adam-sim-dev 2d68b877f2 Merge branch 'master' into gitignore 2025-07-23 10:01:26 +08:00
Tzanio Kolev cce301f92b Extend time allocation for testing on ruby from 45 to 60 minutes 2025-07-22 12:45:09 -07:00
Toni-ko 90ffae5d63 forward and backward solve using darcy flow field 2025-07-22 10:31:43 -07:00
Veselin Dobrev 46ccaee493 Merge branch 'master' into feature/guthrey1/transform_lambda 2025-07-21 19:36:55 -07:00
Tzanio Kolev 7aa327174a Merge pull request #4811 from mfem/dev-derefine
AMR Matrix-free derefinement operator
2025-07-15 12:22:19 -07:00
adam-sim-dev 8c861b4602 Merge branch 'master' into gitignore 2025-07-15 09:20:33 +08:00
Toni-ko 28a9c1a9d1 initial version of branch 2025-07-11 16:36:21 -07:00
Andrew Ho 2d5f00635f fixed derefinement for ordering byVDIM 2025-07-10 17:00:49 -07:00
Hugh Carson 3f447cb11c Check if a cuda or hip compiler is being used before accessing cuda or hip specific variable 2025-07-07 15:47:32 -04:00
Arturo Vargas e629502dca Merge branch 'master' into dev-derefine 2025-07-07 09:55:58 -07:00
Arturo Vargas 48dd78b154 Merge branch 'master' into dev-derefine 2025-07-03 09:23:21 -07:00
Andrew Ho dc08fc65ce updated changelog 2025-07-01 16:01:38 -07:00
Andrew Ho 3276cb955f Merge branch 'master' into dev-derefine 2025-07-01 15:33:44 -07:00
Andrew Ho bcdbda1597 Merge branch 'master' into dev-derefine 2025-07-01 11:24:42 -07:00
adam-sim-dev 0239b86304 Merge branch 'master' into gitignore 2025-06-30 12:15:37 +08:00
Andrew Ho aca8d51f95 Merge branch 'master' into dev-derefine 2025-06-27 09:25:25 -07:00
Andrew Ho 368724d456 fix missing climit header 2025-06-26 09:30:45 -07:00
Tzanio Kolev a99efcdb63 Merge branch 'master' into dev-derefine 2025-06-26 08:25:48 -07:00
adam-sim-dev b5a7cf6470 Merge branch 'master' into gitignore 2025-06-24 12:11:35 +08:00
Andrew Ho e6fc7893be Merge branch 'master' into dev-derefine 2025-06-16 12:20:13 -07:00
adam-sim-devandJan Nikl 78df8dffb2 Update
Co-authored-by: Jan Nikl <nikl1@llnl.gov>
2025-06-10 05:04:44 +08:00
adam-sim-dev d0f5cb73e7 Fix for Windows 2025-06-09 12:17:58 +08:00
Andrew Ho db55be84ad Merge branch 'master' into dev-derefine 2025-06-02 11:37:29 -07:00
Andrew Ho 091c6272fc Merge branch 'master' into dev-derefine 2025-05-27 11:57:28 -07:00
Andrew Ho 101af72047 Merge branch 'master' into dev-derefine 2025-05-19 12:17:39 -07:00
Will Pazner ea449e1c41 Fix bug in LORBase::AddIntegratorsAndMarkers
Was previously potentially dereferencing null pointer
2025-05-17 22:57:58 -07:00
Will Pazner c9115e74fb Merge remote-tracking branch 'origin/master' into lor_dg_preconditioner 2025-05-16 20:25:22 -07:00
Will Pazner 35040b0336 Remove AMR + DG LOR sample runs
AMR + DG diffusion + PA is not yet implemented
2025-05-16 20:25:12 -07:00
Andrew Ho 01990d4605 don't need _VM 2025-05-13 10:32:01 -07:00
Andrew Ho 4a3cd44695 move tag from VarMessageTag to MessageTag 2025-05-13 04:46:00 -07:00
Andrew Ho 3b059c3995 struct instead of class 2025-05-12 17:21:05 -07:00
Andrew Ho 89dc5ce5c0 parallel version needs to compile to nothing if not building with MPI 2025-05-12 17:19:16 -07:00
Andrew Ho eb205caaa2 fixed collision in ifdef guard 2025-05-12 17:08:39 -07:00
Andrew Ho f50601f1aa fixed using struct vs. class 2025-05-12 16:52:32 -07:00
Andrew Ho 529d6e3bc2 Split deref matrix ops into separate files 2025-05-12 16:34:40 -07:00
Andrew Ho ef29b1dd30 Merge remote-tracking branch 'base/dev-derefine' into dev-derefine 2025-05-12 11:58:50 -07:00
Andrew Ho 33b3ef6045 Enable GPU testing of fespace/pfespace derefinement 2025-05-12 11:40:26 -07:00
Andrew Ho 657f6ef5fe Merge branch 'master' into dev-derefine 2025-05-12 11:19:22 -07:00
Andrew Ho 96cfef783c Review suggestions
- const on some local variables
- use VarMessageTag instead of hard-coded integers for MPI messages
- comment updates
2025-05-12 10:59:23 -07:00
Andrew Ho 603f43e775 updated fes_kernels member variable documentation 2025-05-12 10:47:13 -07:00
AnthonyandWill Pazner 4ce11b4a12 Add comment
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-06 14:16:49 -07:00
Andrew Ho a2e33109eb Merge branch 'master' into dev-derefine 2025-05-02 13:04:12 -06:00
Andrew Ho b277a0cb2d Merge branch 'master' into dev-derefine 2025-05-02 10:29:36 -06:00
Will Pazner f17b1c4e4f real_t instead of int in DG LOR test 2025-04-30 09:06:32 -07:00
Tzanio Kolev 7cfd3f5c7e Merge branch 'master' into lor_dg_preconditioner 2025-04-30 09:00:41 -07:00
Will Pazner 63ea83c455 Add make_permuted_mesh.cpp to unit tests CMakeLists.txt 2025-04-29 08:40:07 -07:00
Will Pazner 38ee46c40f Fix shadow warnings 2025-04-28 21:40:10 -07:00
Will Pazner c271283490 Fix lambda capture
Resolve error: lambda capture 'this' is not used
2025-04-28 19:03:19 -07:00
Will Pazner 808f5c94db In LOR solvers, use fast assembly for RHS only for H1 2025-04-28 17:57:22 -07:00
Will Pazner 287631bee4 Fix averaging of face penalty factor in LOR DG 2025-04-28 15:14:18 -07:00
Will Pazner 4da6e52698 Fix Doxygen typo 2025-04-28 15:14:18 -07:00
Will Pazner cc12d37693 Doxygen documentation for LOR DG 2025-04-28 15:14:18 -07:00
Will Pazner 35fe98417c Update copyright year 2025-04-28 15:14:18 -07:00
Will Pazner 936f1fc848 Parallel LOR+DG assembly 2025-04-28 15:14:18 -07:00
Will Pazner 2cadeaad4c Add ParFiniteElementSpace::GetFaceNbrGlobalDofMapArray
Returning Array instead of pointer allows use on device
2025-04-28 15:14:18 -07:00
Will Pazner f7fa092013 Batched LOR DG GPU fixes
For integers a and b, converting pow(a,b) to integer truncates, which
can cause indexing issues.

For example, in a CUDA kernel, int(pow(3,1)) evaluates to 2.
2025-04-28 15:14:18 -07:00
Will Pazner 9103db88b6 DG+LOR updates 2025-04-28 15:14:18 -07:00
Will Pazner 0303f12887 Minor LOR+DG modifications 2025-04-28 15:14:18 -07:00
Will Pazner 6650a1dffe Factor out FaceIdxToVolIdx 2025-04-28 15:14:18 -07:00
Will Pazner 9a22d4533f Remove matrix output in LOR DG test 2025-04-28 15:14:18 -07:00
Will Pazner 412715c2e4 Factor out face terms in LOR + DG 2025-04-28 15:14:18 -07:00
Will Pazner 9803cbb671 Simplify LOR+DG implementation 2025-04-28 15:14:18 -07:00
Will Pazner 6805b8c7f6 Simplify LOR+DG unit tests 2025-04-28 15:14:18 -07:00
Will Pazner 7aeba0ff83 Adjust DG kappa in LOR solvers miniapp 2025-04-28 15:14:18 -07:00
Will Pazner dbd55a8fb4 Simplified implementation of BatchedLOR_DG::Assemble2D 2025-04-28 15:14:18 -07:00
Toni-ko 277199c3a5 gpu compatiblility 2025-04-28 15:14:18 -07:00
Toni-ko e7e00e6e39 Got rid of shoelace formula for element area and volume computations 2025-04-28 15:14:18 -07:00
Toni-ko e78c1b8b4c passes orientation tests 2025-04-28 15:14:18 -07:00
Will Pazner dcec51b98a Fix permuted test case
(Test still fails)
2025-04-28 15:14:18 -07:00
Will Pazner 2e9f545a4e DG LOR test case
Add commented-out code for testing 3D face permutations
2025-04-28 15:14:18 -07:00
Toni-ko e49854f3ba Passes tests on ref cube, inline-hex, and any version of inline-hex where nx, ny, nz, sx, sy, and/or sz are changed. 2025-04-28 15:14:18 -07:00
Toni-ko 0bb71aa1fa Passes tests on ref-cube, and inline-hex (including when nx, ny, nz or sx, sy, and sz are changed). 2025-04-28 15:14:18 -07:00
Will Pazner 07dc0f6cfa Change mesh permutation function names 2025-04-28 15:14:18 -07:00
Will Pazner 847183e668 Factor out test code for making permuted meshes 2025-04-28 15:14:18 -07:00
Toni-ko 8a1a264eaa some clean-up 2025-04-28 15:14:18 -07:00
Anthony Kolshorn 606a597303 debugging for gpu 2025-04-28 15:14:18 -07:00
Toni-ko e194835abd quick edit 2025-04-28 15:14:18 -07:00
Toni-ko 208f9b81b3 gpu debugging 2025-04-28 15:14:18 -07:00
Will Pazner e50ebb573e Bugfix 2025-04-28 15:14:18 -07:00
Toni-ko c315298a86 debugging 2025-04-28 15:14:18 -07:00
Will Pazner 12bafa69e8 Debugging 2025-04-28 15:14:18 -07:00
Toni-ko 1729961a89 testing 2025-04-28 15:14:18 -07:00
Will Pazner 6dcc19abab Small LOR DG fixes 2025-04-28 15:14:18 -07:00
Toni-ko a5fccc7514 Debugging 2025-04-28 15:14:18 -07:00
Will Pazner cfe25c432c Template for unifying LOR DG matrix assembly 2025-04-28 15:14:18 -07:00
Will Pazner b8fe4cbf97 Get (p+2) Lobatto vertex coordinates for DG LOR 2025-04-28 15:14:18 -07:00
Will Pazner ea0abf46fe Use eta instead of kappa in LOR Batched DG unit test 2025-04-28 15:14:17 -07:00
Will Pazner 556b43f900 Small fixes in BatchedLOR_DG::Assemble2D 2025-04-28 15:14:17 -07:00
Will Pazner aa567465ac Extract kappa in BatchedLOR_DG 2025-04-28 15:14:17 -07:00
Will Pazner 986cee600f make style 2025-04-28 15:14:17 -07:00
Toni-ko 7634a4b663 Correct sparsity Pattern for LOR DG, wrong values 2025-04-28 15:14:17 -07:00
Will Pazner 1368cfb8cb Framework for LOR DG preconditioner assembly 2025-04-28 15:14:17 -07:00
Will Pazner 53c561cbf0 Update fem/CMakeLists.txt with missing headers 2025-04-28 15:14:17 -07:00
Will Pazner 3c2e847e0a Factor out test same matrix unit test utils 2025-04-28 15:14:17 -07:00
Tzanio Kolev ac26026ff1 Merge branch 'master' into cmake-replace-library-files-with-targets 2025-04-26 12:19:30 -07:00
Pierson Guthrey d439781751 resolve ambiguity, thanks @v-dobrev 2025-04-23 18:08:06 -07:00
Pierson Guthrey 16c9123f98 allow lambdas for mesh Transformation 2025-04-23 17:21:16 -07:00
Andrew Ho e73ea1fb77 documentation for parallel version 2025-04-23 12:11:54 -07:00
Andrew Ho ea6c104475 added const and documentation 2025-04-23 12:05:21 -07:00
Andrew Ho e7762d126e Merge branch 'master' into dev-derefine 2025-04-23 09:02:36 -07:00
Andrew Ho c61d0e07d3 switch to using INT_MAX for rows to skip 2025-04-22 16:51:04 -07:00
Andrew Ho 720051d641 unused testing var 2025-04-22 08:55:41 -07:00
Andrew Ho 5c25fa49a7 graceful fallback for HOST_PINNED 2025-04-22 08:50:23 -07:00
Andrew Ho d9ef117ee9 Merge branch 'master' into dev-derefine 2025-04-21 23:21:29 -07:00
Andrew Ho b7a8cf6290 CI fixes 2025-04-21 23:20:43 -07:00
Andrew Ho bed1823d72 unused variables 2025-04-21 23:12:54 -07:00
Andrew Ho 93a3648712 fixed a few performance bugs 2025-04-21 20:12:35 -07:00
Christopher vogl 01eeccee7f updated hypre fetch version; removed unnecessary dependencies on install step; leverage METIS_FETCH_VERSION variable 2025-04-21 15:31:52 -07:00
Christopher vogl 39b6112e69 reverted use of MPI target instead of files 2025-04-21 13:49:18 -07:00
Andrew Ho c70d2b7309 off-diagonals should be implemented now 2025-04-21 00:34:35 -07:00
Andrew Ho c98e8bfe07 Think I have ghost comm data being transferred correctly now 2025-04-20 23:03:10 -07:00
Andrew Ho ef9137bd1f const correctness fix 2025-04-20 22:59:08 -07:00
Andrew Ho 8e0c42a132 fixed a few bugs
still need to finish implementing off-diagonals
2025-04-19 23:04:11 -07:00
Andrew Ho d755037904 Re-arranged DerefineMatrixOpFunctor
Now it's a common implementation with the indexing order separated.

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

Co-authored-by: Nuno Nobre <nuno.nobre@stfc.ac.uk>
2025-02-19 08:42:45 -08:00
Christopher vogl 89f1dce705 added support for fetching hypre with CUDA, HIP, and floats 2025-01-23 10:26:20 -08:00
Christopher vogl 7f17c1f049 see prior message (clearly I need more coffee) 2025-01-22 17:48:30 -08:00
Christopher vogl 7e6edcddec changes that should have been part of prior commit (more commenting) 2025-01-22 17:47:38 -08:00
Christopher vogl d89b01e24c updated commenting of new code 2025-01-22 17:46:04 -08:00
Christopher vogl 33f1150c34 updated FindMETIS to also use ExternalProject 2025-01-22 16:14:09 -08:00
Christopher vogl c6a05d371b fixed capitalization and made fetch directory consistent 2025-01-22 16:13:41 -08:00
Christopher vogl f97a29f6e8 refactored FindHYPRE to use ExternalProject instead of FetchContent 2025-01-22 15:00:06 -08:00
Christopher vogl 85ae72fada changed lib to Lib to be consistent with METIS capitalization 2025-01-21 16:16:30 -08:00
Christopher vogl 27a5bd71ee whitespace 2025-01-21 15:27:14 -08:00
Christopher vogl 643db22bef minor tweaks, commenting, and cleanup 2025-01-21 15:25:18 -08:00
Christopher vogl 97fb6be029 Merge remote-tracking branch 'origin/master' into cmake-replace-library-files-with-targets 2025-01-21 13:41:24 -08:00
Christopher vogl 9587ef212d added fetching of HYPRE and METIS with FETCH_TPLS flag 2025-01-21 13:41:08 -08:00
Christopher vogl 35462b8645 refactored CMakeLists to support targets for TPLs 2025-01-15 13:13:18 -08:00
Veselin Dobrev 4f0f353720 Print error code (in addition to the error string) on CUDA/HIP errors 2024-12-09 18:20:32 -08:00
Veselin Dobrev a3156023d0 Avoid HYPRE initialization via calls to HypreUsingGPU() and
GetHypreMemoryLocation() when running serial codes.
2024-12-09 18:03:01 -08:00
77 changed files with 5370 additions and 3768 deletions
+4 -4
View File
@@ -289,10 +289,10 @@ jobs:
run: |
export HOMEBREW_NO_INSTALL_CLEANUP=1
brew update
brew install llvm@19 enzyme
echo "LLVM_PREFIX=$(brew --prefix llvm@19)" >> $GITHUB_ENV
echo "OMPI_CC=$(brew --prefix llvm@19)/bin/clang" >> $GITHUB_ENV
echo "OMPI_CXX=$(brew --prefix llvm@19)/bin/clang++" >> $GITHUB_ENV
brew install llvm@20 enzyme
echo "LLVM_PREFIX=$(brew --prefix llvm@20)" >> $GITHUB_ENV
echo "OMPI_CC=$(brew --prefix llvm@20)/bin/clang" >> $GITHUB_ENV
echo "OMPI_CXX=$(brew --prefix llvm@20)/bin/clang++" >> $GITHUB_ENV
# MFEM build and test
- name: build
+4 -4
View File
@@ -211,7 +211,7 @@ miniapps/electromagnetics/joule
miniapps/electromagnetics/Volta-AMR*
miniapps/electromagnetics/Tesla-AMR*
miniapps/electromagnetics/Maxwell-Parallel*
miniapps/electromagnetics/Joule_*
miniapps/electromagnetics/Joule_[0-9]*
miniapps/gslib/field-diff
miniapps/gslib/field-interp
@@ -267,9 +267,9 @@ miniapps/meshing/bounding-box*
miniapps/meshing/jacobian-determinant*
miniapps/mtop/parheat
miniapps/mtop/ParHeat*
miniapps/mtop/ParHeat/*
miniapps/mtop/seqheat
miniapps/mtop/SeqHeat*
miniapps/mtop/SeqHeat/*
miniapps/autodiff/paradiff
miniapps/autodiff/seqadiff
@@ -277,7 +277,7 @@ miniapps/autodiff/seqtest
miniapps/autodiff/par_example
miniapps/autodiff/seq_example
miniapps/autodiff/seq_test
miniapps/autodiff/Exampl*
miniapps/autodiff/Example/*
miniapps/navier/navier_mms
miniapps/navier/navier_kovasznay
+1 -1
View File
@@ -52,4 +52,4 @@ variables:
- echo ${JOBID}
- echo ${MFEM_DATA_DIR}
- echo ${SPEC}
- srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) --reservation=ci -t 45 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data
- srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) --reservation=ci -t 60 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data
+5
View File
@@ -46,6 +46,11 @@ GPU computing
where host execution is always needed (e.g. when the DOFs array is small).
- Introduced MFEM_FOREACH_THREAD_DIRECT, which directly maps loop tasks to GPU
threads, assigning one task per thread.
- Implemented a GPU-accelerated matrix-free AMR derefinement `GridFunction`
update operator. This supports mixed geometry meshes and variable order
spaces, and is the default derefinement operator constructed by
`FiniteElementSpace::Update` and `ParFiniteElementSpace::Update`.
The operator requires `FiniteElementSpace::Nonconforming() == true`.
New and updated examples and miniapps
-------------------------------------
+14 -5
View File
@@ -598,14 +598,20 @@ set(MFEM_TPLS OPENMP HYPRE LAPACK BLAS SuperLUDist STRUMPACK METIS SuiteSparse
BENCHMARK PARELAG TRIBOL MPI_CXX HIP HIPBLAS HIPSPARSE MOONOLITH BLITZ
ALGOIM ENZYME)
# Add all *_FOUND libraries in the variable TPL_LIBRARIES.
# Add all created targets and *_FOUND libraries in the variables TPL_TARGETS and
# TPL_LIBRARIES, respectively.
set(TPL_TARGETS)
set(TPL_LIBRARIES "")
set(TPL_INCLUDE_DIRS "")
foreach(TPL IN LISTS MFEM_TPLS)
if (${TPL}_FOUND)
if (${TPL}_FOUND OR TARGET ${TPL})
message(STATUS "MFEM: using package ${TPL}")
list(APPEND TPL_LIBRARIES ${${TPL}_LIBRARIES})
list(APPEND TPL_INCLUDE_DIRS ${${TPL}_INCLUDE_DIRS})
if (TARGET ${TPL})
list(APPEND TPL_TARGETS ${TPL})
else()
list(APPEND TPL_LIBRARIES ${${TPL}_LIBRARIES})
list(APPEND TPL_INCLUDE_DIRS ${${TPL}_INCLUDE_DIRS})
endif()
endif()
endforeach(TPL)
list(REVERSE TPL_LIBRARIES)
@@ -680,7 +686,10 @@ set(MFEM_INSTALL_DIR ${CMAKE_INSTALL_PREFIX})
# Declaring the library
mfem_add_library(mfem ${SOURCES} ${HEADERS} ${MASTER_HEADERS})
# message(STATUS "TPL_LIBRARIES = ${TPL_LIBRARIES}")
target_link_libraries(mfem PUBLIC ${TPL_LIBRARIES})
target_link_libraries(mfem PUBLIC ${TPL_LIBRARIES} ${TPL_TARGETS})
if (TPL_TARGETS)
add_dependencies(mfem ${TPL_TARGETS})
endif()
if (MINGW)
target_link_libraries(mfem PRIVATE ws2_32)
endif()
+14
View File
@@ -121,6 +121,11 @@ Parallel build:
make -j 4
(For METIS 5, see https://mfem.org/building/#parallel-build-using-metis-5)
Parallel build with fetching of hypre and METIS:
mkdir <mfem-buil-dir> ; cd <mfem-build-dir>
cmake <mfem-source-dir> -DMFEM_USE_MPI=YES -DFETCH_TPLS=YES
make -j 4
CUDA build:
(this build requires CMake 3.17 or newer)
mkdir <mfem-build-dir> ; cd <mfem-build-dir>
@@ -1074,6 +1079,9 @@ The following options are CMake specific:
MFEM_ENABLE_TESTING - Enable the ctest framework for testing.
MFEM_ENABLE_EXAMPLES - Build all of the examples by default.
MFEM_ENABLE_MINIAPPS - Build all of the miniapps by default.
FETCH_TPLS - Enable fetching of all supported third-party libraries.
HYPRE_FETCH - Enable fetching of hypre.
METIS_FETCH - Enable fetching of metis.
External libraries (CMake):
---------------------------
@@ -1135,6 +1143,12 @@ The following built-in CMake packages are also used:
set the <LIBNAME>_LIBRARIES option directly; the configuration option
<LIBNAME>_DIR is not supported.
The MFEM CMake build system also provides fetching (automated building) for the
packages/libraries listed below. Note that when fetching is enabled, any related
auto-detection functionality is disabled.
- HYPRE
- METIS
Building without GNU make or CMake
==================================
+54 -2
View File
@@ -9,15 +9,18 @@
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
# Defines the following variables:
# Defines the following variables if fetching of TPLs is disabled (default):
# - HYPRE_FOUND
# - HYPRE_LIBRARIES
# - HYPRE_INCLUDE_DIRS
# - HYPRE_VERSION
# - HYPRE_USING_CUDA (internal)
# - HYPRE_USING_HIP (internal)
# otherwise, the following are defined:
# - HYPRE (imported library target)
# - HYPRE_VERSION (cache variable)
if (HYPRE_FOUND)
if (HYPRE_FOUND OR TARGET HYPRE)
if (HYPRE_USING_CUDA)
find_package(CUDAToolkit REQUIRED)
endif()
@@ -33,6 +36,55 @@ if (HYPRE_FOUND)
endif()
endif()
if (HYPRE_FETCH OR FETCH_TPLS)
set(HYPRE_FETCH_VERSION 2.33.0)
add_library(HYPRE STATIC IMPORTED)
# set options and associated dependencies
set(CMAKE_OPTIONS)
list(APPEND CMAKE_OPTIONS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
if (MFEM_USE_CUDA)
list(APPEND CMAKE_OPTIONS -DHYPRE_WITH_CUDA:BOOL=ON)
find_package(CUDAToolkit REQUIRED)
target_link_libraries(HYPRE INTERFACE CUDA::cusparse CUDA::curand CUDA::cublas)
elseif (MFEM_USE_HIP)
list(APPEND CMAKE_OPTIONS -DHYPRE_WITH_HIP:BOOL=ON)
find_package(rocsparse REQUIRED)
find_package(rocrand REQUIRED)
target_link_libraries(HYPRE INTERFACE rocsparse rocrand)
endif()
if (MFEM_USE_SINGLE)
list(APPEND CMAKE_OPTIONS -DHYPRE_ENABLE_SINGLE:BOOL=ON)
endif()
# define external project and create future include directory so it is present
# to pass CMake checks at end of MFEM configuration step
message(STATUS "Will fetch HYPRE ${HYPRE_FETCH_VERSION} to be built with ${CMAKE_OPTIONS}")
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/hypre)
include(ExternalProject)
ExternalProject_Add(hypre
GIT_REPOSITORY https://github.com/hypre-space/hypre.git
GIT_TAG v${HYPRE_FETCH_VERSION}
GIT_SHALLOW TRUE
UPDATE_DISCONNECTED TRUE
SOURCE_SUBDIR src
PREFIX ${PREFIX}
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${PREFIX} -DCMAKE_INSTALL_LIBDIR:PATH=lib ${CMAKE_OPTIONS})
file(MAKE_DIRECTORY ${PREFIX}/include)
# set imported library target properties
add_dependencies(HYPRE hypre)
set_target_properties(HYPRE PROPERTIES
IMPORTED_LOCATION ${PREFIX}/lib/libHYPRE.a
INTERFACE_INCLUDE_DIRECTORIES ${PREFIX}/include)
# convert HYPRE version to integer
string(REGEX MATCHALL "[0-9]+" HYPRE_SPLIT_VERSION ${HYPRE_FETCH_VERSION})
list(GET HYPRE_SPLIT_VERSION 0 HYPRE_MAJOR_VERSION)
list(GET HYPRE_SPLIT_VERSION 1 HYPRE_MINOR_VERSION)
list(GET HYPRE_SPLIT_VERSION 2 HYPRE_PATCH_VERSION)
math(EXPR HYPRE_VERSION "10000*${HYPRE_MAJOR_VERSION} + 100*${HYPRE_MINOR_VERSION} + ${HYPRE_PATCH_VERSION}")
# set cache variables that would otherwise be set after mfem_find_package call
set(HYPRE_VERSION ${HYPRE_VERSION} CACHE STRING "HYPRE version." FORCE)
return()
endif()
include(MfemCmakeUtilities)
mfem_find_package(HYPRE HYPRE HYPRE_DIR "include" "HYPRE.h" "lib" "HYPRE"
"Paths to headers required by HYPRE." "Libraries required by HYPRE."
+29 -1
View File
@@ -9,10 +9,38 @@
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
# Defines the following variables:
# Defines the following variables if fetching of TPLs is disabled (default):
# - METIS_FOUND
# - METIS_LIBRARIES
# - METIS_INCLUDE_DIRS
# - METIS_VERSION_5
# otherwise, the following are defined:
# - METIS (imported library target)
# - METIS_VERSION_5 (cache variable)
if (METIS_FETCH OR FETCH_TPLS)
set(METIS_FETCH_VERSION 4.0.3)
add_library(METIS STATIC IMPORTED)
# define external project
message(STATUS "Will fetch METIS ${METIS_FETCH_VERSION} to be built with default options")
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/metis)
include(ExternalProject)
ExternalProject_Add(metis
GIT_REPOSITORY https://github.com/mfem/tpls
GIT_TAG b60352fbe9675d374b00828055e55be4584c7995 # tag from 1/16/25
GIT_SHALLOW TRUE
UPDATE_DISCONNECTED TRUE
PREFIX ${PREFIX}
CONFIGURE_COMMAND tar -xzf ../metis/metis-${METIS_FETCH_VERSION}-mac.tgz --strip=1
INSTALL_COMMAND mkdir -p ${PREFIX}/lib && cp libmetis.a ${PREFIX}/lib/)
# set imported library target properties
add_dependencies(METIS metis)
set_target_properties(METIS PROPERTIES
IMPORTED_LOCATION ${PREFIX}/lib/libmetis.a)
# set cache variables that would otherwise be set after mfem_find_package call
set(METIS_VERSION_5 FALSE CACHE BOOL "Is METIS version 5?")
return()
endif()
include(MfemCmakeUtilities)
mfem_find_package(METIS METIS METIS_DIR "include;Lib" "metis.h"
+1 -1
View File
@@ -27,7 +27,7 @@ namespace mfem
{
#if (defined(MFEM_USE_CUDA) && defined(__CUDACC__)) || \
(defined(MFEM_USE_HIP) && defined(__HIPCC__))
(defined(MFEM_USE_HIP) && defined(__HIP__))
#define MFEM_HOST_DEVICE __host__ __device__
#else
#define MFEM_HOST_DEVICE
+6
View File
@@ -89,6 +89,12 @@ option(MFEM_ENABLE_EXAMPLES "Build all of the examples" OFF)
option(MFEM_ENABLE_MINIAPPS "Build all of the miniapps" OFF)
option(MFEM_ENABLE_BENCHMARKS "Build all of the benchmarks" OFF)
# Allow a user to specify fetching of certain third-party libraries instead of
# searching for existing installations.
option(FETCH_TPLS "Enable fetching of all supported third-party libraries" OFF)
option(HYPRE_FETCH "Enable fetching of hypre" OFF)
option(METIS_FETCH "Enable fetching of METIS" OFF)
# Setting CXX/MPICXX on the command line or in user.cmake will overwrite the
# autodetected C++ compiler.
# set(CXX g++)
+3 -1
View File
@@ -46,6 +46,7 @@ list(APPEND ALL_EXE_SRCS
ex38.cpp
ex39.cpp
ex40.cpp
ex41.cpp
)
if (MFEM_USE_MPI)
@@ -89,6 +90,7 @@ if (MFEM_USE_MPI)
ex37p.cpp
ex39p.cpp
ex40p.cpp
ex41p.cpp
)
endif()
@@ -151,7 +153,7 @@ if (MFEM_ENABLE_TESTING)
ex1 ex3 ex4 ex5 ex6 ex9 ex14 ex22 ex24 ex25 ex26 ex34
# parallel examples with device support:
ex1p ex2p ex3p ex4p ex5p ex6p ex7p ex9p ex13p ex14p ex22p ex24p ex25p
ex26p ex34p ex35p)
ex26p ex34p ex35p ex41p)
set(MFEM_TEST_DEVICE)
if (MFEM_USE_CUDA)
set(MFEM_TEST_DEVICE "cuda")
+455
View File
@@ -0,0 +1,455 @@
// MFEM Example 41
//
// Compile with: make ex41
//
// Sample runs: ex41
// ex41 -p 0 -r 2 -dt 0.01 -tf 10
// ex41 -m ../data/periodic-hexagon.mesh -p 0 -r 2 -dt 0.005 -tf 10
// ex41 -m ../data/periodic-square.mesh -p 1 -r 2 -dt 0.005 -tf 9
// ex41 -m ../data/periodic-hexagon.mesh -p 1 -r 2 -dt 0.005 -tf 9
// ex41 -m ../data/amr-quad.mesh -p 1 -r 2 -dt 0.002 -tf 9
// ex41 -m ../data/star-q3.mesh -p 1 -r 2 -dt 0.001 -tf 9
// ex41 -m ../data/star-mixed.mesh -p 1 -r 2 -dt 0.005 -tf 9
// ex41 -m ../data/disc-nurbs.mesh -p 1 -r 3 -dt 0.005 -tf 9
// ex41 -m ../data/disc-nurbs.mesh -p 2 -r 3 -dt 0.005 -tf 9
// ex41 -m ../data/periodic-square.mesh -p 3 -r 4 -dt 0.0025 -tf 9 -vs 20
// ex41 -m ../data/periodic-cube.mesh -p 0 -r 2 -o 2 -dt 0.01 -tf 8
//
// Device sample runs:
//
// Description: This example code solves the time-dependent advection-diffusion
// equation du/dt + v.grad(u) - a div(grad(u)) = 0, where v is a
// given fluid velocity, a is the diffusion coefficient, and
// u0(x)=u(0,x) is a given initial condition.
//
// The example demonstrates the use of Discontinuous Galerkin (DG)
// bilinear forms in MFEM (face integrators), and the use of IMEX ODE time integrators.
#include "mfem.hpp"
using namespace std;
using namespace mfem;
// Choice for the problem setup. The fluid velocity, initial condition and
// inflow boundary condition are chosen based on this parameter.
int problem;
// Velocity coefficient
void velocity_function(const Vector &x, Vector &v);
// Initial condition
real_t u0_function(const Vector &x);
// Inflow boundary condition - 0 for this example
real_t inflow_function(const Vector &x);
// Mesh bounding box
Vector bb_min, bb_max;
class DG_Solver : public Solver
{
private:
SparseMatrix &M, &K, &S, A;
CGSolver linear_solver;
BlockILU prec;
real_t dt;
public:
DG_Solver(SparseMatrix &M_, SparseMatrix &K_, SparseMatrix &S_,
const FiniteElementSpace &fes)
: M(M_),
K(K_),
S(S_),
prec(fes.GetTypicalFE()->GetDof(),
BlockILU::Reordering::MINIMUM_DISCARDED_FILL),
dt(1.0)
{
linear_solver.iterative_mode = false;
linear_solver.SetRelTol(1e-9);
linear_solver.SetAbsTol(0.0);
linear_solver.SetMaxIter(100);
linear_solver.SetPrintLevel(0);
linear_solver.SetPreconditioner(prec);
}
void SetTimeStep(real_t dt_)
{
if (dt_ != dt)
{
dt = dt_;
// Form operator A = M + dt*S
A = S;
A *= dt;
A += M;
// this will also call SetOperator on the preconditioner
linear_solver.SetOperator(A);
}
}
void SetOperator(const Operator &op) override
{
linear_solver.SetOperator(op);
}
void Mult(const Vector &x, Vector &y) const override
{
linear_solver.Mult(x, y);
}
};
/** A time-dependent operator for the right-hand side of the ODE. The DG weak
form of the advection-diffusion equation is (M + dt S) du/dt = Su - K u + b, where M and K are the mass
and advection matrices, and b describes the flow on the boundary. In the case of IMEX evolution, the diffusion term is treated
implicitly, and the advection term is treated explicitly. */
class IMEX_Evolution : public SplitTimeDependentOperator
{
private:
BilinearForm &M, &K, &S;
const Vector &b;
unique_ptr<Solver> M_prec;
CGSolver M_solver;
unique_ptr<DG_Solver> dg_solver;
mutable Vector z;
public:
IMEX_Evolution(BilinearForm &M_, BilinearForm &K_, BilinearForm &S_,
const Vector &b_);
void Mult1(const Vector &x, Vector &y) const;
void ImplicitSolve2(const real_t dt, const Vector &x, Vector &k) override;
};
int main(int argc, char *argv[])
{
// 1. Parse command-line options.
problem = 0;
const char *mesh_file = "../data/periodic-square.mesh";
int ref_levels = 2;
int order = 3;
const char *device_config = "cpu";
int ode_solver_type = 58;
real_t t_final = 10.0;
real_t dt = 0.001;
bool paraview = false;
int vis_steps = 50;
real_t diffusion_term = 0.01;
real_t kappa = (order+1)*(order+1);
real_t sigma = -1.0;
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
args.AddOption(&problem, "-p", "--problem",
"Problem setup to use. See options in velocity_function().");
args.AddOption(&ref_levels, "-r", "--refine",
"Number of times to refine the mesh uniformly.");
args.AddOption(&order, "-o", "--order", "Order of the finite elements.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&ode_solver_type, "-s", "--ode-solver",
ODESolver::Types.c_str());
args.AddOption(&t_final, "-tf", "--t-final", "Final time; start time is 0.");
args.AddOption(&dt, "-dt", "--time-step", "Time step.");
args.AddOption(&paraview, "-paraview", "--paraview-datafiles", "-no-paraview",
"--no-paraview-datafiles",
"Save data files for ParaView (paraview.org) visualization.");
args.AddOption(&vis_steps, "-vs", "--visualization-steps",
"Visualize every n-th timestep.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(cout);
return 1;
}
if (kappa < 0)
{
kappa = (order+1)*(order+1);
}
args.PrintOptions(cout);
Device device(device_config);
device.Print();
// 2. Read the mesh from the given mesh file. We can handle geometrically
// periodic meshes in this code.
Mesh mesh(mesh_file);
const int dim = mesh.Dimension();
// 3. Define the Split ODE solver used for time integration. The IMEX solvers currently
// available are: 55 - Forward Backward Euler, 56 - IMEXRK2(2,2,2), 57 - IMEXRK2(2,3,2), and
// 58 - IMEX_DIRK_RK3.
unique_ptr<SplitODESolver> ode_solver = SplitODESolver::Select(ode_solver_type);
// 4. Refine the mesh to increase the resolution. In this example we do
// 'ref_levels' of uniform refinement, where 'ref_levels' is a
// command-line parameter.
for (int lev = 0; lev < ref_levels; lev++) {mesh.UniformRefinement();}
if (mesh.NURBSext) {mesh.SetCurvature(max(order, 1));}
mesh.GetBoundingBox(bb_min, bb_max, max(order, 1));
// 5. Define the discontinuous DG finite element space of the given
// polynomial order on the refined mesh.
DG_FECollection fec(order, dim, BasisType::GaussLobatto);
FiniteElementSpace fes(&mesh, &fec);
cout << "Number of unknowns: " << fes.GetVSize() << endl;
// 6. Set up and assemble the bilinear and linear forms corresponding to the
// DG discretization. The DGTraceIntegrator involves integrals over mesh
// interior faces.
VectorFunctionCoefficient velocity(dim, velocity_function);
FunctionCoefficient inflow(inflow_function); //Zero for now
ConstantCoefficient diff_coeff(diffusion_term);
BilinearForm m(&fes);
BilinearForm k(&fes);
BilinearForm s(&fes);
m.AddDomainIntegrator(new MassIntegrator);
constexpr real_t alpha = -1.0;
k.AddDomainIntegrator(new ConvectionIntegrator(velocity, alpha));
k.AddInteriorFaceIntegrator(new NonconservativeDGTraceIntegrator(velocity,
alpha));
k.AddBdrFaceIntegrator(new NonconservativeDGTraceIntegrator(velocity, alpha));
s.AddDomainIntegrator(new DiffusionIntegrator(diff_coeff));
s.AddInteriorFaceIntegrator(new DGDiffusionIntegrator(diff_coeff, sigma,
kappa));
s.AddBdrFaceIntegrator(new DGDiffusionIntegrator(diff_coeff, sigma, kappa));
LinearForm b(&fes);
b.AddBdrFaceIntegrator(new BoundaryFlowIntegrator(inflow, velocity, alpha));
int skip_zeros = 0;
m.Assemble(skip_zeros);
k.Assemble(skip_zeros);
s.Assemble(skip_zeros);
b.Assemble();
m.Finalize(skip_zeros);
k.Finalize(skip_zeros);
s.Finalize(skip_zeros);
// 7. Define the initial conditions.
FunctionCoefficient u0(u0_function);
GridFunction u(&fes);
u.ProjectCoefficient(u0);
// 8. Set up paraview visualization, if desired.
unique_ptr<ParaViewDataCollection> pv;
if (paraview)
{
pv = make_unique<ParaViewDataCollection>("Example41", &mesh);
pv->SetPrefixPath("ParaView");
pv->RegisterField("solution", &u);
pv->SetLevelsOfDetail(order);
pv->SetDataFormat(VTKFormat::BINARY);
pv->SetHighOrderOutput(true);
pv->SetCycle(0);
pv->SetTime(0.0);
pv->Save();
}
// 9. Define the time-dependent evolution operator describing the ODE
// right-hand side, and perform time-integration (looping over the time
// iterations, ti, with a time-step dt).
IMEX_Evolution adv(m, k, s, b);
real_t t = 0.0;
adv.SetTime(t);
ode_solver->Init(adv);
bool done = false;
for (int ti = 0; !done; )
{
real_t dt_real = min(dt, t_final - t);
ode_solver->Step(u, t, dt_real);
ti++;
done = (t >= t_final - 1e-8*dt);
if (done || ti % vis_steps == 0)
{
cout << "time step: " << ti << ", time: " << t << endl;
if (paraview)
{
pv->SetCycle(ti);
pv->SetTime(t);
pv->Save();
}
}
}
return 0;
}
// Implementation of class IMEX_Evolution
IMEX_Evolution::IMEX_Evolution(BilinearForm &M_, BilinearForm &K_,
BilinearForm &S_, const Vector &b_)
: SplitTimeDependentOperator(M_.FESpace()->GetTrueVSize()),
M(M_), K(K_), S(S_), b(b_), z(height)
{
Array<int> ess_tdof_list;
if (M.GetAssemblyLevel() == AssemblyLevel::LEGACY)
{
M_prec = make_unique<DSmoother>(M.SpMat());
M_solver.SetOperator(M.SpMat());
dg_solver = make_unique<DG_Solver>(M.SpMat(), K.SpMat(), S.SpMat(),
*M.FESpace());
}
else
{
M_prec = make_unique<OperatorJacobiSmoother>(M, ess_tdof_list);
M_solver.SetOperator(M);
dg_solver = NULL;
}
M_solver.SetPreconditioner(*M_prec);
M_solver.iterative_mode = false;
M_solver.SetRelTol(1e-9);
M_solver.SetAbsTol(0.0);
M_solver.SetMaxIter(100);
M_solver.SetPrintLevel(0);
}
void IMEX_Evolution::Mult1(const Vector &x, Vector &y) const
{
// Perform the explicit step
// y = M^{-1} (K x + b)
K.Mult(x, z);
z += b;
M_solver.Mult(z, y);
}
void IMEX_Evolution::ImplicitSolve2(const real_t dt, const Vector &x, Vector &k)
{
// Perform the implicit step
// solve for k, k = -(M+dt S)^{-1} S x
MFEM_VERIFY(dg_solver != NULL,
"Implicit time integration is not supported with partial assembly");
S.Mult(x, z);
z*= -1.0;
dg_solver->SetTimeStep(dt);
dg_solver->Mult(z, k);
}
// Velocity coefficient
void velocity_function(const Vector &x, Vector &v)
{
int dim = x.Size();
// map to the reference [-1,1] domain
Vector X(dim);
for (int i = 0; i < dim; i++)
{
real_t center = (bb_min[i] + bb_max[i]) * 0.5;
X(i) = 2 * (x(i) - center) / (bb_max[i] - bb_min[i]);
}
switch (problem)
{
case 0:
{
// Translations in 1D, 2D, and 3D
switch (dim)
{
case 1: v(0) = 1.0; break;
case 2: v(0) = sqrt(2./3.); v(1) = sqrt(1./3.); break;
case 3: v(0) = sqrt(3./6.); v(1) = sqrt(2./6.); v(2) = sqrt(1./6.);
break;
}
break;
}
case 1:
case 2:
{
// Clockwise rotation in 2D around the origin
const real_t w = M_PI/2;
switch (dim)
{
case 1: v(0) = 1.0; break;
case 2: v(0) = w*X(1); v(1) = -w*X(0); break;
case 3: v(0) = w*X(1); v(1) = -w*X(0); v(2) = 0.0; break;
}
break;
}
case 3:
{
// Clockwise twisting rotation in 2D around the origin
const real_t w = M_PI/2;
real_t d = max((X(0)+1.)*(1.-X(0)),0.) * max((X(1)+1.)*(1.-X(1)),0.);
d = d*d;
switch (dim)
{
case 1: v(0) = 1.0; break;
case 2: v(0) = d*w*X(1); v(1) = -d*w*X(0); break;
case 3: v(0) = d*w*X(1); v(1) = -d*w*X(0); v(2) = 0.0; break;
}
break;
}
}
}
// Initial condition
real_t u0_function(const Vector &x)
{
int dim = x.Size();
// map to the reference [-1,1] domain
Vector X(dim);
for (int i = 0; i < dim; i++)
{
real_t center = (bb_min[i] + bb_max[i]) * 0.5;
X(i) = 2 * (x(i) - center) / (bb_max[i] - bb_min[i]);
}
switch (problem)
{
case 0:
case 1:
{
switch (dim)
{
case 1:
return exp(-40.*pow(X(0)-0.5,2));
case 2:
case 3:
{
real_t rx = 0.45, ry = 0.25, cx = 0., cy = -0.2, w = 10.;
if (dim == 3)
{
const real_t s = (1. + 0.25*cos(2*M_PI*X(2)));
rx *= s;
ry *= s;
}
return ( std::erfc(w*(X(0)-cx-rx))*std::erfc(-w*(X(0)-cx+rx)) *
std::erfc(w*(X(1)-cy-ry))*std::erfc(-w*(X(1)-cy+ry)) )/16;
}
}
}
case 2:
{
real_t x_ = X(0), y_ = X(1), rho, phi;
rho = std::hypot(x_, y_);
phi = atan2(y_, x_);
return pow(sin(M_PI*rho),2)*sin(3*phi);
}
case 3:
{
const real_t f = M_PI;
return sin(f*X(0))*sin(f*X(1));
}
}
return 0.0;
}
// Inflow boundary condition (zero for the problems considered in this example)
real_t inflow_function(const Vector &x)
{
switch (problem)
{
case 0:
case 1:
case 2:
case 3: return 0.0;
}
return 0.0;
}
+666
View File
@@ -0,0 +1,666 @@
// MFEM Example 41 - Parallel Version
//
// Compile with: make ex41p
//
// Sample runs: mpirun -np 4 ex41p
// mpirun -np 4 ex41p -p 0 -dt 0.01 -tf 10
// mpirun -np 4 ex41p -m ../data/periodic-hexagon.mesh -p 0 -dt 0.005 -tf 10
// mpirun -np 4 ex41p -m ../data/periodic-square.mesh -p 1 -dt 0.005 -tf 9
// mpirun -np 4 ex41p -m ../data/periodic-hexagon.mesh -p 1 -dt 0.005 -tf 9
// mpirun -np 4 ex41p -m ../data/star-q3.mesh -p 1 -rp 1 -dt 0.001 -tf 9
// mpirun -np 4 ex41p -m ../data/disc-nurbs.mesh -p 1 -rp 1 -dt 0.005 -tf 9
// mpirun -np 4 ex41p -m ../data/disc-nurbs.mesh -p 2 -rp 1 -dt 0.005 -tf 9
// mpirun -np 4 ex41p -m ../data/periodic-square.mesh -rp 2 -dt 0.0025 -tf 9 -vs 20
// mpirun -np 4 ex41p -m ../data/periodic-cube.mesh -p 0 -rs 2 -o 2 -dt 0.01 -tf 8
//
// Device sample runs:
//
// Description: This example code solves the time-dependent advection-diffusion
// equation du/dt + v.grad(u) - a div(grad(u)) = 0, where v is a
// given fluid velocity, a is the diffusion coefficient, and
// u0(x)=u(0,x) is a given initial condition.
//
// The example demonstrates the use of Discontinuous Galerkin (DG)
// bilinear forms in MFEM (face integrators), DG-LOR Preconditioning and the use of IMEX ODE time integrators.
#include "mfem.hpp"
using namespace std;
using namespace mfem;
// Choice for the problem setup. The fluid velocity, initial condition and
// inflow boundary condition are chosen based on this parameter.
int problem;
// Velocity coefficient
void velocity_function(const Vector &x, Vector &v);
// Initial condition
real_t u0_function(const Vector &x);
// Inflow boundary condition
real_t inflow_function(const Vector &x);
// Mesh bounding box
Vector bb_min, bb_max;
class DG_Solver : public Solver
{
private:
HypreParMatrix &M, &K, &S;
HypreParMatrix *A;
CGSolver linear_solver;
real_t dt;
SparseMatrix M_diag;
public:
DG_Solver(HypreParMatrix &M_, HypreParMatrix &K_, HypreParMatrix &S_,
const FiniteElementSpace &fes)
: M(M_),
K(K_),
S(S_),
A(nullptr),
linear_solver(M.GetComm()),
dt(1.0)
{
linear_solver.iterative_mode = false;
linear_solver.SetRelTol(1e-9);
linear_solver.SetAbsTol(0.0);
linear_solver.SetMaxIter(100);
linear_solver.SetPrintLevel(0);
M.GetDiag(M_diag);
}
void SetTimeStep(real_t dt_)
{
if (dt_ != dt)
{
delete A;
dt = dt_;
// // Form operator A = M + dt*S
A = Add(dt, S, 1.0, M);
linear_solver.SetOperator(*A);
}
}
void SetOperator(const Operator &op) override
{
linear_solver.SetOperator(op);
}
void Mult(const Vector &x, Vector &y) const override
{
linear_solver.Mult(x, y);
}
void SetPreconditioner(Solver &precond)
{
linear_solver.SetPreconditioner(precond);
}
~DG_Solver() override
{
delete A;
}
};
/** A time-dependent operator for the right-hand side of the ODE. The DG weak
form of the advection-diffusion equation is (M + dt S) du/dt = Su - K u + b, where M and K are the mass
and advection matrices, and b describes the flow on the boundary. In the case of IMEX evolution, the diffusion term is treated
implicitly, and the advection term is treated explicitly. */
class IMEX_Evolution : public SplitTimeDependentOperator
{
private:
OperatorHandle M, K, S, A;
const Vector &b;
Solver *M_prec;
CGSolver M_solver;
DG_Solver *dg_solver;
LORSolver<HypreBoomerAMG>* lor_solver;
mutable Vector z;
mutable Vector w;
public:
IMEX_Evolution(ParBilinearForm &M_, ParBilinearForm &K_, ParBilinearForm &S_,
const Vector &b_, ParBilinearForm &A_);
virtual
~IMEX_Evolution()
{
delete dg_solver;
delete lor_solver;
delete M_prec;
}
virtual
void Mult1(const Vector &x, Vector &y) const override;
virtual
void ImplicitSolve2(const real_t dt, const Vector &x, Vector &k) override;
};
int main(int argc, char *argv[])
{
// 1. Initialize MPI and HYPRE.
Mpi::Init();
int num_procs = Mpi::WorldSize();
int myid = Mpi::WorldRank();
Hypre::Init();
// 2. Parse command-line options.
problem = 0;
const char *mesh_file = "../data/periodic-square.mesh";
int ser_ref_levels = 2;
int par_ref_levels = 0;
int order = 3;
bool pa = false;
bool ea = false;
bool fa = false;
const char *device_config = "cpu";
int ode_solver_type = 58;
real_t t_final = 10.0;
real_t dt = 0.001;
bool paraview = false;
int vis_steps = 50;
bool adios2 = false;
bool binary = false;
real_t diffusion_term = 0.01;
real_t kappa = -1.0;
real_t sigma = -1.0;
bool visualization = false;
// #if MFEM_HYPRE_VERSION >= 21800
// PrecType prec_type = PrecType::AIR;
// #else
// PrecType prec_type = PrecType::ILU;
// #endif
int precision = 16;
cout.precision(precision);
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&problem, "-p", "--problem",
"Problem setup to use. See options in velocity_function().");
args.AddOption(&ser_ref_levels, "-rs", "--refine-serial",
"Number of times to refine the mesh uniformly in serial.");
args.AddOption(&par_ref_levels, "-rp", "--refine-parallel",
"Number of times to refine the mesh uniformly in parallel.");
args.AddOption(&order, "-o", "--order",
"Order (degree) of the finite elements.");
args.AddOption(&pa, "-pa", "--partial-assembly", "-no-pa",
"--no-partial-assembly", "Enable Partial Assembly.");
args.AddOption(&ea, "-ea", "--element-assembly", "-no-ea",
"--no-element-assembly", "Enable Element Assembly.");
args.AddOption(&fa, "-fa", "--full-assembly", "-no-fa",
"--no-full-assembly", "Enable Full Assembly.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&ode_solver_type, "-s", "--ode-solver",
SplitODESolver::Types.c_str());
args.AddOption(&t_final, "-tf", "--t-final",
"Final time; start time is 0.");
args.AddOption(&dt, "-dt", "--time-step",
"Time step.");
args.AddOption(&paraview, "-paraview", "--paraview-datafiles", "-no-paraview",
"--no-paraview-datafiles",
"Save data files for ParaView (paraview.org) visualization.");
args.AddOption(&adios2, "-adios2", "--adios2-streams", "-no-adios2",
"--no-adios2-streams",
"Save data using adios2 streams.");
args.AddOption(&binary, "-binary", "--binary-datafiles", "-ascii",
"--ascii-datafiles",
"Use binary (Sidre) or ascii format for VisIt data files.");
args.AddOption(&vis_steps, "-vs", "--visualization-steps",
"Visualize every n-th timestep.");
args.Parse();
if (!args.Good())
{
if (Mpi::Root())
{
args.PrintUsage(cout);
}
return 1;
}
if (Mpi::Root())
{
args.PrintOptions(cout);
}
if (kappa < 0)
{
kappa = (order+1)*(order+1);
}
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
// 3. Read the mesh from the given mesh file. We can handle geometrically
// periodic meshes in this code.
Mesh *mesh = new Mesh(mesh_file);
const int dim = mesh->Dimension();
// 4. Define the Split ODE solver used for time integration. The IMEX solvers currently
// available are: 55 - Forward Backward Euler, 56 - IMEXRK2(2,2,2), 57 - IMEXRK2(2,3,2), and
// 58 - IMEX_DIRK_RK3.
unique_ptr<SplitODESolver> ode_solver = SplitODESolver::Select(ode_solver_type);
// 5. Refine the mesh to increase the resolution. In this example we do
// 'ref_levels' of uniform refinement, where 'ref_levels' is a
// command-line parameter.
for (int lev = 0; lev < ser_ref_levels; lev++) { mesh->UniformRefinement(); }
if (mesh->NURBSext)
{
mesh->SetCurvature(max(order, 1));
}
mesh->GetBoundingBox(bb_min, bb_max, max(order, 1));
// 6. Define the parallel mesh by a partitioning of the serial mesh. Refine
// this mesh further in parallel to increase the resolution. Once the
// parallel mesh is defined, the serial mesh can be deleted.
ParMesh *pmesh = new ParMesh(MPI_COMM_WORLD, *mesh);
delete mesh;
for (int lev = 0; lev < par_ref_levels; lev++)
{
pmesh->UniformRefinement();
}
// 7. Define the discontinuous DG finite element space of the given
// polynomial order on the refined mesh.
DG_FECollection fec(order, dim, BasisType::GaussLobatto);
ParFiniteElementSpace *fes = new ParFiniteElementSpace(pmesh, &fec);
HYPRE_BigInt global_vSize = fes->GlobalTrueVSize();
if (Mpi::Root())
{
cout << "Number of unknowns: " << global_vSize << endl;
}
// 8. Set up and assemble the bilinear and linear forms corresponding to the
// DG discretization. The DGTraceIntegrator involves integrals over mesh
// interior faces.
VectorFunctionCoefficient velocity(dim, velocity_function);
FunctionCoefficient inflow(inflow_function);
ConstantCoefficient diff_coeff(diffusion_term);
ConstantCoefficient dt_diff_coeff(dt*diffusion_term);
ParBilinearForm *m = new ParBilinearForm(fes);
ParBilinearForm *k = new ParBilinearForm(fes);
ParBilinearForm *s = new ParBilinearForm(fes);
if (pa)
{
m->SetAssemblyLevel(AssemblyLevel::PARTIAL);
k->SetAssemblyLevel(AssemblyLevel::PARTIAL);
s->SetAssemblyLevel(AssemblyLevel::PARTIAL);
}
else if (ea)
{
m->SetAssemblyLevel(AssemblyLevel::ELEMENT);
k->SetAssemblyLevel(AssemblyLevel::ELEMENT);
s->SetAssemblyLevel(AssemblyLevel::ELEMENT);
}
else if (fa)
{
m->SetAssemblyLevel(AssemblyLevel::FULL);
k->SetAssemblyLevel(AssemblyLevel::FULL);
s->SetAssemblyLevel(AssemblyLevel::FULL);
}
m->AddDomainIntegrator(new MassIntegrator);
constexpr real_t alpha = -1.0;
k->AddDomainIntegrator(new ConvectionIntegrator(velocity, alpha));
k->AddInteriorFaceIntegrator(new NonconservativeDGTraceIntegrator(velocity,
alpha));
k->AddBdrFaceIntegrator(new NonconservativeDGTraceIntegrator(velocity, alpha));
s->AddDomainIntegrator(new DiffusionIntegrator(diff_coeff));
s->AddInteriorFaceIntegrator(new DGDiffusionIntegrator(diff_coeff, sigma,
kappa));
s->AddBdrFaceIntegrator(new DGDiffusionIntegrator(diff_coeff, sigma, kappa));
ParLinearForm *b = new ParLinearForm(fes);
b->AddBdrFaceIntegrator(new BoundaryFlowIntegrator(inflow, velocity, alpha));
//For the preconditioner - create billinear form corresponding to operator (M + dt S)
ParBilinearForm *a = new ParBilinearForm(fes);
a->AddDomainIntegrator(new MassIntegrator);
a->AddDomainIntegrator(new DiffusionIntegrator(dt_diff_coeff));
a->AddInteriorFaceIntegrator(new DGDiffusionIntegrator(dt_diff_coeff, sigma,
kappa));
a->AddBdrFaceIntegrator(new DGDiffusionIntegrator(dt_diff_coeff, sigma, kappa));
int skip_zeros = 0;
m->Assemble(skip_zeros);
k->Assemble(skip_zeros);
s->Assemble(skip_zeros);
a->Assemble();
b->Assemble();
m->Finalize(skip_zeros);
k->Finalize(skip_zeros);
s->Finalize(skip_zeros);
a->Finalize(skip_zeros);
HypreParVector *B = b->ParallelAssemble();
// 9. Define the initial conditions. Set up visualization (if desired).
FunctionCoefficient u0(u0_function);
ParGridFunction *u = new ParGridFunction(fes);
u->ProjectCoefficient(u0);
HypreParVector *U = u->GetTrueDofs();
{
ostringstream mesh_name, sol_name;
mesh_name << "ex41-mesh." << setfill('0') << setw(6) << myid;
sol_name << "ex41-init." << setfill('0') << setw(6) << myid;
ofstream omesh(mesh_name.str().c_str());
omesh.precision(precision);
pmesh->Print(omesh);
ofstream osol(sol_name.str().c_str());
osol.precision(precision);
u->Save(osol);
}
ParaViewDataCollection *pd = NULL;
if (paraview)
{
pd = new ParaViewDataCollection("Example41P", pmesh);
pd->SetPrefixPath("ParaView");
pd->RegisterField("solution", u);
pd->SetLevelsOfDetail(order);
pd->SetDataFormat(VTKFormat::BINARY);
pd->SetHighOrderOutput(true);
pd->SetCycle(0);
pd->SetTime(0.0);
pd->Save();
}
socketstream sout;
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
sout.open(vishost, visport);
if (!sout)
{
if (Mpi::Root())
{
cout << "Unable to connect to GLVis server at "
<< vishost << ':' << visport << endl;
}
visualization = false;
if (Mpi::Root())
{
cout << "GLVis visualization disabled.\n";
}
}
else
{
sout << "parallel " << num_procs << " " << myid << "\n";
sout.precision(precision);
sout << "solution\n" << *pmesh << *u;
sout << "pause\n";
sout << flush;
if (Mpi::Root())
{
cout << "GLVis visualization paused."
<< " Press space (in the GLVis window) to resume it.\n";
}
}
}
// 10. Define the time-dependent evolution operator describing the ODE
// right-hand side, and perform time-integration (looping over the time
// iterations, ti, with a time-step dt).
IMEX_Evolution adv(*m, *k, *s, *B, *a);
real_t t = 0.0;
adv.SetTime(t);
ode_solver->Init(adv);
bool done = false;
for (int ti = 0; !done; )
{
real_t dt_real = min(dt, t_final - t);
ode_solver->Step(*U, t, dt_real);
ti++;
done = (t >= t_final - 1e-8*dt);
if (done || ti % vis_steps == 0)
{
if (Mpi::Root())
{
cout << "time step: " << ti << ", time: " << t << endl;
}
*u = *U;
if (visualization)
{
sout << "parallel " << num_procs << " " << myid << "\n";
sout << "solution\n" << *pmesh << *u << flush;
}
if (paraview)
{
pd->SetCycle(ti);
pd->SetTime(t);
pd->Save();
}
}
}
{
*u = *U;
ostringstream sol_name;
sol_name << "ex41-final." << setfill('0') << setw(6) << myid;
ofstream osol(sol_name.str().c_str());
osol.precision(precision);
u->Save(osol);
}
// 11. Free the used memory.
delete U;
delete u;
delete B;
delete a;
delete b;
delete s;
delete k;
delete m;
delete fes;
delete pmesh;
return 0;
}
// Implementation of class IMEX_Evolution
IMEX_Evolution::IMEX_Evolution(ParBilinearForm &M_, ParBilinearForm &K_,
ParBilinearForm &S_, const Vector &b_, ParBilinearForm &A_)
: SplitTimeDependentOperator(M_.ParFESpace()->GetTrueVSize()), b(b_),
M_solver(M_.ParFESpace()->GetComm()), z(height), w(height)
{
if (M_.GetAssemblyLevel()==AssemblyLevel::LEGACY)
{
M.Reset(M_.ParallelAssemble(), true);
K.Reset(K_.ParallelAssemble(), true);
S.Reset(S_.ParallelAssemble(), true);
}
else
{
M.Reset(&M_, false);
K.Reset(&K_, false);
S.Reset(&S_, false);
}
M_solver.SetOperator(*M);
Array<int> ess_tdof_list;
if (M_.GetAssemblyLevel() == AssemblyLevel::LEGACY)
{
A.Reset(A_.ParallelAssemble(), true);
HypreParMatrix &M_mat = *M.As<HypreParMatrix>();
HypreParMatrix &K_mat = *K.As<HypreParMatrix>();
HypreParMatrix &S_mat = *S.As<HypreParMatrix>();
HypreSmoother *hypre_prec = new HypreSmoother(M_mat, HypreSmoother::Jacobi);
M_prec = hypre_prec;
dg_solver = new DG_Solver(M_mat, K_mat, S_mat, *M_.FESpace());
lor_solver = new LORSolver<HypreBoomerAMG>(A_, ess_tdof_list);
lor_solver->GetSolver().SetSystemsOptions(A_.ParFESpace()->GetVDim(), true);
dg_solver -> SetPreconditioner(*lor_solver);
}
else
{
M_prec = new OperatorJacobiSmoother(M_, ess_tdof_list);
dg_solver = NULL;
}
M_solver.SetPreconditioner(*M_prec);
M_solver.iterative_mode = false;
M_solver.SetRelTol(1e-9);
M_solver.SetAbsTol(0.0);
M_solver.SetMaxIter(100);
M_solver.SetPrintLevel(0);
}
void IMEX_Evolution::Mult1(const Vector &x, Vector &y) const
{
// Perform the explicit step
// y = M^{-1} (K x + b)
K->Mult(x, z);
z += b;
M_solver.Mult(z, y);
}
void IMEX_Evolution::ImplicitSolve2(const real_t dt, const Vector &x, Vector &k)
{
// Perform the implicit step
// solve for k, k = -(M+dt S)^{-1} S x
MFEM_VERIFY(dg_solver != NULL,
"Implicit time integration is not supported with partial assembly");
S->Mult(x, z);
z*= -1.0;
dg_solver->SetTimeStep(dt);
dg_solver->Mult(z, k);
}
// Velocity coefficient
void velocity_function(const Vector &x, Vector &v)
{
int dim = x.Size();
// map to the reference [-1,1] domain
Vector X(dim);
for (int i = 0; i < dim; i++)
{
real_t center = (bb_min[i] + bb_max[i]) * 0.5;
X(i) = 2 * (x(i) - center) / (bb_max[i] - bb_min[i]);
}
switch (problem)
{
case 0:
{
// Translations in 1D, 2D, and 3D
switch (dim)
{
case 1: v(0) = 1.0; break;
case 2: v(0) = sqrt(2./3.); v(1) = sqrt(1./3.); break;
case 3: v(0) = sqrt(3./6.); v(1) = sqrt(2./6.); v(2) = sqrt(1./6.);
break;
}
break;
}
case 1:
case 2:
{
// Clockwise rotation in 2D around the origin
const real_t w = M_PI/2;
switch (dim)
{
case 1: v(0) = 1.0; break;
case 2: v(0) = w*X(1); v(1) = -w*X(0); break;
case 3: v(0) = w*X(1); v(1) = -w*X(0); v(2) = 0.0; break;
}
break;
}
case 3:
{
// Clockwise twisting rotation in 2D around the origin
const real_t w = M_PI/2;
real_t d = max((X(0)+1.)*(1.-X(0)),0.) * max((X(1)+1.)*(1.-X(1)),0.);
d = d*d;
switch (dim)
{
case 1: v(0) = 1.0; break;
case 2: v(0) = d*w*X(1); v(1) = -d*w*X(0); break;
case 3: v(0) = d*w*X(1); v(1) = -d*w*X(0); v(2) = 0.0; break;
}
break;
}
}
}
// Initial condition
real_t u0_function(const Vector &x)
{
int dim = x.Size();
// map to the reference [-1,1] domain
Vector X(dim);
for (int i = 0; i < dim; i++)
{
real_t center = (bb_min[i] + bb_max[i]) * 0.5;
X(i) = 2 * (x(i) - center) / (bb_max[i] - bb_min[i]);
}
switch (problem)
{
case 0:
case 1:
{
switch (dim)
{
case 1:
return exp(-40.*pow(X(0)-0.5,2));
case 2:
case 3:
{
real_t rx = 0.45, ry = 0.25, cx = 0., cy = -0.2, w = 10.;
if (dim == 3)
{
const real_t s = (1. + 0.25*cos(2*M_PI*X(2)));
rx *= s;
ry *= s;
}
return ( std::erfc(w*(X(0)-cx-rx))*std::erfc(-w*(X(0)-cx+rx)) *
std::erfc(w*(X(1)-cy-ry))*std::erfc(-w*(X(1)-cy+ry)) )/16;
}
}
}
case 2:
{
real_t x_ = X(0), y_ = X(1), rho, phi;
rho = std::hypot(x_, y_);
phi = atan2(y_, x_);
return pow(sin(M_PI*rho),2)*sin(3*phi);
}
case 3:
{
const real_t f = M_PI;
return sin(f*X(0))*sin(f*X(1));
}
}
return 0.0;
}
// Inflow boundary condition (zero for the problems considered in this example)
real_t inflow_function(const Vector &x)
{
switch (problem)
{
case 0:
case 1:
case 2:
case 3: return 0.0;
}
return 0.0;
}
+3 -3
View File
@@ -22,14 +22,14 @@ MFEM_LIB_FILE = mfem_is_not_built
SEQ_EXAMPLES = ex0 ex1 ex2 ex3 ex4 ex5 ex6 ex7 ex8 ex9 ex10 ex14 ex15 ex16 \
ex17 ex18 ex19 ex20 ex21 ex22 ex23 ex24 ex25 ex26 ex27 ex28 ex29 ex30 \
ex31 ex33 ex34 ex36 ex37 ex38 ex39 ex40
ex31 ex33 ex34 ex36 ex37 ex38 ex39 ex40 ex41
PAR_EXAMPLES = ex0p ex1p ex2p ex3p ex4p ex5p ex6p ex7p ex8p ex9p ex10p ex11p \
ex12p ex13p ex14p ex15p ex16p ex17p ex18p ex19p ex20p ex21p ex22p ex24p \
ex25p ex26p ex27p ex28p ex29p ex30p ex31p ex32p ex33p ex34p ex35p ex36p \
ex37p ex39p ex40p
ex37p ex39p ex40p ex41p
SEQ_DEVICE_EXAMPLES = ex1 ex3 ex4 ex5 ex6 ex9 ex14 ex22 ex24 ex25 ex26 ex34
PAR_DEVICE_EXAMPLES = ex1p ex2p ex3p ex4p ex5p ex6p ex7p ex9p ex13p ex14p \
ex22p ex24p ex25p ex26p ex34p ex35p
ex22p ex24p ex25p ex26p ex34p ex35p ex41p
ifeq ($(MFEM_USE_LAPACK),YES)
SEQ_EXAMPLES += ex38
+7
View File
@@ -82,6 +82,8 @@ set(SRCS
fe/fe_ser.cpp
fe_coll.cpp
fespace.cpp
derefmat_op.cpp
pderefmat_op.cpp
geom.cpp
gridfunc.cpp
hybridization.cpp
@@ -239,8 +241,13 @@ set(HDRS
lor/lor_ams.hpp
lor/lor_batched.hpp
lor/lor_h1.hpp
lor/lor_dg.hpp
lor/lor_nd.hpp
lor/lor_rt.hpp
lor/lor_h1_impl.hpp
lor/lor_dg_impl.hpp
lor/lor_nd_impl.hpp
lor/lor_rt_impl.hpp
lor/lor_util.hpp
multigrid.hpp
nonlinearform.hpp
+12 -10
View File
@@ -812,7 +812,7 @@ protected:
const FiniteElement & test_fe) const
{
return (trial_fe.GetDim() == 1 && test_fe.GetDim() == 1 &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
}
@@ -884,7 +884,7 @@ protected:
const FiniteElement & trial_fe,
const FiniteElement & test_fe) const
{
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
}
@@ -919,7 +919,7 @@ protected:
const FiniteElement & trial_fe,
const FiniteElement & test_fe) const
{
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
}
@@ -1600,7 +1600,7 @@ public:
{
return (trial_fe.GetCurlDim() == 3 && test_fe.GetRangeDim() == 3 &&
trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
}
@@ -1635,7 +1635,7 @@ public:
{
return (trial_fe.GetDim() == 2 && test_fe.GetDim() == 2 &&
trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
}
@@ -1669,7 +1669,7 @@ public:
{
return (trial_fe.GetDim() == 2 && test_fe.GetDim() == 2 &&
trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
}
@@ -1760,7 +1760,7 @@ public:
const FiniteElement & test_fe) const
{
return (trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
}
@@ -1793,7 +1793,7 @@ public:
const FiniteElement & test_fe) const
{
return (trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
test_fe.GetDerivType() == mfem::FiniteElement::DIV );
}
@@ -1832,7 +1832,7 @@ public:
const FiniteElement & test_fe) const
{
return (trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
test_fe.GetDerivType() == mfem::FiniteElement::GRAD
);
@@ -1973,7 +1973,7 @@ protected:
const FiniteElement & test_fe) const override
{
return (trial_fe.GetCurlDim() == 3 && test_fe.GetRangeDim() == 3 &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
}
@@ -3459,6 +3459,8 @@ public:
const IntegrationRule &GetRule(int order, Geometry::Type geom);
real_t GetPenaltyParameter() const { return kappa; }
private:
void SetupPA(const FiniteElementSpace &fes, FaceType type);
};
+4 -4
View File
@@ -912,7 +912,7 @@ ConduitDataCollection::GridFunctionToBlueprintField(mfem::GridFunction *gf,
if (vdim == 1) // scalar case
{
n_field["values"].set_external(gf->GetData(),
n_field["values"].set_external(const_cast<real_t *>(gf->HostRead()),
ndofs);
}
else // vector case
@@ -925,18 +925,18 @@ ConduitDataCollection::GridFunctionToBlueprintField(mfem::GridFunction *gf,
int vdim_stride = (ordering == Ordering::byNODES ? ndofs : 1);
index_t offset = 0;
index_t stride = sizeof(double) * entry_stride;
index_t stride = sizeof(real_t) * entry_stride;
for (int d = 0; d < vdim; d++)
{
std::ostringstream oss;
oss << "v" << d;
std::string comp_name = oss.str();
n_field["values"][comp_name].set_external(gf->GetData(),
n_field["values"][comp_name].set_external(const_cast<real_t *>(gf->HostRead()),
ndofs,
offset,
stride);
offset += sizeof(double) * vdim_stride;
offset += sizeof(real_t) * vdim_stride;
}
}
+266
View File
@@ -0,0 +1,266 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "derefmat_op.hpp"
#include "fes_kernels.hpp"
/// \cond DO_NOT_DOCUMENT
namespace mfem
{
namespace internal
{
template <Ordering::Type Order, bool Atomic>
static void DerefMultKernelImpl(const DerefineMatrixOp &op, const Vector &x,
Vector &y)
{
DerefineMatrixOpMultFunctor<Order, Atomic> func;
func.xptr = x.Read();
y.UseDevice();
y = 0.;
func.yptr = y.ReadWrite();
func.bsptr = op.block_storage.Read();
func.boptr = op.block_offsets.Read();
func.brptr = op.block_row_idcs_offsets.Read();
func.bcptr = op.block_col_idcs_offsets.Read();
func.rptr = op.row_idcs.Read();
func.cptr = op.col_idcs.Read();
func.vdims = op.fespace->GetVDim();
func.nblocks = op.block_offsets.Size();
func.width = op.Width() / func.vdims;
func.height = op.Height() / func.vdims;
func.Run(op.max_rows);
}
} // namespace internal
DerefineMatrixOp::DerefineMatrixOp(FiniteElementSpace &fespace_, int old_ndofs,
const Table *old_elem_dof,
const Table *old_elem_fos)
: Operator(fespace_.GetVSize(), old_ndofs * fespace_.GetVDim()),
fespace(&fespace_)
{
static Kernels kernels;
constexpr int max_team_size = 256;
/// TODO: Implement DofTransformation support
MFEM_VERIFY(fespace->Nonconforming(),
"Not implemented for conforming meshes.");
MFEM_VERIFY(old_ndofs, "Missing previous (finer) space.");
MFEM_VERIFY(fespace->GetNDofs() <= old_ndofs,
"Previous space is not finer.");
const CoarseFineTransformations &dtrans =
fespace->GetMesh()->ncmesh->GetDerefinementTransforms();
MFEM_ASSERT(dtrans.embeddings.Size() == old_elem_dof->Size(), "");
const bool is_dg = fespace->FEColl()->GetContType()
== FiniteElementCollection::DISCONTINUOUS;
DenseMatrix localRVO; // for variable-order only
DenseTensor localR[Geometry::NumGeom];
int total_rows = 0;
int total_cols = 0;
block_offsets.SetSize(dtrans.embeddings.Size());
block_offsets.HostWrite();
if (fespace->IsVariableOrder())
{
// TODO: any potential for some compression here?
// determine storage size and offsets
block_offsets[0] = 0;
int total_size = 0;
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
{
const Embedding &emb = dtrans.embeddings[k];
const FiniteElement *fe = fespace->GetFE(emb.parent);
const int ldof = fe->GetDof();
if (k + 1 < dtrans.embeddings.Size())
{
block_offsets[k + 1] = block_offsets[k] + ldof * ldof;
}
total_rows += ldof;
total_cols += ldof;
total_size += ldof * ldof;
}
block_storage.SetSize(total_size);
}
else
{
// compression scheme:
// block_offsets is the start of each block, potentially repeated
// only need to store localR for used shapes
Mesh::GeometryList elem_geoms(*fespace->GetMesh());
int geom_offsets[Geometry::NumGeom];
{
int size = 0;
for (int i = 0; i < elem_geoms.Size(); ++i)
{
fespace->GetLocalDerefinementMatrices(elem_geoms[i],
localR[elem_geoms[i]]);
geom_offsets[elem_geoms[i]] = size;
size += localR[elem_geoms[i]].TotalSize();
}
block_storage.SetSize(size);
// copy blocks into block_storage
auto bs_ptr = block_storage.HostWrite();
for (int i = 0; i < elem_geoms.Size(); ++i)
{
std::copy(localR[elem_geoms[i]].Data(),
localR[elem_geoms[i]].Data()
+ localR[elem_geoms[i]].TotalSize(),
bs_ptr);
bs_ptr += localR[elem_geoms[i]].TotalSize();
}
}
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
{
const Embedding &emb = dtrans.embeddings[k];
Geometry::Type geom =
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
auto size = localR[geom].SizeI() * localR[geom].SizeJ();
total_rows += localR[geom].SizeI();
total_cols += localR[geom].SizeJ();
// set block offsets and sizes
block_offsets[k] = geom_offsets[geom] + size * emb.matrix;
}
}
row_idcs.SetSize(total_rows);
row_idcs.HostWrite();
col_idcs.SetSize(total_cols);
col_idcs.HostWrite();
block_row_idcs_offsets.SetSize(dtrans.embeddings.Size() + 1);
block_row_idcs_offsets.HostWrite();
block_col_idcs_offsets.SetSize(dtrans.embeddings.Size() + 1);
block_col_idcs_offsets.HostWrite();
block_row_idcs_offsets[0] = 0;
block_col_idcs_offsets[0] = 0;
// compute index information
Array<int> dofs, old_dofs;
max_rows = 1;
{
Array<int> mark(fespace->GetNDofs());
mark = 0;
auto bs_ptr = block_storage.HostWrite();
int ridx = 0;
int cidx = 0;
int num_marked = 0;
for (int k = 0; k < dtrans.embeddings.Size(); k++)
{
const Embedding &emb = dtrans.embeddings[k];
Geometry::Type geom =
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
if (fespace->IsVariableOrder())
{
const FiniteElement *fe = fespace->GetFE(emb.parent);
const DenseTensor &pmats = dtrans.point_matrices[geom];
const int ldof = fe->GetDof();
IsoparametricTransformation isotr;
isotr.SetIdentityTransformation(geom);
localRVO.SetSize(ldof, ldof);
isotr.SetPointMat(pmats(emb.matrix));
// Local restriction is size ldofxldof assuming that the parent
// and child are of same polynomial order.
fe->GetLocalRestriction(isotr, localRVO);
// copy block
auto size = localRVO.Height() * localRVO.Width();
std::copy(localRVO.Data(), localRVO.Data() + size, bs_ptr);
bs_ptr += size;
}
DenseMatrix &lR =
fespace->IsVariableOrder() ? localRVO : localR[geom](emb.matrix);
block_row_idcs_offsets[k + 1] =
block_row_idcs_offsets[k] + lR.Height();
block_col_idcs_offsets[k + 1] = block_col_idcs_offsets[k] + lR.Width();
max_rows = std::max(lR.Height(), max_rows);
// index information
fespace->elem_dof->GetRow(emb.parent, dofs);
old_elem_dof->GetRow(k, old_dofs);
MFEM_VERIFY(old_dofs.Size() == dofs.Size(),
"Parent and child must have same #dofs.");
for (int i = 0; i < lR.Height(); ++i, ++ridx)
{
if (!std::isfinite(lR(i, 0)))
{
row_idcs[ridx] = INT_MAX;
continue;
}
int r = dofs[i];
int m = (r >= 0) ? r : (-1 - r);
if (is_dg || !mark[m])
{
row_idcs[ridx] = r;
mark[m] = 1;
++num_marked;
}
else
{
row_idcs[ridx] = INT_MAX;
}
}
for (int i = 0; i < lR.Width(); ++i, ++cidx)
{
col_idcs[cidx] = old_dofs[i];
}
}
if (!is_dg && !fespace->IsVariableOrder())
{
MFEM_VERIFY(num_marked * fespace->GetVDim() == Height(),
"internal error: not all rows were set.");
}
}
// if not using GPU, set max_rows/max_cols to zero
if (Device::Allows(Backend::DEVICE_MASK))
{
max_rows = std::min(max_rows, max_team_size);
}
else
{
max_rows = 1;
}
}
void DerefineMatrixOp::Mult(const Vector &x, Vector &y) const
{
const bool is_dg = fespace->FEColl()->GetContType()
== FiniteElementCollection::DISCONTINUOUS;
// DG needs atomic summation
MultKernel::Run(fespace->GetOrdering(), is_dg, *this, x, y);
}
DerefineMatrixOp::Kernels::Kernels()
{
MultKernel::Specialization<Ordering::byNODES, false>::Add();
MultKernel::Specialization<Ordering::byVDIM, false>::Add();
MultKernel::Specialization<Ordering::byNODES, true>::Add();
MultKernel::Specialization<Ordering::byVDIM, true>::Add();
}
template <Ordering::Type Order, bool Atomic>
DerefineMatrixOp::MultKernelType DerefineMatrixOp::MultKernel::Kernel()
{
return internal::DerefMultKernelImpl<Order, Atomic>;
}
DerefineMatrixOp::MultKernelType
DerefineMatrixOp::MultKernel::Fallback(Ordering::Type, bool)
{
MFEM_ABORT("invalid MultKernel parameters");
}
} // namespace mfem
/// \endcond DO_NOT_DOCUMENT
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_DEREFMAT_OP
#define MFEM_DEREFMAT_OP
#include "fespace.hpp"
#include "kernel_dispatch.hpp"
namespace mfem
{
/// \cond DO_NOT_DOCUMENT
struct DerefineMatrixOp : public Operator
{
FiniteElementSpace *fespace;
/// offsets into block_storage
Array<int> block_offsets;
/// offsets into row_idcs
Array<int> block_row_idcs_offsets;
/// offsets into col_idcs
Array<int> block_col_idcs_offsets;
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
/// negative means the row data should be negated.
Array<int> row_idcs;
/// mapping for col dofs, negative means the col data should be negated.
Array<int> col_idcs;
/// dense block matrices which can be reused to construct the full matrix
/// operation. These are stored contiguously and blocks have no restrictions
/// on shape (can be rectangle and differ from block to block).
Vector block_storage;
/// maximum height of any block in block_storage for GPU
/// parallelization, or 1 for CPU runs.
int max_rows;
using MultKernelType = void (*)(const DerefineMatrixOp &, const Vector &,
Vector &);
/// template args: ordering, atomic
MFEM_REGISTER_KERNELS(MultKernel, MultKernelType, (Ordering::Type, bool));
struct Kernels
{
Kernels();
};
void Mult(const Vector &x, Vector &y) const;
DerefineMatrixOp(FiniteElementSpace &fespace_, int old_ndofs,
const Table *old_elem_dof, const Table *old_elem_fos);
};
/// \endcond DO_NOT_DOCUMENT
} // namespace mfem
#endif
+2 -2
View File
@@ -568,7 +568,7 @@ struct ThreadBlocks
int z = 1;
};
#if (defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
#if defined(MFEM_USE_CUDA_OR_HIP)
template <typename func_t>
__global__ void forall_kernel_shmem(func_t f, int n)
{
@@ -591,7 +591,7 @@ void forall(func_t f,
if (Device::Allows(Backend::CUDA_MASK) ||
Device::Allows(Backend::HIP_MASK))
{
#if (defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
#if defined(MFEM_USE_CUDA_OR_HIP)
// int gridsize = (N + Z - 1) / Z;
int num_bytes = num_shmem * sizeof(decltype(shmem));
dim3 block_size(blocks.x, blocks.y, blocks.z);
+24
View File
@@ -259,6 +259,30 @@ inline void FaceIdxToVolIdx3D(const int index, const int size1d,
i = yz_plane ? level : _i;
}
MFEM_HOST_DEVICE
inline int FaceIdxToVolIdx(int dim, int i, int size1d, int face0, int face1,
int side, int orientation)
{
if (dim == 2)
{
int ix, iy;
internal::FaceIdxToVolIdx2D(i, size1d, face0, face1, side, ix, iy);
return ix + iy*size1d;
}
else if (dim == 3)
{
int ix, iy, iz;
internal::FaceIdxToVolIdx3D(i, size1d, face0, face1, side, orientation,
ix, iy, iz);
return ix + size1d*iy + size1d*size1d*iz;
}
else
{
MFEM_ABORT_KERNEL("Invalid dimension");
return -1;
}
};
} // namespace internal
} // namespace mfem
+249
View File
@@ -0,0 +1,249 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_FES_KERNELS_HPP
#define MFEM_FES_KERNELS_HPP
#include "../general/forall.hpp"
#include <climits>
namespace mfem
{
/// \cond DO_NOT_DOCUMENT
namespace internal
{
///
/// Implements matrix-vector multiply $y = A x$ for a sparse matrix composed of
/// a sum of smaller dense blocks. There is additional permutation/sign
/// information associated with each block. The base class only implements
/// helper routines such as computing block widths, index into x, index into y,
/// and column in A given sub-block information.
/// @sa DerefineMatrixOpMultFunctor
///
/// @tparam Order vdim ordering for x and y. Note that for Diag = false this is
/// ignored for x as x has a special interleaved order.
/// @tparam Base used for the curious recurring template pattern (CRTP) so the
/// base class can access child class fields without virtual functions
/// @tparam Diag true if this corresponds to the diagonal block (coarse element
/// and fine element are on our rank), false otherwise (coarse element is on our
/// rank, fine element is on a different rank).
///
template <Ordering::Type Order, class Base, bool Diag = true>
struct DerefineMatrixOpFunctorBase;
template <class Base>
struct DerefineMatrixOpFunctorBase<Ordering::byNODES, Base, true>
{
/// block column indices offsets
const int *bcptr;
/// column indices
const int *cptr;
int MFEM_HOST_DEVICE BlockWidth(int k) const
{
return bcptr[k + 1] - bcptr[k];
}
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
{
col = cptr[bcptr[k] + j];
if (col < 0)
{
col = -1 - col;
sign = -sign;
}
}
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int) const
{
return col + vdim * static_cast<const Base *>(this)->width;
}
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
{
return row + vdim * static_cast<const Base *>(this)->height;
}
};
template <class Base>
struct DerefineMatrixOpFunctorBase<Ordering::byVDIM, Base, true>
{
/// block column indices offsets
const int *bcptr;
/// column indices
const int *cptr;
int MFEM_HOST_DEVICE BlockWidth(int k) const
{
return bcptr[k + 1] - bcptr[k];
}
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
{
col = cptr[bcptr[k] + j];
if (col < 0)
{
col = -1 - col;
sign = -sign;
}
}
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int) const
{
return vdim + col * static_cast<const Base *>(this)->vdims;
}
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
{
return vdim + row * static_cast<const Base *>(this)->vdims;
}
};
template <class Base>
struct DerefineMatrixOpFunctorBase<Ordering::byNODES, Base, false>
{
/// receive segment offsets
const int *segptr;
/// receive segment index
const int *rsptr;
/// off-diagonal block column offsets
const int *coptr;
/// off-diagonal block widths
const int *bwptr;
int MFEM_HOST_DEVICE BlockWidth(int k) const { return bwptr[k]; }
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
{
col = coptr[k] + j;
}
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int k) const
{
int tmp = rsptr[k];
int segwidth = segptr[tmp + 1] - segptr[tmp];
return segptr[tmp] * static_cast<const Base *>(this)->vdims + col +
vdim * segwidth;
}
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
{
return row + vdim * static_cast<const Base *>(this)->height;
}
};
template <class Base>
struct DerefineMatrixOpFunctorBase<Ordering::byVDIM, Base, false>
{
/// receive segment offsets
const int *segptr;
/// receive segment index
const int *rsptr;
/// off-diagonal block column offsets
const int *coptr;
/// off-diagonal block widths
const int *bwptr;
int MFEM_HOST_DEVICE BlockWidth(int k) const { return bwptr[k]; }
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
{
col = coptr[k] + j;
}
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int k) const
{
int tmp = rsptr[k];
int segwidth = segptr[tmp + 1] - segptr[tmp];
return segptr[tmp] * static_cast<const Base *>(this)->vdims + col +
vdim * segwidth;
}
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
{
return vdim + row * static_cast<const Base *>(this)->vdims;
}
};
/// internally used to implement the derefinement operator Mult diagonal
/// block
template <Ordering::Type Order, bool Atomic, bool Diag = true>
struct DerefineMatrixOpMultFunctor
: public DerefineMatrixOpFunctorBase<
Order, DerefineMatrixOpMultFunctor<Order, Atomic, Diag>, Diag>
{
const real_t *xptr;
real_t *yptr;
/// block storage
const real_t *bsptr;
/// block offsets
const int *boptr;
/// block row index offsets
const int *brptr;
/// row indices
const int *rptr;
// number of blocks
int nblocks;
// number of components
int vdims;
/// overall operator height (for vdim = 1)
int height;
/// overall operator width (for vdim = 1)
int width;
void MFEM_HOST_DEVICE operator()(int kidx) const
{
int k = kidx % nblocks;
int vdim = kidx / nblocks;
int block_height = brptr[k + 1] - brptr[k];
int block_width = this->BlockWidth(k);
MFEM_FOREACH_THREAD(i, x, block_height)
{
int row = rptr[brptr[k] + i];
int rsign = 1;
if (row < 0)
{
row = -1 - row;
rsign = -1;
}
if (row < INT_MAX)
{
// row not marked as unused
real_t sum = 0;
for (int j = 0; j < block_width; ++j)
{
int col, sign = rsign;
this->Col(j, k, col, sign);
sum += sign * bsptr[boptr[k] + i + j * block_height] *
xptr[this->IndexX(col, vdim, k)];
}
#if defined(__CUDA_ARCH__) or defined(__HIP_DEVICE_COMPILE__)
if (Atomic)
{
atomicAdd(yptr + this->IndexY(row, vdim), sum);
}
else
#endif
{
yptr[this->IndexY(row, vdim)] += sum;
}
}
}
}
/// N is the max block row size (doesn't have to be a power of 2)
void Run(int N) const { forall_2D(nblocks * vdims, N, 1, *this); }
};
} // namespace internal
/// \endcond DO_NOT_DOCUMENT
} // namespace mfem
#endif
+13 -6
View File
@@ -17,6 +17,9 @@
#include "fem.hpp"
#include "ceed/interface/util.hpp"
#include "derefmat_op.hpp"
#include <algorithm>
#include <cmath>
#include <cstdarg>
@@ -24,9 +27,9 @@ using namespace std;
namespace mfem
{
template <> void Ordering::
DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim, Array<int> &dofs)
template <>
void Ordering::DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim,
Array<int> &dofs)
{
// static method
int size = dofs.Size();
@@ -40,8 +43,9 @@ DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim, Array<int> &dofs)
}
}
template <> void Ordering::
DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim, Array<int> &dofs)
template <>
void Ordering::DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim,
Array<int> &dofs)
{
// static method
int size = dofs.Size();
@@ -55,7 +59,6 @@ DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim, Array<int> &dofs)
}
}
FiniteElementSpace::FiniteElementSpace()
: mesh(NULL), fec(NULL), vdim(0), ordering(Ordering::byNODES),
ndofs(0), nvdofs(0), nedofs(0), nfdofs(0), nbdofs(0),
@@ -4244,7 +4247,11 @@ void FiniteElementSpace::Update(bool want_transform)
case Mesh::DEREFINE:
{
BuildConformingInterpolation();
#if 0
Th.Reset(DerefinementMatrix(old_ndofs, old_elem_dof, old_elem_fos));
#else
Th.Reset(new DerefineMatrixOp(*this, old_ndofs, old_elem_dof, old_elem_fos));
#endif
if (IsVariableOrder())
{
if (cP && cR_hp)
+2 -1
View File
@@ -113,7 +113,7 @@ class QuadratureSpace;
class QuadratureInterpolator;
class FaceQuadratureInterpolator;
class PRefinementTransferOperator;
struct DerefineMatrixOp;
/** @brief Class FiniteElementSpace - responsible for providing FEM view of the
mesh, mainly managing the set of degrees of freedom.
@@ -246,6 +246,7 @@ class FiniteElementSpace
friend class PRefinementTransferOperator;
friend void Mesh::Swap(Mesh &, bool);
friend class LORBase;
friend struct DerefineMatrixOp;
protected:
/// The mesh that FE space lives on (not owned).
+29 -29
View File
@@ -346,13 +346,13 @@ private:
template<typename T>
T operator() (const blitz::TinyVector<T,3>& x) const
{
int el_order=el->GetOrder();
T u1[el_order+1];
T u2[el_order+1];
T u3[el_order+1];
TmplPoly_1D::CalcBernstein(el_order, x[0], u1);
TmplPoly_1D::CalcBernstein(el_order, x[1], u2);
TmplPoly_1D::CalcBernstein(el_order, x[2], u3);
const int el_order = el->GetOrder();
std::vector<T> u1(el_order+1);
std::vector<T> u2(el_order+1);
std::vector<T> u3(el_order+1);
TmplPoly_1D::CalcBernstein(el_order, x[0], u1.data());
TmplPoly_1D::CalcBernstein(el_order, x[1], u2.data());
TmplPoly_1D::CalcBernstein(el_order, x[2], u3.data());
const Array<int>& dof_map=el->GetDofMap();
@@ -370,17 +370,17 @@ private:
template<typename T>
blitz::TinyVector<T,3> grad(const blitz::TinyVector<T,3>& x) const
{
int el_order=el->GetOrder();
T u1[el_order+1];
T u2[el_order+1];
T u3[el_order+1];
T d1[el_order+1];
T d2[el_order+1];
T d3[el_order+1];
const int el_order = el->GetOrder();
std::vector<T> u1(el_order+1);
std::vector<T> u2(el_order+1);
std::vector<T> u3(el_order+1);
std::vector<T> d1(el_order+1);
std::vector<T> d2(el_order+1);
std::vector<T> d3(el_order+1);
TmplPoly_1D::CalcBernstein(el_order,x[0], u1, d1);
TmplPoly_1D::CalcBernstein(el_order,x[1], u2, d2);
TmplPoly_1D::CalcBernstein(el_order,x[2], u3, d3);
TmplPoly_1D::CalcBernstein(el_order,x[0], u1.data(), d1.data());
TmplPoly_1D::CalcBernstein(el_order,x[1], u2.data(), d2.data());
TmplPoly_1D::CalcBernstein(el_order,x[2], u3.data(), d3.data());
blitz::TinyVector<T,3> res(T(0.0),T(0.0),T(0.0));
@@ -415,11 +415,11 @@ private:
template<typename T>
T operator() (const blitz::TinyVector<T,2>& x) const
{
int el_order=el->GetOrder();
T u1[el_order+1];
T u2[el_order+1];
TmplPoly_1D::CalcBernstein(el_order, x[0], u1);
TmplPoly_1D::CalcBernstein(el_order, x[1], u2);
const int el_order = el->GetOrder();
std::vector<T> u1(el_order+1);
std::vector<T> u2(el_order+1);
TmplPoly_1D::CalcBernstein(el_order, x[0], u1.data());
TmplPoly_1D::CalcBernstein(el_order, x[1], u2.data());
const Array<int>& dof_map=el->GetDofMap();
@@ -437,14 +437,14 @@ private:
template<typename T>
blitz::TinyVector<T,2> grad(const blitz::TinyVector<T,2>& x) const
{
int el_order=el->GetOrder();
T u1[el_order+1];
T u2[el_order+1];
T d1[el_order+1];
T d2[el_order+1];
const int el_order = el->GetOrder();
std::vector<T> u1(el_order+1);
std::vector<T> u2(el_order+1);
std::vector<T> d1(el_order+1);
std::vector<T> d2(el_order+1);
TmplPoly_1D::CalcBernstein(el_order,x[0], u1, d1);
TmplPoly_1D::CalcBernstein(el_order,x[1], u2, d2);
TmplPoly_1D::CalcBernstein(el_order,x[0], u1.data(), d1.data());
TmplPoly_1D::CalcBernstein(el_order,x[1], u2.data(), d2.data());
blitz::TinyVector<T,2> res(T(0.0),T(0.0));
+1 -1
View File
@@ -544,7 +544,7 @@ public:
Specifically, given the Dirichlet data $u_D$, the linear form assembles the
following integrals on the boundary:
$$
\sigma \langle u_D, (Q \nabla v)) \cdot n \rangle + \kappa \langle {h^{-1} Q} u_D, v \rangle,
\sigma \langle u_D, (Q \nabla v) \cdot n \rangle + \kappa \langle {h^{-1} Q} u_D, v \rangle,
$$
where Q is a scalar or matrix diffusion coefficient and v is the test
function. The parameters $\sigma$ and $\kappa$ should be the same as the ones
+1 -1
View File
@@ -48,7 +48,7 @@ void LORBase::AddIntegratorsAndMarkers(BilinearForm &a_from,
for (int i=0; i<integrators->Size(); ++i)
{
BilinearFormIntegrator *integrator = (*integrators)[i];
if (markers[i] != nullptr)
if (markers[i])
{
(a_to.*add_integrator_marker)(integrator, *markers[i]);
}
+264 -22
View File
@@ -14,9 +14,11 @@
#include "../../general/forall.hpp"
#include <climits>
#include "../pbilinearform.hpp"
#include "../../fem/fe/face_map_utils.hpp"
// Specializations
#include "lor_h1.hpp"
#include "lor_dg.hpp"
#include "lor_nd.hpp"
#include "lor_rt.hpp"
@@ -54,17 +56,18 @@ bool BatchedLORAssembly::FormIsSupported(BilinearForm &a)
// Batched LOR requires all tensor elements
if (!UsesTensorBasis(*a.FESpace())) { return false; }
if (dynamic_cast<const H1_FECollection*>(fec))
if (dynamic_cast<const H1_FECollection*>(fec) ||
dynamic_cast<const DG_FECollection*>(fec))
{
if (HasIntegrators<DiffusionIntegrator, MassIntegrator>(a)) { return true; }
return HasIntegrators<DiffusionIntegrator, MassIntegrator>(a);
}
else if (dynamic_cast<const ND_FECollection*>(fec))
{
if (HasIntegrators<CurlCurlIntegrator, VectorFEMassIntegrator>(a)) { return true; }
return HasIntegrators<CurlCurlIntegrator, VectorFEMassIntegrator>(a);
}
else if (dynamic_cast<const RT_FECollection*>(fec))
{
if (HasIntegrators<DivDivIntegrator, VectorFEMassIntegrator>(a)) { return true; }
return HasIntegrators<DivDivIntegrator, VectorFEMassIntegrator>(a);
}
return false;
}
@@ -75,12 +78,14 @@ void BatchedLORAssembly::FormLORVertexCoordinates(FiniteElementSpace &fes_ho,
Mesh &mesh_ho = *fes_ho.GetMesh();
mesh_ho.EnsureNodes();
const bool dg = fes_ho.IsDGSpace();
// Get nodal points at the LOR vertices
const int dim = mesh_ho.Dimension();
const int sdim = mesh_ho.SpaceDimension();
const int nel_ho = mesh_ho.GetNE();
const int order = fes_ho.GetMaxElementOrder();
const int nd1d = order + 1;
const int nd1d = dg ? order + 2 : order + 1;
const int ndof_per_el = static_cast<int>(pow(nd1d, dim));
const GridFunction *nodal_gf = mesh_ho.GetNodes();
@@ -92,7 +97,8 @@ void BatchedLORAssembly::FormLORVertexCoordinates(FiniteElementSpace &fes_ho,
Vector nodal_evec(nodal_restriction->Height());
nodal_restriction->Mult(*nodal_gf, nodal_evec);
IntegrationRule ir = GetCollocatedIntRule(fes_ho);
const IntegrationRule ir = GetLobattoIntRule(
mesh_ho.GetTypicalElementGeometry(), nd1d);
// Map from nodal E-vector to Q-vector at the LOR vertex points
X_vert.SetSize(sdim*ndof_per_el*nel_ho);
@@ -159,6 +165,7 @@ int BatchedLORAssembly::FillI(SparseMatrix &A) const
const auto K = dof_glob2loc_offsets_.Read();
const auto map = Reshape(sparse_mapping.Read(), nnz_per_row, ndof_per_el);
auto I = A.WriteI();
mfem::forall(nvdof + 1, [=] MFEM_HOST_DEVICE (int ii) { I[ii] = 0; });
@@ -358,6 +365,177 @@ void BatchedLORAssembly::FillJAndData(SparseMatrix &A) const
});
}
void BatchedLORAssembly::SparseIJToCSR_DG(OperatorHandle &A) const
{
const int ndof_per_el = fes_ho.GetFE(0)->GetDof();
const int nel_ho = fes_ho.GetNE();
const int nnz_per_row = sparse_ij.Size()/ndof_per_el/nel_ho;
const int dim = fes_ho.GetMesh()->Dimension();
const int nrows = nel_ho*ndof_per_el;
const int p = fes_ho.GetMaxElementOrder();
const int pp1 = p + 1;
const int nnz = nrows*nnz_per_row;
const int face_nbr_vsize = [&]()
{
#ifdef MFEM_USE_MPI
if (auto *par_fes = dynamic_cast<ParFiniteElementSpace*>(&fes_ho))
{
return par_fes->GetFaceNbrVSize();
}
#endif
return 0;
}();
// If A contains an existing SparseMatrix, reuse it (and try to reuse its
// I, J, A arrays if they are big enough)
SparseMatrix *A_mat = A.Is<SparseMatrix>();
if (!A_mat)
{
A_mat = new SparseMatrix;
A.Reset(A_mat);
}
// The second argument (nrows + face_nbr_vsize) accounts for additional
// columns contributed by DG face neighbors in parallel finite element
// spaces. In serial, face_nbr_vsize is set to 0.
A_mat->OverrideSize(nrows, nrows + face_nbr_vsize);
EnsureCapacity(A_mat->GetMemoryI(), nrows + 1);
EnsureCapacity(A_mat->GetMemoryJ(), nnz);
EnsureCapacity(A_mat->GetMemoryData(), nnz);
Array<int> nbr_info(nel_ho*3*2*dim);
auto h_nbr_info = Reshape(nbr_info.HostWrite(), nel_ho, 2*dim, 3);
const int num_faces = fes_ho.GetMesh()->GetNumFaces();
for (int f = 0; f < num_faces; f++)
{
Mesh::FaceInformation finfo = fes_ho.GetMesh()->GetFaceInformation(f);
int e0 = finfo.element[0].index;
int f0 = finfo.element[0].local_face_id;
if (finfo.IsBoundary())
{
h_nbr_info(e0,f0,0) = -1;
h_nbr_info(e0,f0,1)= -1;
h_nbr_info(e0,f0,2)= -1;
}
else if (finfo.IsShared())
{
// Face neighbors elements are indexed after the last local element
h_nbr_info(e0,f0,0) = nel_ho + finfo.element[1].index;
h_nbr_info(e0,f0,1)= finfo.element[1].orientation;
h_nbr_info(e0,f0,2)= finfo.element[1].local_face_id;
}
else if (finfo.IsInterior())
{
int e1 = finfo.element[1].index;
int f1 = finfo.element[1].local_face_id;
h_nbr_info(e0,f0,0) = e1;
h_nbr_info(e0,f0,1)= finfo.element[1].orientation;
h_nbr_info(e0,f0,2)= f1;
h_nbr_info(e1,f1,0) = e0;
h_nbr_info(e1,f1,1) = finfo.element[1].orientation;
h_nbr_info(e1,f1,2) = f0;
}
};
auto h_I = A_mat->HostWriteI();
h_I[0] = 0;
for (int i = 0; i < nrows; ++i)
{
const int iel_ho = i / ndof_per_el;
const int iloc = i % ndof_per_el;
static const int lex_map_2[4] = {3, 1, 0, 2};
static const int lex_map_3[6] = {4, 2, 1, 3, 0, 5};
const int local_i[3] = {iloc % pp1, (iloc/pp1)%pp1, iloc/pp1/pp1};
int bdr_count = 0;
for (int n_idx = 0; n_idx < dim; ++n_idx)
{
for (int e_i = 0; e_i < 2; ++e_i)
{
const int j_lex = e_i + n_idx*2;
const int f = (dim == 3) ? lex_map_3[j_lex]:lex_map_2[j_lex];
const bool boundary = (local_i[n_idx] == e_i * p);
if (boundary)
{
int neighbor_idx = h_nbr_info(iel_ho, f, 0);
if (neighbor_idx == -1)
{
++bdr_count;
}
}
}
}
h_I[i+1] = h_I[i] + (nnz_per_row - bdr_count);
}
const auto V = Reshape(sparse_ij.Read(), nnz_per_row, ndof_per_el, nel_ho);
auto J = A_mat->WriteJ();
auto AV = A_mat->WriteData();
auto I = A_mat->ReadI();
auto d_nbr_info = Reshape(nbr_info.Read(), nel_ho, 2*dim, 3);
mfem::forall(nrows, [=] MFEM_HOST_DEVICE (int i)
{
const int e = i / ndof_per_el;
const int iloc = i % ndof_per_el;
const int local_x = iloc % pp1;
const int local_y = (iloc/pp1)%pp1;
const int local_z = iloc/pp1/pp1;
const int local_i[3] = {local_x, local_y, local_z};
int offset = I[i];
static const int lex_map_2[4] = {3, 1, 0, 2};
static const int lex_map_3[6] = {4,2,1,3,0,5};
const int *lex_map = (dim == 2) ? lex_map_2 : lex_map_3;
AV[offset] = V(0, iloc, e);
J[offset] = i;
++offset;
for (int n_idx = 0; n_idx < dim; ++n_idx)
{
// qi is the face lexicographic index, obtained by taking the
// lexicographic index of the coordinates ommiting n_idx.
int qi = 0;
int stride = 1;
for (int d = 0; d < dim; ++d)
{
if (d != n_idx)
{
qi += local_i[d]*stride;
stride *= pp1;
}
}
for (int e_i = 0; e_i < 2; ++e_i)
{
const int j_lex = e_i + n_idx*2;
const int f = lex_map[j_lex];
const bool bdr = (local_i[n_idx] == e_i * p);
if (bdr)
{
const int nbr_e = d_nbr_info(e, f, 0);
const int nbr_ori = d_nbr_info(e, f, 1);
const int nbr_f = d_nbr_info(e, f, 2);
if (nbr_e != -1)
{
const int nbr_loc_idx = internal::FaceIdxToVolIdx(
dim, qi, pp1, f, nbr_f, 1, nbr_ori);
J[offset] = nbr_e*ndof_per_el + nbr_loc_idx;
AV[offset] = V(f+1, iloc, e);
++offset;
}
}
else
{
int shift = (e_i == 0) ? -1 : 1;
for (int n = 0; n < n_idx; ++n) { shift *= pp1; }
J[offset] = i + shift;
AV[offset] = V(f+1, iloc, e);
++offset;
}
}
}
});
}
void BatchedLORAssembly::SparseIJToCSR(OperatorHandle &A) const
{
const int nvdof = fes_ho.GetVSize();
@@ -372,12 +550,11 @@ void BatchedLORAssembly::SparseIJToCSR(OperatorHandle &A) const
}
A_mat->OverrideSize(nvdof, nvdof);
EnsureCapacity(A_mat->GetMemoryI(), nvdof + 1);
A_mat->GetMemoryI().New(nvdof+1, Device::GetDeviceMemoryType());
int nnz = FillI(*A_mat);
A_mat->GetMemoryJ().New(nnz, Device::GetDeviceMemoryType());
A_mat->GetMemoryData().New(nnz, Device::GetDeviceMemoryType());
const int nnz = FillI(*A_mat);
EnsureCapacity(A_mat->GetMemoryJ(), nnz);
EnsureCapacity(A_mat->GetMemoryData(), nnz);
FillJAndData(*A_mat);
}
@@ -431,6 +608,19 @@ void BatchedLORAssembly::AssembleWithoutBC(BilinearForm &a, OperatorHandle &A)
// Assemble the matrix, depending on what the form is.
// This fills in the arrays sparse_ij and sparse_mapping.
const FiniteElementCollection *fec = fes_ho.FEColl();
// Handle DG case separately, because assembly of CSR matrix requires
// handling face terms.
if (dynamic_cast<const DG_FECollection*>(fec))
{
if (HasIntegrators<DiffusionIntegrator, MassIntegrator>(a))
{
AssemblyKernel<BatchedLOR_DG>(a);
}
SparseIJToCSR_DG(A);
return;
}
if (dynamic_cast<const H1_FECollection*>(fec))
{
if (HasIntegrators<DiffusionIntegrator, MassIntegrator>(a))
@@ -453,10 +643,47 @@ void BatchedLORAssembly::AssembleWithoutBC(BilinearForm &a, OperatorHandle &A)
}
}
return SparseIJToCSR(A);
SparseIJToCSR(A);
}
#ifdef MFEM_USE_MPI
void BatchedLORAssembly::ParAssemble_DG(SparseMatrix &A_local,
OperatorHandle &A)
{
auto &par_fes = static_cast<ParFiniteElementSpace&>(fes_ho);
// handle the case when 'a' contains off-diagonal
const int lvsize = par_fes.GetVSize();
const Array<HYPRE_BigInt> &face_nbr_glob_ldof =
par_fes.GetFaceNbrGlobalDofMapArray();
const HYPRE_BigInt ldof_offset = par_fes.GetMyDofOffset();
const int nnz_local = A_local.NumNonZeroElems();
Array<HYPRE_BigInt> glob_J(nnz_local);
const HYPRE_BigInt *d_face_nbr_glob_ldof = face_nbr_glob_ldof.Read();
const int *d_J = A_local.ReadJ();
HYPRE_BigInt *d_glob_J = glob_J.Write();
mfem::forall(nnz_local, [=] MFEM_HOST_DEVICE (int i)
{
if (d_J[i] < lvsize)
{
d_glob_J[i] = d_J[i] + ldof_offset;
}
else
{
d_glob_J[i] = d_face_nbr_glob_ldof[d_J[i] - lvsize];
}
});
A.Reset(new HypreParMatrix(
par_fes.GetComm(), lvsize, par_fes.GlobalVSize(),
par_fes.GlobalVSize(), A_local.HostReadWriteI(),
glob_J.HostReadWrite(), A_local.HostReadWriteData(),
par_fes.GetDofOffsets(), par_fes.GetDofOffsets()));
}
void BatchedLORAssembly::ParAssemble(
BilinearForm &a, const Array<int> &ess_dofs, OperatorHandle &A)
{
@@ -464,13 +691,18 @@ void BatchedLORAssembly::ParAssemble(
OperatorHandle A_local;
AssembleWithoutBC(a, A_local);
ParBilinearForm *pa =
dynamic_cast<ParBilinearForm*>(&a);
pa->ParallelRAP(*A_local.As<SparseMatrix>(), A, true);
A.As<HypreParMatrix>()->EliminateBC(ess_dofs,
Operator::DiagonalPolicy::DIAG_ONE);
if (dynamic_cast<const DG_FECollection*>(fes_ho.FEColl()))
{
ParAssemble_DG(*A_local.As<SparseMatrix>(), A);
}
else
{
ParBilinearForm *pa =
dynamic_cast<ParBilinearForm*>(&a);
pa->ParallelRAP(*A_local.As<SparseMatrix>(), A, true);
A.As<HypreParMatrix>()->EliminateBC(ess_dofs,
Operator::DiagonalPolicy::DIAG_ONE);
}
}
#endif
@@ -504,12 +736,22 @@ BatchedLORAssembly::BatchedLORAssembly(FiniteElementSpace &fes_ho_)
FormLORVertexCoordinates(fes_ho, X_vert);
}
IntegrationRule GetCollocatedIntRule(FiniteElementSpace &fes)
IntegrationRule GetLobattoIntRule(Geometry::Type geom, int nd1d)
{
IntegrationRules irs(0, Quadrature1D::GaussLobatto);
const Geometry::Type geom = fes.GetMesh()->GetTypicalElementGeometry();
const int nd1d = fes.GetMaxElementOrder() + 1;
return irs.Get(geom, 2*nd1d - 3);
}
IntegrationRule GetCollocatedIntRule(FiniteElementSpace &fes)
{
const Geometry::Type geom = fes.GetMesh()->GetTypicalElementGeometry();
return GetLobattoIntRule(geom, fes.GetMaxElementOrder() + 1);
}
IntegrationRule GetCollocatedFaceIntRule(FiniteElementSpace &fes)
{
const Geometry::Type geom = fes.GetMesh()->GetTypicalFaceGeometry();
return GetLobattoIntRule(geom, fes.GetMaxElementOrder() + 1);
}
} // namespace mfem
+32 -2
View File
@@ -25,6 +25,7 @@ namespace mfem
/// supported, currently:
///
/// - H1 diffusion + mass
/// - DG diffusion + mass (in progress)
/// - ND curl-curl + mass
/// - RT div-div + mass
///
@@ -73,6 +74,9 @@ public:
/// Return the vertices of the LOR mesh in E-vector format
const Vector &GetLORVertexCoordinates() { return X_vert; }
/// Specialized implementation of SparseIJToCSR for DG spaces.
void SparseIJToCSR_DG(OperatorHandle &A) const;
protected:
/// After assembling the "sparse IJ" format, convert it to CSR.
void SparseIJToCSR(OperatorHandle &A) const;
@@ -105,6 +109,9 @@ public:
void FillJAndData(SparseMatrix &A) const;
#ifdef MFEM_USE_MPI
/// Assemble the parallel DG matrix (with shared faces).
void ParAssemble_DG(SparseMatrix &A_local, OperatorHandle &A);
/// Assemble the system in parallel and place the result in @a A.
void ParAssemble(BilinearForm &a, const Array<int> &ess_dofs,
OperatorHandle &A);
@@ -128,9 +135,8 @@ void EnsureCapacity(Memory<T> &mem, int capacity)
/// Return the first domain integrator in the form @a i of type @a T.
template <typename T>
static T *GetIntegrator(BilinearForm &a)
static T *GetIntegrator(Array<BilinearFormIntegrator*> *integs)
{
Array<BilinearFormIntegrator*> *integs = a.GetDBFI();
if (integs != NULL)
{
for (auto *i : *integs)
@@ -144,8 +150,32 @@ static T *GetIntegrator(BilinearForm &a)
return nullptr;
}
template <typename T>
static T *GetIntegrator(BilinearForm &a)
{
return GetIntegrator<T>(a.GetDBFI());
}
template <typename T>
static T *GetInteriorFaceIntegrator(BilinearForm &a)
{
return GetIntegrator<T>(a.GetFBFI());
}
/// @brief Return the Gauss-Lobatto rule for geometry @a geom with @a nd1d
/// points per dimension.
IntegrationRule GetLobattoIntRule(Geometry::Type geom, int nd1d);
/// @brief Return the Gauss-Lobatto rule collocated with the element nodes.
///
/// Assumes @a fes uses Gauss-Lobatto basis.
IntegrationRule GetCollocatedIntRule(FiniteElementSpace &fes);
/// @brief Return the Gauss-Lobatto rule collocated with face nodes.
///
/// Assumes @a fes uses Gauss-Lobatto basis.
IntegrationRule GetCollocatedFaceIntRule(FiniteElementSpace &fes);
template <typename INTEGRATOR>
void ProjectLORCoefficient(BilinearForm &a, CoefficientVector &coeff_vector)
{
+79
View File
@@ -0,0 +1,79 @@
// 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_LOR_DG
#define MFEM_LOR_DG
#include "lor_batched.hpp"
namespace mfem
{
// BatchedLORKernel specialization for DG spaces. Not user facing. See the
// classes BatchedLORAssembly and BatchedLORKernel .
class BatchedLOR_DG : BatchedLORKernel
{
IntegrationRule ir_face; ///< Collocated Gauss-Lobatto face quadrature rule.
real_t kappa; ///< DG penalty parameter.
public:
template <int ORDER, int SDIM> void Assemble2D();
template <int ORDER> void Assemble3D();
BatchedLOR_DG(BilinearForm &a,
FiniteElementSpace &fes_ho_,
Vector &X_vert_,
Vector &sparse_ij_,
Array<int> &sparse_mapping_)
: BatchedLORKernel(fes_ho_, X_vert_, sparse_ij_, sparse_mapping_),
ir_face(GetLobattoIntRule(fes_ho_.GetMesh()->GetTypicalFaceGeometry(),
fes_ho_.GetMaxElementOrder() + 1))
{
ProjectLORCoefficient<MassIntegrator>(a, c1);
ProjectLORCoefficient<DiffusionIntegrator>(a, c2);
auto *integ = GetInteriorFaceIntegrator<DGDiffusionIntegrator>(a);
if (integ)
{
kappa = integ->GetPenaltyParameter();
}
else
{
kappa = 0.0;
}
}
/// @brief Compute and return the face info array.
///
/// The face info array has shape (6, nf), where @a nf is the number of
/// faces. For each face @a i, the column (:,i) has entries (e0, f0, o0, e1,
/// f1, o1), where @a e is adjacent element, @a f is the local face index,
/// and @a o is the orientation. For boundary and shared faces, (e1, f1, o1)
/// are all set to -1.
Array<int> GetFaceInfo() const;
/// @brief Compute and return the boundary penalty factor.
///
/// The returned vector has shape (nq, nf), where @a nq is the number of
/// nodes per face, and @a nf is the number of faces.
///
/// The boundary penalty factor is $J_f / h = J_f^2 / J_e$ (since $h = J_e /
/// J_f$), where $J_f$ is the face Jacobian determinant, and $J_e$ is the
/// element Jacobian determinant.
Vector GetBdrPenaltyFactor() const;
/// Assemble the face penalty terms in the matrix @a sparse_ij.
void AssembleFaceTerms();
};
}
#include "lor_dg_impl.hpp"
#endif
+391
View File
@@ -0,0 +1,391 @@
// 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 "lor_util.hpp"
#include "../../linalg/dtensor.hpp"
#include "../../general/forall.hpp"
#include "../../mesh/face_nbr_geom.hpp"
#include "lor_dg.hpp"
namespace mfem
{
Array<int> BatchedLOR_DG::GetFaceInfo() const
{
Mesh &mesh = *fes_ho.GetMesh();
const int nf = mesh.GetNumFaces();
Array<int> face_info(nf * 6); // (e0, f0, o0, e1, f1, o1)
auto h_face_info = Reshape(face_info.HostWrite(), 6, nf);
for (int f = 0; f < nf; ++f)
{
auto finfo = mesh.GetFaceInformation(f);
h_face_info(0, f) = finfo.element[0].index;
h_face_info(1, f) = finfo.element[0].local_face_id;
h_face_info(2, f) = finfo.element[0].orientation;
if (finfo.IsLocal()) // Interior, non-shared face
{
h_face_info(3, f) = finfo.element[1].index;
h_face_info(4, f) = finfo.element[1].local_face_id;
h_face_info(5, f) = finfo.element[1].orientation;
}
else
{
h_face_info(3, f) = -1;
h_face_info(4, f) = -1;
h_face_info(5, f) = -1;
}
}
return face_info;
}
Vector BatchedLOR_DG::GetBdrPenaltyFactor() const
{
Mesh &mesh = *fes_ho.GetMesh();
const int nf = mesh.GetNumFaces();
Array<int> f_int(mesh.GetNFbyType(FaceType::Interior));
Array<int> f_bdr(mesh.GetNFbyType(FaceType::Boundary));
{
int i_int = 0;
int i_bdr = 0;
for (int i = 0; i < nf; ++i)
{
const auto f = mesh.GetFaceInformation(i);
if (f.IsBoundary())
{
f_bdr[i_bdr] = i;
++i_bdr;
}
else if (f.IsInterior())
{
f_int[i_int] = i;
++i_int;
}
}
}
const auto geom = fes_ho.GetMesh()->GetGeometricFactors(
ir, GeometricFactors::DETERMINANTS);
const int nq = ir_face.Size();
Vector face_Jh(nq * nf);
for (const FaceType ft : {FaceType::Interior, FaceType::Boundary})
{
const int nft = mesh.GetNFbyType(ft);
auto *geom_face = mesh.GetFaceGeometricFactors(
ir_face, FaceGeometricFactors::DETERMINANTS, ft);
const L2FaceValues fv = (ft == FaceType::Interior)
? L2FaceValues::DoubleValued
: L2FaceValues::SingleValued;
const int m = (fv == L2FaceValues::DoubleValued) ? 2 : 1;
auto *r = fes_ho.GetFaceRestriction(ElementDofOrdering::LEXICOGRAPHIC, ft, fv);
Vector detJ_r(nq * m * nft);
r->Mult(geom->detJ, detJ_r);
const auto *d_i = (ft == FaceType::Interior) ? f_int.Read() : f_bdr.Read();
const auto d_detJ_face = Reshape(geom_face->detJ.Read(), nq, nft);
const auto d_detJ_r = Reshape(detJ_r.Read(), nq, m, nft);
auto d_face_Jh = Reshape(face_Jh.Write(), nq, nf);
mfem::forall(nft * nq, [=] MFEM_HOST_DEVICE (int ii)
{
const int i = ii % nq;
const int f = ii / nq;
const real_t J_el = 0.5*(d_detJ_r(i, 0, f) + d_detJ_r(i, m==2?1:0, f));
const real_t J_f = d_detJ_face(i, f);
d_face_Jh(i, d_i[f]) = J_f * J_f / J_el;
});
}
return face_Jh;
}
void BatchedLOR_DG::AssembleFaceTerms()
{
Mesh &mesh = *fes_ho.GetMesh();
const int nnz_per_row = 1 + mesh.Dimension()*2;
const int pp1 = fes_ho.GetMaxElementOrder() + 1;
const int nel_ho = mesh.GetNE();
const int nf = mesh.GetNumFaces();
const int nd_face = ir_face.Size();
const int nd = ir.Size();
const int dim = mesh.Dimension();
Array<int> face_info = GetFaceInfo();
const auto d_face_info = Reshape(face_info.Read(), 6, nf);
Vector face_Jh = GetBdrPenaltyFactor();
const auto d_face_Jh = Reshape(face_Jh.Read(), nd_face, nf);
const auto *w_face = ir_face.GetWeights().Read();
// Penalty parameter (avoid capturing *this in lambda)
const real_t d_kappa = kappa;
// Get diffusion coefficient
const bool const_dq = c2.Size() == 1;
const auto DQ = const_dq?Reshape(c2.Read(),1,1):Reshape(c2.Read(),nd,nel_ho);
// Sparse matrix entries
auto V = Reshape(sparse_ij.ReadWrite(), nnz_per_row, nd, nel_ho);
mfem::forall(nf, [=] MFEM_HOST_DEVICE (int f)
{
const int f_0 = d_face_info(1, f);
const int f_1 = d_face_info(4, f);
const int nsides = (f_1 >= 0) ? 2 : 1;
for (int el_i = 0; el_i < nsides; ++el_i)
{
const int e = d_face_info(3*el_i, f);
const int o = d_face_info(3*el_i + 2, f);
const int v_idx = 1 + ((el_i == 0) ? f_0 : f_1);
for (int i = 0; i < nd_face; ++i)
{
const int ii = internal::FaceIdxToVolIdx(dim, i, pp1, f_0, f_1, el_i, o);
const real_t Jh = d_face_Jh(i, f);
const real_t dq = const_dq ? DQ(0,0) : DQ(ii, e);
V(v_idx, ii, e) = -dq*d_kappa*Jh*w_face[i];
}
}
});
}
template <int ORDER, int SDIM>
void BatchedLOR_DG::Assemble2D()
{
MFEM_VERIFY(SDIM == 2, "Surface meshes not currently supported for LOR-DG.")
static constexpr int pp1 = ORDER + 1;
static constexpr int ndof_per_el = pp1*pp1;
static constexpr int nnz_per_row = 5;
const int nel_ho = fes_ho.GetNE();
// Get element geometric factors; calling before AssembleFaceTerms, since
// in AssembleFaceTerms, element Jacobian determinants are used, potentially
// saving recomputation.
const auto factors = GeometricFactors::DETERMINANTS |
GeometricFactors::JACOBIANS;
const auto *geom = fes_ho.GetMesh()->GetGeometricFactors(ir, factors);
// Sparse matrix entries
sparse_ij.SetSize(nnz_per_row*ndof_per_el*nel_ho);
sparse_ij.UseDevice(true);
sparse_ij = 0.0;
auto V = Reshape(sparse_ij.ReadWrite(), nnz_per_row, pp1, pp1, nel_ho);
AssembleFaceTerms();
// Populate Gauss-Lobatto quadrature rule of size (p+1)
IntegrationRule ir_pp1;
QuadratureFunctions1D::GaussLobatto(pp1, &ir_pp1);
Vector glx_pp1(pp1), glw_pp1(pp1);
for (int i = 0; i < pp1; ++i)
{
glx_pp1[i] = ir_pp1[i].x;
glw_pp1[i] = ir_pp1[i].weight;
}
const auto *x_pp1 = glx_pp1.Read();
const auto *w_1d = glw_pp1.Read();
// Get coefficients for mass and diffusion
const bool const_mq = c1.Size() == 1;
const auto MQ = const_mq
? Reshape(c1.Read(), 1, 1, 1)
: Reshape(c1.Read(), pp1, pp1, nel_ho);
const bool const_dq = c2.Size() == 1;
const auto DQ = const_dq
? Reshape(c2.Read(), 1, 1, 1)
: Reshape(c2.Read(), pp1, pp1, nel_ho);
const auto detJ = Reshape(geom->detJ.Read(), pp1, pp1, nel_ho);
const auto J = Reshape(geom->J.Read(), pp1, pp1, 2, 2, nel_ho);
const auto W = Reshape(ir.GetWeights().Read(), pp1, pp1);
mfem::forall(nel_ho, [=] MFEM_HOST_DEVICE (int iel_ho)
{
for (int iy = 0; iy < pp1; ++iy)
{
for (int ix = 0; ix < pp1; ++ix)
{
const real_t mq = const_mq ? MQ(0,0,0) : MQ(ix, iy, iel_ho);
const real_t dq = const_dq ? DQ(0,0,0) : DQ(ix, iy, iel_ho);
for (int n_idx = 0; n_idx < 2; ++n_idx)
{
for (int e_i = 0; e_i < 2; ++e_i)
{
const int i_0 = (n_idx == 0) ? ix + e_i : ix;
const int j_0 = (n_idx == 1) ? iy + e_i : iy;
const bool bdr = (n_idx == 0 && (i_0 == 0 || i_0 == pp1)) ||
(n_idx == 1 && (j_0 == 0 || j_0 == pp1));
if (bdr) { continue; }
static constexpr int lex_map[] = {4, 2, 1, 3};
const int v_idx_lex = e_i + n_idx*2;
const int v_idx = lex_map[v_idx_lex];
const int w_idx = (n_idx == 0) ? iy : ix;
const int x_idx = (n_idx == 0) ? i_0 : j_0;
const real_t J1 = J(ix, iy, n_idx, !n_idx, iel_ho);
const real_t J2 = J(ix, iy, !n_idx, !n_idx, iel_ho);
const real_t Jh = (J1*J1 + J2*J2) / detJ(ix, iy, iel_ho);
V(v_idx, ix, iy, iel_ho) =
-dq * Jh * w_1d[w_idx] / (x_pp1[x_idx] - x_pp1[x_idx -1]);
}
}
V(0, ix, iy, iel_ho) = mq * detJ(ix, iy, iel_ho) * W(ix, iy);
for (int i = 1; i < nnz_per_row; ++i)
{
V(0, ix, iy, iel_ho) -= V(i, ix, iy, iel_ho);
}
}
}
});
}
template <int ORDER>
void BatchedLOR_DG::Assemble3D()
{
static constexpr int pp1 = ORDER + 1;
static constexpr int ndof_per_el = pp1*pp1*pp1;
static constexpr int nnz_per_row = 7;
const int nel_ho = fes_ho.GetNE();
// Get element geometric factors; calling before AssembleFaceTerms, since
// in AssembleFaceTerms, element Jacobian determinants are used, potentially
// saving recomputation.
const auto factors = GeometricFactors::DETERMINANTS |
GeometricFactors::JACOBIANS;
const auto geom = fes_ho.GetMesh()->GetGeometricFactors(ir, factors);
sparse_ij.SetSize(nnz_per_row*ndof_per_el*nel_ho);
sparse_ij.UseDevice(true);
sparse_ij = 0.0;
auto V = Reshape(sparse_ij.Write(), nnz_per_row, pp1, pp1, pp1, nel_ho);
AssembleFaceTerms();
// Populate Gauss-Lobatto quadrature rule of size (p+1)
IntegrationRule ir_pp1;
QuadratureFunctions1D::GaussLobatto(pp1, &ir_pp1);
Vector glx_pp1(pp1), glw_pp1(pp1);
for (int i = 0; i < pp1; ++i)
{
glx_pp1[i] = ir_pp1[i].x;
glw_pp1[i] = ir_pp1[i].weight;
}
const auto *x_pp1 = glx_pp1.Read();
const auto *w_1d = glw_pp1.Read();
const bool const_mq = c1.Size() == 1;
const auto MQ = const_mq
? Reshape(c1.Read(), 1, 1, 1, 1)
: Reshape(c1.Read(), pp1, pp1, pp1, nel_ho);
const bool const_dq = c2.Size() == 1;
const auto DQ = const_dq
? Reshape(c2.Read(), 1, 1, 1, 1)
: Reshape(c2.Read(), pp1, pp1, pp1, nel_ho);
const auto W = Reshape(ir.GetWeights().Read(), pp1, pp1, pp1);
const auto detJ = Reshape(geom->detJ.Read(), pp1, pp1, pp1, nel_ho);
const auto J = Reshape(geom->J.Read(), pp1, pp1, pp1, 3, 3, nel_ho);
mfem::forall(nel_ho, [=] MFEM_HOST_DEVICE (int iel_ho)
{
for (int iz = 0; iz < pp1; ++iz)
{
for (int iy = 0; iy < pp1; ++iy)
{
for (int ix = 0; ix < pp1; ++ix)
{
const real_t mq = const_mq ? MQ(0,0,0,0) : MQ(ix, iy, iz, iel_ho);
const real_t dq = const_dq ? DQ(0,0,0,0) : DQ(ix, iy, iz, iel_ho);
const real_t DETJ = detJ(ix, iy, iz, iel_ho);
for (int n_idx = 0; n_idx < 3; ++n_idx)
{
for (int e_i = 0; e_i < 2; ++e_i)
{
static constexpr int lex_map[] = {5,3,2,4,1,6};
const int v_idx_lex = e_i + n_idx*2;
const int v_idx = lex_map[v_idx_lex];
const int i_0 = (n_idx == 0) ? ix + e_i : ix;
const int j_0 = (n_idx == 1) ? iy + e_i : iy;
const int k_0 = (n_idx == 2) ? iz + e_i : iz;
const bool bdr =
(n_idx == 0 && (i_0 == 0 || i_0 == pp1)) ||
(n_idx == 1 && (j_0 == 0 || j_0 == pp1)) ||
(n_idx == 2 && (k_0 == 0 || k_0 == pp1));
if (bdr) { continue; }
int x_idx = (n_idx == 0) ? i_0 : (n_idx == 1) ? j_0 : k_0;
int w_idx_1 = (n_idx == 0) ? iy : (n_idx == 1) ? iz : ix;
int w_idx_2 = (n_idx == 0) ? iz : (n_idx == 1) ? ix : iy;
const real_t J00 = J(ix, iy, iz, 0, 0, iel_ho);
const real_t J01 = J(ix, iy, iz, 0, 1, iel_ho);
const real_t J02 = J(ix, iy, iz, 0, 2, iel_ho);
const real_t J10 = J(ix, iy, iz, 1, 0, iel_ho);
const real_t J11 = J(ix, iy, iz, 1, 1, iel_ho);
const real_t J12 = J(ix, iy, iz, 1, 2, iel_ho);
const real_t J20 = J(ix, iy, iz, 2, 0, iel_ho);
const real_t J21 = J(ix, iy, iz, 2, 1, iel_ho);
const real_t J22 = J(ix, iy, iz, 2, 2, iel_ho);
real_t JinvJinvT_diag = 0.0;
if (n_idx == 0)
{
JinvJinvT_diag = J02*J02*(J11*J11 + J21*J21) + (J12*J21 - J11*J22)*
(J12*J21 - J11*J22) - 2*J01*J02*(J11*J12 + J21*J22) + J01*J01*
(J12*J12 + J22*J22);
}
else if (n_idx == 1)
{
JinvJinvT_diag = J02*J02*(J10*J10 + J20*J20) + (J12*J20 - J10*J22)*
(J12*J20 - J10*J22) - 2*J00*J02*(J10*J12 + J20*J22) + J00*J00*
(J12*J12 + J22*J22);
}
else if (n_idx == 2)
{
JinvJinvT_diag = J01*J01*(J10*J10 + J20*J20) + (J11*J20 - J10*J21)*
(J11*J20 - J10*J21) - 2*J00*J01*(J10*J11 + J20*J21) + J00*J00*
(J11*J11 + J21*J21);
}
const real_t Jh = JinvJinvT_diag / DETJ;
V(v_idx, ix, iy, iz, iel_ho) = -dq * Jh * w_1d[w_idx_1] * w_1d[w_idx_2] /
(x_pp1[x_idx] - x_pp1[x_idx -1]);
}
}
V(0, ix, iy, iz, iel_ho) = mq * DETJ * W(ix, iy, iz);
for (int i = 1; i < 7; ++i)
{
V(0, ix, iy, iz, iel_ho) -= V(i, ix, iy, iz, iel_ho);
}
}
}
}
});
}
} // namespace mfem
+591
View File
@@ -0,0 +1,591 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "pderefmat_op.hpp"
#ifdef MFEM_USE_MPI
#include "fes_kernels.hpp"
/// \cond DO_NOT_DOCUMENT
namespace mfem
{
namespace internal
{
template <Ordering::Type Order, bool Atomic>
static void ParDerefMultKernelImpl(const ParDerefineMatrixOp &op,
const Vector &x, Vector &y)
{
// pack sends
if (op.xghost_send.Size())
{
auto src = x.Read();
auto idcs = op.send_permutations.Read();
auto dst = Device::GetGPUAwareMPI() ? op.xghost_send.Write()
: op.xghost_send.HostWrite();
auto vdims = op.fespace->GetVDim();
auto sptr = op.send_segment_idcs.Read();
auto lptr = op.send_segments.Read();
auto old_ndofs = x.Size() / vdims;
forall(op.send_permutations.Size(), [=] MFEM_HOST_DEVICE(int i)
{
int seg = sptr[i];
int width = lptr[seg + 1] - lptr[seg];
auto tdst = dst + i + lptr[seg] * vdims;
int sign = 1;
int col = idcs[i];
if (col < 0)
{
sign = -1;
col = -1 - col;
}
for (int vdim = 0; vdim < vdims; ++vdim)
{
tdst[vdim * width] =
sign
* src[Order == Ordering::byNODES ? (col + vdim * old_ndofs)
: (col * vdims + vdim)];
}
});
// TODO: is this needed so we can send the packed data correctly?
// unclear for GPU-aware MPI, definitely required otherwise
MFEM_DEVICE_SYNC;
}
// initialize off-diagonal receive and send
op.requests.clear();
if (op.xghost_recv.Size())
{
auto vdims = op.fespace->GetVDim();
auto rcv = Device::GetGPUAwareMPI() ? op.xghost_recv.Write()
: op.xghost_recv.HostWrite();
for (int i = 0; i < op.recv_ranks.Size(); ++i)
{
op.requests.emplace_back();
MPI_Irecv(rcv + op.recv_segments[i] * vdims,
(op.recv_segments[i + 1] - op.recv_segments[i]) * vdims,
MPITypeMap<real_t>::mpi_type, op.recv_ranks[i],
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA,
op.fespace->GetComm(), &op.requests.back());
}
}
if (op.xghost_send.Size())
{
auto vdims = op.fespace->GetVDim();
// only is a GPU mem ptr if GPU-aware MPI is enabled
auto dst = Device::GetGPUAwareMPI() ? op.xghost_send.Write()
: op.xghost_send.HostWrite();
for (int i = 0; i < op.send_ranks.Size(); ++i)
{
op.requests.emplace_back();
MPI_Isend(dst + op.send_segments[i] * vdims,
(op.send_segments[i + 1] - op.send_segments[i]) * vdims,
MPITypeMap<real_t>::mpi_type, op.send_ranks[i],
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA,
op.fespace->GetComm(), &op.requests.back());
}
}
{
// diagonal
DerefineMatrixOpMultFunctor<Order, Atomic, true> func;
func.xptr = x.Read();
y.UseDevice();
y = 0.;
func.yptr = y.ReadWrite();
func.bsptr = op.block_storage.Read();
func.boptr = op.block_offsets.Read();
func.brptr = op.block_row_idcs_offsets.Read();
func.bcptr = op.block_col_idcs_offsets.Read();
func.rptr = op.row_idcs.Read();
func.cptr = op.col_idcs.Read();
func.vdims = op.fespace->GetVDim();
func.nblocks = op.block_offsets.Size();
func.width = op.Width() / func.vdims;
func.height = op.Height() / func.vdims;
func.Run(op.max_rows);
}
// wait for comm to finish, if any
if (op.requests.size())
{
MPI_Waitall(op.requests.size(), op.requests.data(), MPI_STATUSES_IGNORE);
if (op.xghost_recv.Size())
{
// off-diagonal kernel
DerefineMatrixOpMultFunctor<Order, Atomic, false> func;
// directly read from host-pinned memory if not using GPU-aware MPI
func.xptr = Device::GetGPUAwareMPI() ? op.xghost_recv.Read()
: op.xghost_recv.HostRead();
func.yptr = y.ReadWrite();
func.bsptr = op.block_storage.Read();
func.boptr = op.off_diag_block_offsets.Read();
func.brptr = op.block_off_diag_row_idcs_offsets.Read();
func.rsptr = op.recv_segment_idcs.Read();
func.segptr = op.recv_segments.Read();
func.coptr = op.block_off_diag_col_offsets.Read();
func.bwptr = op.block_off_diag_widths.Read();
func.rptr = op.row_off_diag_idcs.Read();
func.vdims = op.fespace->GetVDim();
func.nblocks = op.off_diag_block_offsets.Size();
func.width = op.xghost_recv.Size() / func.vdims;
func.height = op.Height() / func.vdims;
func.Run(op.max_rows);
}
}
}
} // namespace internal
template <Ordering::Type Order, bool Atomic>
ParDerefineMatrixOp::MultKernelType ParDerefineMatrixOp::MultKernel::Kernel()
{
return internal::ParDerefMultKernelImpl<Order, Atomic>;
}
ParDerefineMatrixOp::MultKernelType
ParDerefineMatrixOp::MultKernel::Fallback(Ordering::Type, bool)
{
MFEM_ABORT("invalid MultKernel parameters");
}
ParDerefineMatrixOp::Kernels::Kernels()
{
MultKernel::Specialization<Ordering::byNODES, false>::Add();
MultKernel::Specialization<Ordering::byVDIM, false>::Add();
MultKernel::Specialization<Ordering::byNODES, true>::Add();
MultKernel::Specialization<Ordering::byVDIM, true>::Add();
}
void ParDerefineMatrixOp::Mult(const Vector &x, Vector &y) const
{
const bool is_dg = fespace->FEColl()->GetContType()
== FiniteElementCollection::DISCONTINUOUS;
// DG needs atomic summation
MultKernel::Run(fespace->GetOrdering(), is_dg, *this, x, y);
// use this to prevent xghost* from being re-purposed for subsequent Mult
// calls
MFEM_DEVICE_SYNC;
}
ParDerefineMatrixOp::ParDerefineMatrixOp(ParFiniteElementSpace &fespace_,
int old_ndofs,
const Table *old_elem_dof,
const Table *old_elem_fos)
: Operator(fespace_.GetVSize(), old_ndofs * fespace_.GetVDim()),
fespace(&fespace_)
{
static Kernels kernels;
constexpr int max_team_size = 256;
const int NRanks = fespace->GetNRanks();
const int nrk = HYPRE_AssumedPartitionCheck() ? 2 : NRanks;
MFEM_VERIFY(fespace->Nonconforming(),
"Not implemented for conforming meshes.");
MFEM_VERIFY(fespace->old_dof_offsets[nrk],
"Missing previous (finer) space.");
const int MyRank = fespace->GetMyRank();
ParNCMesh *old_pncmesh = fespace->GetParMesh()->pncmesh;
const CoarseFineTransformations &dtrans =
old_pncmesh->GetDerefinementTransforms();
const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
const bool is_dg = fespace->FEColl()->GetContType()
== FiniteElementCollection::DISCONTINUOUS;
DenseMatrix localRVO; // for variable-order only
DenseTensor localR[Geometry::NumGeom];
int diag_rows = 0;
int off_diag_rows = 0;
int diag_cols = 0;
auto get_ldofs = [&](int k) -> int
{
const Embedding &emb = dtrans.embeddings[k];
if (fespace->IsVariableOrder())
{
const FiniteElement *fe = fespace->GetFE(emb.parent);
return fe->GetDof();
}
else
{
Geometry::Type geom =
fespace->GetParMesh()->GetElementBaseGeometry(emb.parent);
return fespace->FEColl()->FiniteElementForGeometry(geom)->GetDof();
}
};
Array<int> dofs, old_dofs;
max_rows = 1;
// first pass:
// - determine memory block lengths
// - identify dofs in x we need to send/receive
// don't need to send the indices, fine rank will re-arrange and sign
// change x before transmitting the ghost data
// key: coarse rank to send to
// value: old dofs to send (with sign)
std::map<int, std::vector<int>> to_send;
// key: fine rank
// value: indices into dtrans.embeddings
std::map<int, std::vector<int>> od_ks;
// key: fine rank
// value: recv segment length
std::map<int, int> od_seg_lens;
int send_len = 0;
int recv_len = 0;
// size of block_storage, if fespace->IsVariableOrder()
// otherwise unused
int total_size = 0;
int num_diagonal_blocks = 0;
int num_offdiagonal_blocks = 0;
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
{
const Embedding &emb = dtrans.embeddings[k];
int fine_rank = old_ranks[k];
int coarse_rank = (emb.parent < 0) ? (-1 - emb.parent)
: old_pncmesh->ElementRank(emb.parent);
if (coarse_rank != MyRank && fine_rank == MyRank)
{
// this rank needs to send data in x to course_rank
old_elem_dof->GetRow(k, old_dofs);
auto &tmp = to_send[coarse_rank];
send_len += old_dofs.Size();
for (int i = 0; i < old_dofs.Size(); ++i)
{
tmp.emplace_back(old_dofs[i]);
}
}
else if (coarse_rank == MyRank && fine_rank != MyRank)
{
// this rank needs to receive data in x from fine_rank
MFEM_ASSERT(emb.parent >= 0, "");
auto ldofs = get_ldofs(k);
off_diag_rows += ldofs;
recv_len += ldofs;
od_ks[fine_rank].emplace_back(k);
od_seg_lens[fine_rank] += ldofs;
++num_offdiagonal_blocks;
if (fespace->IsVariableOrder())
{
total_size += ldofs * ldofs;
}
}
else if (coarse_rank == MyRank && fine_rank == MyRank)
{
MFEM_ASSERT(emb.parent >= 0, "");
// diagonal
++num_diagonal_blocks;
auto ldofs = get_ldofs(k);
diag_rows += ldofs;
diag_cols += ldofs;
if (fespace->IsVariableOrder())
{
total_size += ldofs * ldofs;
}
}
}
send_segments.SetSize(to_send.size() + 1);
send_segments.HostWrite();
send_ranks.SetSize(to_send.size());
send_ranks.HostWrite();
{
int idx = 0;
send_segments[0] = 0;
for (auto &tmp : to_send)
{
send_ranks[idx] = tmp.first;
send_segments[idx + 1] = send_segments[idx] + tmp.second.size();
++idx;
}
}
recv_segment_idcs.SetSize(off_diag_rows);
recv_segment_idcs.HostWrite();
recv_segments.SetSize(od_ks.size() + 1);
recv_segments.HostWrite();
recv_ranks.SetSize(od_ks.size());
recv_ranks.HostWrite();
// set sizes
row_idcs.SetSize(diag_rows);
row_idcs.HostWrite();
row_off_diag_idcs.SetSize(off_diag_rows);
row_off_diag_idcs.HostWrite();
col_idcs.SetSize(diag_cols);
col_idcs.HostWrite();
block_row_idcs_offsets.SetSize(num_diagonal_blocks + 1);
block_row_idcs_offsets.HostWrite();
block_col_idcs_offsets.SetSize(num_diagonal_blocks + 1);
block_col_idcs_offsets.HostWrite();
block_off_diag_row_idcs_offsets.SetSize(num_offdiagonal_blocks + 1);
block_off_diag_row_idcs_offsets.HostWrite();
block_off_diag_col_offsets.SetSize(num_offdiagonal_blocks);
block_off_diag_col_offsets.HostWrite();
block_off_diag_widths.SetSize(num_offdiagonal_blocks);
block_off_diag_widths.HostWrite();
pack_col_idcs.SetSize(send_len);
// memory manager doesn't appear to have a graceful fallback for
// HOST_PINNED if not built with CUDA or HIP
#if defined(MFEM_USE_CUDA) or defined(MFEM_USE_HIP)
xghost_send.SetSize(send_len * fespace->GetVDim(),
Device::GetGPUAwareMPI() ? MemoryType::DEFAULT
: MemoryType::HOST_PINNED);
xghost_recv.SetSize(recv_len * fespace->GetVDim(),
Device::GetGPUAwareMPI() ? MemoryType::DEFAULT
: MemoryType::HOST_PINNED);
#else
xghost_send.SetSize(send_len * fespace->GetVDim());
xghost_recv.SetSize(recv_len * fespace->GetVDim());
#endif
send_permutations.SetSize(send_len);
send_segment_idcs.SetSize(send_len);
block_offsets.SetSize(num_diagonal_blocks);
block_offsets.HostWrite();
off_diag_block_offsets.SetSize(num_offdiagonal_blocks);
off_diag_block_offsets.HostWrite();
int geom_offsets[Geometry::NumGeom];
real_t *bs_ptr;
if (fespace->IsVariableOrder())
{
block_storage.SetSize(total_size);
bs_ptr = block_storage.HostWrite();
// compute block data later
}
else
{
// compression scheme:
// block_offsets is the start of each block, potentially repeated
// only need to store localR for used shapes
Mesh::GeometryList elem_geoms(*fespace->GetMesh());
int size = 0;
for (int i = 0; i < elem_geoms.Size(); ++i)
{
fespace->GetLocalDerefinementMatrices(elem_geoms[i],
localR[elem_geoms[i]]);
geom_offsets[elem_geoms[i]] = size;
size += localR[elem_geoms[i]].TotalSize();
}
block_storage.SetSize(size);
bs_ptr = block_storage.HostWrite();
// copy blocks into block_storage
for (int i = 0; i < elem_geoms.Size(); ++i)
{
std::copy(localR[elem_geoms[i]].Data(),
localR[elem_geoms[i]].Data()
+ localR[elem_geoms[i]].TotalSize(),
bs_ptr);
bs_ptr += localR[elem_geoms[i]].TotalSize();
}
}
// second pass:
// - initialize buffers
{
auto ptr = send_permutations.HostWrite();
auto ptr2 = send_segment_idcs.HostWrite();
int i = 0;
for (auto &v : to_send)
{
ptr = std::copy(v.second.begin(), v.second.end(), ptr);
for (size_t idx = 0; idx < v.second.size(); ++idx)
{
*ptr2 = i;
++ptr2;
}
++i;
}
}
block_row_idcs_offsets[0] = 0;
block_col_idcs_offsets[0] = 0;
block_off_diag_row_idcs_offsets[0] = 0;
Array<int> mark(fespace->GetNDofs());
mark = 0;
{
int idx = 0;
recv_segments[0] = 0;
for (auto &v : od_seg_lens)
{
recv_ranks[idx] = v.first;
recv_segments[idx + 1] = recv_segments[idx] + v.second;
++idx;
}
}
// key: index into dtrans.embeddings
// value: off-diagonal block offset, od_ridx, seg id
std::unordered_map<int, std::array<int, 3>> ks_map;
{
int od_ridx = 0;
int seg_id = 0;
for (auto &v1 : od_ks)
{
for (auto k : v1.second)
{
auto &tmp = ks_map[k];
tmp[0] = ks_map.size() - 1;
tmp[1] = od_ridx;
tmp[2] = seg_id;
od_ridx += get_ldofs(k);
}
++seg_id;
}
}
int diag_idx = 0;
int var_offset = 0;
int ridx = 0;
int cidx = 0;
// can't break this up into separate diagonals/off-diagonals loops because
// of mark
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
{
const Embedding &emb = dtrans.embeddings[k];
if (emb.parent < 0)
{
continue;
}
int fine_rank = old_ranks[k];
int coarse_rank = (emb.parent < 0) ? (-1 - emb.parent)
: old_pncmesh->ElementRank(emb.parent);
if (coarse_rank == MyRank)
{
// either diagonal or off-diagonal
Geometry::Type geom =
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
if (fespace->IsVariableOrder())
{
const FiniteElement *fe = fespace->GetFE(emb.parent);
const DenseTensor &pmats = dtrans.point_matrices[geom];
const int ldof = fe->GetDof();
IsoparametricTransformation isotr;
isotr.SetIdentityTransformation(geom);
localRVO.SetSize(ldof, ldof);
isotr.SetPointMat(pmats(emb.matrix));
// Local restriction is size ldofxldof assuming that the parent
// and child are of same polynomial order.
fe->GetLocalRestriction(isotr, localRVO);
// copy block
auto s = localRVO.Height() * localRVO.Width();
std::copy(localRVO.Data(), localRVO.Data() + s, bs_ptr);
bs_ptr += s;
}
DenseMatrix &lR =
fespace->IsVariableOrder() ? localRVO : localR[geom](emb.matrix);
max_rows = std::max(lR.Height(), max_rows);
auto size = lR.Height() * lR.Width();
fespace->elem_dof->GetRow(emb.parent, dofs);
if (fine_rank == MyRank)
{
// diagonal
old_elem_dof->GetRow(k, old_dofs);
MFEM_VERIFY(old_dofs.Size() == dofs.Size(),
"Parent and child must have same #dofs.");
block_row_idcs_offsets[diag_idx + 1] =
block_row_idcs_offsets[diag_idx] + lR.Height();
block_col_idcs_offsets[diag_idx + 1] =
block_col_idcs_offsets[diag_idx] + lR.Width();
if (fespace->IsVariableOrder())
{
block_offsets[diag_idx] = var_offset;
var_offset += size;
}
else
{
block_offsets[diag_idx] = geom_offsets[geom] + size * emb.matrix;
}
for (int i = 0; i < lR.Height(); ++i, ++ridx)
{
if (!std::isfinite(lR(i, 0)))
{
row_idcs[ridx] = INT_MAX;
continue;
}
int r = dofs[i];
int m = (r >= 0) ? r : (-1 - r);
if (is_dg || !mark[m])
{
row_idcs[ridx] = r;
mark[m] = 1;
}
else
{
row_idcs[ridx] = INT_MAX;
}
}
for (int i = 0; i < lR.Width(); ++i, ++cidx)
{
col_idcs[cidx] = old_dofs[i];
}
++diag_idx;
}
else
{
// off-diagonal
auto &tmp = ks_map.at(k);
auto od_idx = tmp[0];
auto od_ridx = tmp[1];
block_off_diag_row_idcs_offsets[od_idx + 1] =
block_off_diag_row_idcs_offsets[od_idx] + lR.Height();
block_off_diag_col_offsets[od_idx] = od_ridx;
block_off_diag_widths[od_idx] = lR.Width();
recv_segment_idcs[od_idx] = tmp[2];
if (fespace->IsVariableOrder())
{
off_diag_block_offsets[od_idx] = var_offset;
var_offset += size;
}
else
{
off_diag_block_offsets[od_idx] =
geom_offsets[geom] + size * emb.matrix;
}
for (int i = 0; i < lR.Height(); ++i, ++od_ridx)
{
if (!std::isfinite(lR(i, 0)))
{
row_off_diag_idcs[od_ridx] = INT_MAX;
continue;
}
int r = dofs[i];
int m = (r >= 0) ? r : (-1 - r);
if (is_dg || !mark[m])
{
row_off_diag_idcs[od_ridx] = r;
mark[m] = 1;
}
else
{
row_off_diag_idcs[od_ridx] = INT_MAX;
}
}
++od_idx;
}
}
}
// if not using GPU, set max_rows/max_cols to zero
if (Device::Allows(Backend::DEVICE_MASK))
{
max_rows = std::min(max_rows, max_team_size);
}
else
{
max_rows = 1;
}
requests.reserve(recv_ranks.Size() + send_ranks.Size());
}
} // namespace mfem
/// \endcond DO_NOT_DOCUMENT
#endif
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_PDEREFMAT_OP
#define MFEM_PDEREFMAT_OP
#include "../config/config.hpp"
#ifdef MFEM_USE_MPI
#include "pfespace.hpp"
#include "kernel_dispatch.hpp"
#include <vector>
namespace mfem
{
/// \cond DO_NOT_DOCUMENT
struct ParDerefineMatrixOp : public Operator
{
ParFiniteElementSpace *fespace;
/// offsets into block_storage for diagonal
Array<int> block_offsets;
/// offsets into row_idcs for diagonal
Array<int> block_row_idcs_offsets;
/// offsets into col_idcs for diagonal
Array<int> block_col_idcs_offsets;
/// offsets into block_storage for off-diagonal
Array<int> off_diag_block_offsets;
/// offsets into row_idcs for off-diagonal
Array<int> block_off_diag_row_idcs_offsets;
Array<int> block_off_diag_col_offsets;
Array<int> block_off_diag_widths;
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
/// negative means the row data should be negated.
/// only for diagonal blocks
Array<int> row_idcs;
/// mapping for col dofs, negative means the col data should be negated.
/// only for diagonal blocks
Array<int> col_idcs;
Array<int> pack_col_idcs;
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
/// negative means the row data should be negated.
/// only for off-diagonal blocks
Array<int> row_off_diag_idcs;
/// dense block matrices which can be reused to construct the full matrix
/// operation. These are stored contiguously and blocks have no restrictions
/// on shape (can be rectangle and differ from block to block).
/// This is only for the diagonal block.
Vector block_storage;
/// maximum height of any block in block_storage for GPU
/// parallelization, or 1 for CPU runs.
int max_rows;
/// quasi Ordering::byNODES, broken into sections by ranks we need to send
/// the data to
mutable Vector xghost_send;
/// quasi Ordering::byNODES, broken into sections by ranks we received
/// the data from
mutable Vector xghost_recv;
/// maps off-diagonal k to segment
Array<int> recv_segment_idcs;
/// cumulative count of dofs which will be received from other ranks
Array<int> recv_segments;
/// Source rank of each recv segment
Array<int> recv_ranks;
/// What send segment each entry in send_permutations corresponds to
Array<int> send_segment_idcs;
/// cumulative count of dofs which will be sent to other ranks
Array<int> send_segments;
/// Destination rank of each send segment
Array<int> send_ranks;
/// how to permute/sign change values from our local x to send to other ranks
Array<int> send_permutations;
/// internal buffer for MPI requests
mutable std::vector<MPI_Request> requests;
using MultKernelType = void (*)(const ParDerefineMatrixOp &, const Vector &,
Vector &);
/// template args: ordering, atomic
MFEM_REGISTER_KERNELS(MultKernel, MultKernelType, (Ordering::Type, bool));
struct Kernels
{
Kernels();
};
void Mult(const Vector &x, Vector &y) const;
ParDerefineMatrixOp(ParFiniteElementSpace &fespace_, int old_ndofs,
const Table *old_elem_dof, const Table *old_elem_fos);
};
/// \endcond DO_NOT_DOCUMENT
} // namespace mfem
#endif
#endif
+49 -31
View File
@@ -22,12 +22,13 @@
#include "../mesh/mesh_headers.hpp"
#include "../general/binaryio.hpp"
#include "pderefmat_op.hpp"
#include <limits>
#include <list>
namespace mfem
{
ParFiniteElementSpace::ParFiniteElementSpace(
const ParFiniteElementSpace &orig, ParMesh *pmesh,
const FiniteElementCollection *fec)
@@ -4487,13 +4488,6 @@ ParFiniteElementSpace::RebalanceMatrix(int old_ndofs,
return M;
}
struct DerefDofMessage
{
std::vector<HYPRE_BigInt> dofs;
MPI_Request request;
};
HypreParMatrix*
ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
const Table* old_elem_dof,
@@ -4536,7 +4530,13 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
old_pncmesh->GetDerefinementTransforms();
const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
std::map<int, DerefDofMessage> messages;
// key: other rank
// value: send or recieve buffer
std::map<int, std::vector<HYPRE_BigInt>> to_send;
std::map<int, std::vector<HYPRE_BigInt>> to_recv;
// key: index into dtrans.embeddings
// value: [start, stop]
std::unordered_map<int, std::array<size_t, 2>> recv_messages;
HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
? old_dof_offsets[0] : old_dof_offsets[MyRank];
@@ -4556,30 +4556,46 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
old_elem_dof->GetRow(k, dofs);
DofsToVDofs(dofs, old_ndofs);
DerefDofMessage &msg = messages[k];
msg.dofs.resize(dofs.Size());
std::vector<HYPRE_BigInt>& send_buf = to_send[coarse_rank];
auto pos = send_buf.size();
send_buf.resize(pos + dofs.Size());
for (int i = 0; i < dofs.Size(); i++)
{
msg.dofs[i] = old_offset + dofs[i];
send_buf[pos + i] = old_offset + dofs[i];
}
MPI_Isend(&msg.dofs[0], static_cast<int>(msg.dofs.size()), HYPRE_MPI_BIG_INT,
coarse_rank, 291, MyComm, &msg.request);
}
else if (coarse_rank == MyRank && fine_rank != MyRank)
{
MFEM_ASSERT(emb.parent >= 0, "");
Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
DerefDofMessage &msg = messages[k];
msg.dofs.resize(ldof[geom]*vdim);
MPI_Irecv(&msg.dofs[0], ldof[geom]*vdim, HYPRE_MPI_BIG_INT,
fine_rank, 291, MyComm, &msg.request);
std::vector<HYPRE_BigInt>& recv_buf = to_recv[fine_rank];
auto& msg = recv_messages[k];
msg[0] = recv_buf.size();
recv_buf.resize(recv_buf.size() + ldof[geom] * vdim);
msg[1] = recv_buf.size();
}
// TODO: coalesce Isends/Irecvs to the same rank. Typically, on uniform
// derefinement, there should be just one send to MyRank-1 and one recv
// from MyRank+1
}
// assume embedding orders are consistent (i.e. what we expect to receive
// first from a given rank is sent first, etc.)
std::vector<MPI_Request> requests;
requests.reserve(to_send.size() + to_recv.size());
// enqueue recvs
for (auto &v : to_recv)
{
requests.emplace_back();
MPI_Irecv(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA, MyComm,
&requests.back());
}
// enqueue sends
for (auto &v : to_send)
{
requests.emplace_back();
MPI_Isend(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA, MyComm,
&requests.back());
}
DenseTensor localR[Geometry::NumGeom];
@@ -4637,10 +4653,7 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
diag->Finalize();
// wait for all sends/receives to complete
for (auto it = messages.begin(); it != messages.end(); ++it)
{
MPI_Wait(&it->second.request, MPI_STATUS_IGNORE);
}
MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
// create the off-diagonal part of the derefinement matrix
SparseMatrix *offd = new SparseMatrix(ndofs*vdim, 1);
@@ -4661,13 +4674,14 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
elem_dof->GetRow(emb.parent, dofs);
DerefDofMessage &msg = messages[k];
MFEM_ASSERT(msg.dofs.size(), "");
auto& odofs = to_recv.at(fine_rank);
auto &msg = recv_messages[k];
MFEM_ASSERT(msg[1] > msg[0], "");
for (int vd = 0; vd < vdim; vd++)
{
MFEM_ASSERT(ldof[geom], "");
HYPRE_BigInt* remote_dofs = &msg.dofs[vd*ldof[geom]];
HYPRE_BigInt *remote_dofs = odofs.data() + msg[0] + vd * ldof[geom];
for (int i = 0; i < lR.Height(); i++)
{
@@ -4694,7 +4708,6 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
}
}
messages.clear();
offd->Finalize(0);
offd->SetWidth(static_cast<int>(col_map.size()));
@@ -4946,8 +4959,13 @@ void ParFiniteElementSpace::Update(bool want_transform)
case Mesh::DEREFINE:
{
#if 0
Th.Reset(ParallelDerefinementMatrix(old_ndofs, old_elem_dof,
old_elem_fos));
#else
Th.Reset(new ParDerefineMatrixOp(*this, old_ndofs, old_elem_dof,
old_elem_fos));
#endif
if (Nonconforming())
{
Th.SetOperatorOwner(false);
+4
View File
@@ -24,9 +24,12 @@
namespace mfem
{
struct ParDerefineMatrixOp;
/// Abstract parallel finite element space.
class ParFiniteElementSpace : public FiniteElementSpace
{
friend struct ParDerefineMatrixOp;
private:
/// MPI data.
MPI_Comm MyComm;
@@ -478,6 +481,7 @@ public:
that the number of DOFs is @a ndofs. */
const FiniteElement *GetFaceNbrFE(int i, int ndofs = 0) const;
const FiniteElement *GetFaceNbrFaceFE(int i) const;
const Array<HYPRE_BigInt> &GetFaceNbrGlobalDofMapArray() { return face_nbr_glob_dof_map; }
const HYPRE_BigInt *GetFaceNbrGlobalDofMap() { return face_nbr_glob_dof_map; }
ElementTransformation *GetFaceNbrElementTransformation(int i) const
{ return pmesh->GetFaceNbrElementTransformation(i); }
+6 -6
View File
@@ -14,7 +14,7 @@
#include "../config/config.hpp"
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
#include <cusparse.h>
#include <library_types.h>
#include <cuda_runtime.h>
@@ -22,7 +22,7 @@
#endif
#include "cuda.hpp"
#ifdef MFEM_USE_HIP
#if defined(MFEM_USE_HIP) && defined(__HIP__)
#include <hip/hip_runtime.h>
#endif
#include "hip.hpp"
@@ -43,7 +43,7 @@
#endif
#endif
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
#if !defined(MFEM_USE_CUDA_OR_HIP)
#define MFEM_DEVICE
#define MFEM_HOST
#define MFEM_LAMBDA
@@ -55,7 +55,7 @@
#endif
#if !((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
#define MFEM_SHARED
#define MFEM_SYNC_THREAD
#define MFEM_BLOCK_ID(k) 0
@@ -66,7 +66,7 @@
#endif
// 'double' and 'float' atomicAdd implementation for previous versions of CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) && __CUDA_ARCH__ < 600
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 600)
MFEM_DEVICE inline mfem::real_t atomicAdd(mfem::real_t *add, mfem::real_t val)
{
unsigned long long int *ptr = (unsigned long long int *) add;
@@ -94,7 +94,7 @@ template <typename T>
MFEM_HOST_DEVICE T AtomicAdd(T &add, const T val)
{
#if ((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
return atomicAdd(&add,val);
#else
T old = add;
+16 -8
View File
@@ -425,16 +425,24 @@ public:
~GroupCommunicator();
};
/// General MPI message tags used by MFEM
enum MessageTag
{
DEREFINEMENT_MATRIX_CONSTRUCTION_DATA =
291, /// ParFiniteElementSpace ParallelDerefinementMatrix and
/// ParDerefineMatrixOp
};
enum VarMessageTag
{
NEIGHBOR_ELEMENT_RANK_VM, ///< NeighborElementRankMessage
NEIGHBOR_ORDER_VM, ///< NeighborOrderMessage
NEIGHBOR_DEREFINEMENT_VM, ///< NeighborDerefinementMessage
NEIGHBOR_REFINEMENT_VM, ///< NeighborRefinementMessage
NEIGHBOR_PREFINEMENT_VM, ///< NeighborPRefinementMessage
NEIGHBOR_ROW_VM, ///< NeighborRowMessage
REBALANCE_VM, ///< RebalanceMessage
REBALANCE_DOF_VM ///< RebalanceDofMessage
NEIGHBOR_ELEMENT_RANK_VM, ///< NeighborElementRankMessage
NEIGHBOR_ORDER_VM, ///< NeighborOrderMessage
NEIGHBOR_DEREFINEMENT_VM, ///< NeighborDerefinementMessage
NEIGHBOR_REFINEMENT_VM, ///< NeighborRefinementMessage
NEIGHBOR_PREFINEMENT_VM, ///< NeighborPRefinementMessage
NEIGHBOR_ROW_VM, ///< NeighborRowMessage
REBALANCE_VM, ///< RebalanceMessage
REBALANCE_DOF_VM, ///< RebalanceDofMessage
};
/// \brief Variable-length MPI message containing unspecific binary data.
+1 -1
View File
@@ -24,7 +24,7 @@ void mfem_cuda_error(cudaError_t err, const char *expr, const char *func,
const char *file, int line)
{
mfem::err << "\n\nCUDA error: (" << expr << ") failed with error:\n --> "
<< cudaGetErrorString(err)
<< cudaGetErrorString(err) << " [code: " << (int)err << ']'
<< "\n ... in function: " << func
<< "\n ... in file: " << file << ':' << line << '\n';
mfem_error();
+5 -5
View File
@@ -18,7 +18,7 @@
// CUDA block size used by MFEM.
#define MFEM_CUDA_BLOCKS 256
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
#define MFEM_USE_CUDA_OR_HIP
#define MFEM_DEVICE __device__
#define MFEM_HOST __host__
@@ -37,10 +37,9 @@
__FILE__, __LINE__); \
} \
} while (0)
#endif // MFEM_USE_CUDA
// Define the MFEM inner threading macros
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)
#if defined(__CUDA_ARCH__)
#define MFEM_SHARED __shared__
#define MFEM_SYNC_THREAD __syncthreads()
#define MFEM_BLOCK_ID(k) blockIdx.k
@@ -48,12 +47,13 @@
#define MFEM_THREAD_SIZE(k) blockDim.k
#define MFEM_FOREACH_THREAD(i,k,N) for(int i=threadIdx.k; i<N; i+=blockDim.k)
#define MFEM_FOREACH_THREAD_DIRECT(i,k,N) if(const int i=threadIdx.k; i<N)
#endif
#endif // defined(__CUDA_ARCH__)
#endif // defined(MFEM_USE_CUDA) && defined(__CUDACC__)
namespace mfem
{
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
// Function used by the macro MFEM_GPU_CHECK.
void mfem_cuda_error(cudaError_t err, const char *expr, const char *func,
const char *file, int line);
+1 -1
View File
@@ -615,7 +615,7 @@ void Device::Setup(const std::string &device_option, const int device_id)
if (Allows(Backend::DEBUG_DEVICE)) { ngpu = 1; }
}
MemoryType Device::QueryMemoryType(void *ptr)
MemoryType Device::QueryMemoryType(const void* ptr)
{
// from HYPRE's hypre_GetPointerLocation
MemoryType res = MemoryType::HOST;
+3 -3
View File
@@ -297,9 +297,9 @@ public:
/// Get the status of GPU-aware MPI flag.
static bool GetGPUAwareMPI() { return Get().mpi_gpu_aware; }
/** @brief Query the device driver for what memory type a given @a ptr is
allocated with. */
static MemoryType QueryMemoryType(void *ptr);
/** Query the device driver for what memory type a given @a ptr is allocated
* with. */
static MemoryType QueryMemoryType(const void* ptr);
/** @brief The number of hardware compute units/streaming multiprocessors
available on a given compute device @a device_id. */
+1 -1
View File
@@ -176,7 +176,7 @@ __device__ void abort_msg(T & msg)
printf(__VA_ARGS__); \
asm("trap;"); \
}
#elif defined(MFEM_USE_HIP)
#elif defined(__HIP_DEVICE_COMPILE__)
#define MFEM_ABORT_KERNEL(...) \
{ \
printf(__VA_ARGS__); \
+12 -12
View File
@@ -158,8 +158,8 @@ private:
#define MFEM_PRAGMA(X) _Pragma(#X)
// MFEM_UNROLL pragma macro that can be used inside MFEM_FORALL macros.
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)
#ifdef __NVCC__
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) // Clang cuda or nvcc
#ifdef __NVCC__ // nvcc specifically
#define MFEM_UNROLL(N) MFEM_PRAGMA(unroll(N))
#else // Assuming Clang CUDA
#define MFEM_UNROLL(N) MFEM_PRAGMA(unroll N)
@@ -169,12 +169,12 @@ private:
#endif
// MFEM_GPU_FORALL: "parallel for" executed with CUDA or HIP based on the MFEM
// build-time configuration (MFEM_USE_CUDA or MFEM_USE_HIP). If neither CUDA nor
// HIP is enabled, this macro is a no-op.
#if defined(MFEM_USE_CUDA)
// build-time configuration (MFEM_USE_CUDA or MFEM_USE_HIP), and if compiling
// with CUDA/HIP language. Otherwise, this macro is a no-op.
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
#define MFEM_GPU_FORALL(i, N,...) CuWrap1D(N, [=] MFEM_DEVICE \
(int i) {__VA_ARGS__})
#elif defined(MFEM_USE_HIP)
#elif defined(MFEM_USE_HIP) && defined(__HIP__)
#define MFEM_GPU_FORALL(i, N,...) HipWrap1D(N, [=] MFEM_DEVICE \
(int i) {__VA_ARGS__})
#else
@@ -481,7 +481,7 @@ void RajaSeqWrap(const int N, HBODY &&h_body)
/// CUDA backend
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
template <typename BODY> __global__ static
void CuKernel1D(const int N, BODY body)
@@ -573,11 +573,11 @@ struct CuWrap<3>
}
};
#endif // MFEM_USE_CUDA
#endif // defined(MFEM_USE_CUDA) && defined(__CUDACC__)
/// HIP backend
#ifdef MFEM_USE_HIP
#if defined(MFEM_USE_HIP) && defined(__HIP__)
template <typename BODY> __global__ static
void HipKernel1D(const int N, BODY body)
@@ -668,7 +668,7 @@ struct HipWrap<3>
}
};
#endif // MFEM_USE_HIP
#endif // defined(MFEM_USE_HIP) && defined(__HIP__)
/// The forall kernel body wrapper
@@ -701,7 +701,7 @@ inline void ForallWrap(const bool use_dev, const int N,
}
#endif
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
// If Backend::CUDA is allowed, use it
if (Device::Allows(Backend::CUDA))
{
@@ -709,7 +709,7 @@ inline void ForallWrap(const bool use_dev, const int N,
}
#endif
#ifdef MFEM_USE_HIP
#if defined(MFEM_USE_HIP) && defined(__HIP__)
// If Backend::HIP is allowed, use it
if (Device::Allows(Backend::HIP))
{
+1 -1
View File
@@ -24,7 +24,7 @@ void mfem_hip_error(hipError_t err, const char *expr, const char *func,
const char *file, int line)
{
mfem::err << "\n\nHIP error: (" << expr << ") failed with error:\n --> "
<< hipGetErrorString(err)
<< hipGetErrorString(err) << " [code: " << (int)err << ']'
<< "\n ... in function: " << func
<< "\n ... in file: " << file << ':' << line << '\n';
mfem_error();
+4 -4
View File
@@ -18,7 +18,7 @@
// HIP block size used by MFEM.
#define MFEM_HIP_BLOCKS 256
#ifdef MFEM_USE_HIP
#if defined(MFEM_USE_HIP) && defined(__HIP__)
#define MFEM_USE_CUDA_OR_HIP
#define MFEM_DEVICE __device__
#define MFEM_HOST __host__
@@ -37,10 +37,9 @@
__FILE__, __LINE__); \
} \
} while (0)
#endif // MFEM_USE_HIP
// Define the MFEM inner threading macros
#if defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)
#if defined(__HIP_DEVICE_COMPILE__)
#define MFEM_SHARED __shared__
#define MFEM_SYNC_THREAD __syncthreads()
#define MFEM_BLOCK_ID(k) hipBlockIdx_ ##k
@@ -50,7 +49,8 @@
for(int i=hipThreadIdx_ ##k; i<N; i+=hipBlockDim_ ##k)
#define MFEM_FOREACH_THREAD_DIRECT(i,k,N) \
if(const int i=hipThreadIdx_ ##k; i<N)
#endif
#endif // defined(__HIP_DEVICE_COMPILE__)
#endif // defined(MFEM_USE_HIP) && defined(__HIP__)
namespace mfem
{
+3 -1
View File
@@ -896,6 +896,7 @@ inline HYPRE_MemoryLocation GetHypreMemoryLocation()
#elif MFEM_HYPRE_VERSION < 23100
return HYPRE_MEMORY_DEVICE;
#else // HYPRE_USING_GPU is defined and MFEM_HYPRE_VERSION >= 23100
if (!HYPRE_Initialized()) { return HYPRE_MEMORY_HOST; }
HYPRE_MemoryLocation loc;
HYPRE_GetMemoryLocation(&loc);
return loc;
@@ -1057,7 +1058,8 @@ inline void Memory<T>::MakeAlias(const Memory &base, int offset, int size)
// register the 'base' if the MemoryManager::Exists():
MemoryManager::Exists()
#else // HYPRE_USING_GPU is defined and MFEM_HYPRE_VERSION >= 23100
MemoryManager::Exists() && HypreUsingGPU()
IsDeviceMemory(MemoryManager::GetDeviceMemoryType()) ||
(MemoryManager::Exists() && HypreUsingGPU())
#endif
)
{
+1 -1
View File
@@ -537,7 +537,7 @@ void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
return;
}
#if defined(MFEM_USE_HIP) || defined(MFEM_USE_CUDA)
#if defined(MFEM_USE_CUDA_OR_HIP)
if (use_dev &&
mfem::Device::Allows(Backend::CUDA | Backend::HIP | Backend::RAJA_CUDA |
Backend::RAJA_HIP))
+214
View File
@@ -28,6 +28,11 @@ std::string ODESolver::ImplicitTypes =
" GA : 40 -- 50 - Generalized-alpha,\n\t"
" AM : 51 - AM1, 52 - AM2, 53 - AM3, 54 - AM4\n";
std::string ODESolver::IMEXTypes =
"\n\tIMEX solver: \n\t"
" 55 - Forward Backward Euler, 56 - IMEXRK2(2,2,2), 57 - IMEXRK2(2,3,2)\n\t"
" 58 - IMEX_DIRK_RK3\n";
std::string ODESolver::Types = ODESolver::ExplicitTypes +
ODESolver::ImplicitTypes;
@@ -41,6 +46,10 @@ std::unique_ptr<ODESolver> ODESolver::Select(int ode_solver_type)
{
return SelectImplicit(ode_solver_type);
}
// else
// {
// return SelectIMEX(ode_solver_type);
// }
}
std::unique_ptr<ODESolver> ODESolver::SelectExplicit(int ode_solver_type)
@@ -106,6 +115,20 @@ std::unique_ptr<ODESolver> ODESolver::SelectImplicit(int ode_solver_type)
}
}
std::unique_ptr<SplitODESolver> SplitODESolver::Select(int ode_solver_type)
{
using ode_ptr = std::unique_ptr<SplitODESolver>;
switch (ode_solver_type)
{
case 55: return ode_ptr(new IMEXExpImplEuler);
case 56: return ode_ptr(new IMEXRK2);
case 57: return ode_ptr(new IMEXRK2_3StageExplicit);
case 58: return ode_ptr(new IMEX_DIRK_RK3);
default: MFEM_ABORT("Unknown ODE solver type: " << ode_solver_type );
}
}
void ODEStateDataVector::SetSize( int vsize, MemoryType m_t)
{
@@ -1277,4 +1300,195 @@ void GeneralizedAlpha2Solver::Step(Vector &x, Vector &dxdt,
t += dt;
}
void SplitODESolver::Init(SplitTimeDependentOperator &f_)
{
this->f = &f_;
mem_type = GetMemoryType(f_.GetMemoryClass());
}
void IMEXExpImplEuler::Init(SplitTimeDependentOperator &f_)
{
SplitODESolver::Init(f_);
int n = f->Width();
k1.SetSize(n, mem_type);
k2.SetSize(n, mem_type);
}
void IMEXExpImplEuler::Step(Vector &x, real_t &t, real_t &dt)
{
f->SetTime(t);
f->Mult1(x, k1);
f->SetTime(t+dt);
f->ImplicitSolve2(dt, x, k2);
f->SetTime(t);
x.Add(dt, k1);
x.Add(dt, k2);
t += dt;
}
void IMEXRK2::Init(SplitTimeDependentOperator &f_)
{
SplitODESolver::Init(f_);
int n = f->Width();
k1_exp.SetSize(n, mem_type);
k2_exp.SetSize(n, mem_type);
k2_imp.SetSize(n, mem_type);
k3_imp.SetSize(n, mem_type);
y.SetSize(n, mem_type);
z.SetSize(n, mem_type);
}
void IMEXRK2::Step(Vector &x, real_t &t, real_t &dt)
{
double gamma = 1 - sqrt(2)/2;
double delta = 1 - 1/(2*gamma);
f->SetTime(t);
//K1 exp is just f_1(t, x)
f->Mult1(x, k1_exp);
//K2 exp is f_1(t + gamma dt, x + dt gamma K1)
f->SetTime(t + gamma*dt);
add(x, dt*gamma, k1_exp, y);
f->Mult1(y, k2_exp);
//K2_imp = f_2(t + gamma dt, x + dt gamma K2_imp)
f->ImplicitSolve2(dt*gamma, x, k2_imp);
//K3_imp = f_2(t+dt,x + dt(1-gamma)K2_imp + dt gamma K3_imp)
f -> SetTime(t + dt);
add(x, dt*(1-gamma), k2_imp, z);
f->ImplicitSolve2(dt*gamma, z, k3_imp);
//add it all up
x.Add(dt*delta, k1_exp);
x.Add(dt*(1-delta), k2_exp);
x.Add(dt*(1-gamma), k2_imp);
x.Add(dt*gamma, k3_imp);
t += dt;
}
void IMEXRK2_3StageExplicit::Init(SplitTimeDependentOperator &f_)
{
SplitODESolver::Init(f_);
int n = f->Width();
k1_exp.SetSize(n, mem_type);
k2_exp.SetSize(n, mem_type);
k3_exp.SetSize(n, mem_type);
k2_imp.SetSize(n, mem_type);
k3_imp.SetSize(n, mem_type);
y.SetSize(n, mem_type);
z.SetSize(n, mem_type);
w.SetSize(n, mem_type);
}
void IMEXRK2_3StageExplicit::Step(Vector &x, real_t &t, real_t &dt)
{
// WIP
double gamma = 1 - sqrt(2)/2;
double delta = -2*sqrt(2)/3;
f->SetTime(t);
//K1 exp is just f_1(t, x)
f->Mult1(x, k1_exp);
//K2 exp is f_1(t + gamma dt, x + dt gamma K1)
f->SetTime(t + gamma*dt);
add(x, dt*gamma, k1_exp, y);
f->Mult1(y, k2_exp);
//K3 Exp is f_1(t + dt, x + dt gamma K1_exp + dt (1-gamma) K2_exp)
f->SetTime(t + dt);
add(x, dt*delta, k1_exp, y);
add(y, dt*(1-delta), k2_exp, w);
f->Mult1(w, k3_exp);
//K2_imp = f_2(t + gamma dt, x + dt gamma K2_imp)
f->SetTime(t + gamma*dt);
f->ImplicitSolve2(dt*gamma, x, k2_imp);
//K3_imp = f_2(t+dt,x + dt(1-gamma)K2_imp + dt gamma K3_imp)
f -> SetTime(t + dt);
add(x, dt*(1-gamma), k2_imp, z);
f->ImplicitSolve2(dt*gamma, z, k3_imp);
//add it all up
x.Add(dt*delta, k2_exp);
x.Add(dt*(1-delta), k3_exp);
x.Add(dt*(1-gamma), k2_imp);
x.Add(dt*gamma, k3_imp);
t += dt;
}
void IMEX_DIRK_RK3::Init(SplitTimeDependentOperator &f_)
{
SplitODESolver::Init(f_);
int n = f->Width();
k1_exp.SetSize(n, mem_type);
k2_exp.SetSize(n, mem_type);
k3_exp.SetSize(n, mem_type);
k4_exp.SetSize(n, mem_type);
k2_imp.SetSize(n, mem_type);
k3_imp.SetSize(n, mem_type);
k4_imp.SetSize(n, mem_type);
y.SetSize(n, mem_type);
z.SetSize(n, mem_type);
w.SetSize(n, mem_type);
v.SetSize(n, mem_type);
u.SetSize(n, mem_type);
}
void IMEX_DIRK_RK3::Step(Vector &x, real_t &t, real_t &dt)
{
double gamma = 0.4358665215;
double b1 = 1.208496649;
double b2 = -0.644363171;
double a_31 = 0.3212788860;
double a_32 = 0.3966543747;
double a_41 = -0.105858296;
double a_42 = 0.5529291479;
double a_43 = 0.5529291479;
//K1_exp
f->SetTime(t);
f->Mult1(x, k1_exp);
//K2_imp, K2_exp
f->SetTime(t + gamma*dt);
add(x, dt*gamma, k1_exp, y);
f->Mult1(y, k2_exp);
f->ImplicitSolve2(dt*gamma, x, k2_imp);
//K3_imp, K3_exp
f->SetTime(t + (1+gamma)/2*dt);
add(x, dt*a_31, k1_exp, y);
add(y, dt*a_32, k2_exp, w);
f->Mult1(w, k3_exp);
add(x, dt*(1-gamma)/2, k2_imp, z);
f->ImplicitSolve2(dt*gamma, z, k3_imp);
//K4_imp, K4_exp
f->SetTime(t+dt);
add(x, dt*a_41, k1_exp, y);
add(y, dt*a_42, k2_exp, w);
add(w, dt*a_43, k3_exp, v);
f->Mult1(v, k4_exp);
add(x, dt*b1, k2_imp, z);
add(z, dt*b2, k3_imp, u);
f->ImplicitSolve2(dt*gamma, u, k4_imp);
//add it all together
x.Add(dt*b1, k2_exp);
x.Add(dt*b2, k3_exp);
x.Add(dt*gamma, k4_exp);
x.Add(dt*b1, k2_imp);
x.Add(dt*b2, k3_imp);
x.Add(dt*gamma, k4_imp);
t += dt;
}
}
+93 -1
View File
@@ -184,6 +184,7 @@ public:
// Help info for ODESolver options
static MFEM_EXPORT std::string ExplicitTypes;
static MFEM_EXPORT std::string ImplicitTypes;
static MFEM_EXPORT std::string IMEXTypes;
static MFEM_EXPORT std::string Types;
/// Function for selecting the desired ODESolver (Explicit and Implicit)
@@ -203,6 +204,12 @@ public:
static MFEM_EXPORT std::unique_ptr<ODESolver> SelectImplicit(
const int ode_solver_type);
/// Function for selecting the desired IMEX ODESolver
/// Returns an ODESolver pointer based on an type
/// Caller gets ownership of the object and is responsible for its deletion
//static MFEM_EXPORT std::unique_ptr<SplitODESolver> SelectIMEX(const int ode_solver_type);
virtual ~ODESolver() { }
};
@@ -931,6 +938,91 @@ public:
};
/// Class for solving systems of split ODEs: dx/dt = f_1(x,t) + f_2(x,t)
class SplitODESolver : public ODESolver
{
protected:
/// Pointer to the associated SplitTimeDependentOperator.
SplitTimeDependentOperator *f; // f(.,t) : R^n --> R^n
MemoryType mem_type;
public:
SplitODESolver() : f(NULL) { mem_type = Device::GetHostMemoryType(); }
/// Associate a SplitTimeDependentOperator with the ODE solver. Overrides Init from ODESolver
/** This method has to be called:
- Before the first call to Step().
- When the dimensions of the associated SplitTimeDependentOperator change.
- When a time stepping sequence has to be restarted.
- To change the associated SplitTimeDependentOperator. */
virtual void Init(SplitTimeDependentOperator &f);
static MFEM_EXPORT std::unique_ptr<SplitODESolver> Select(
const int ode_solver_type);
virtual ~SplitODESolver() { }
};
class IMEXExpImplEuler : public SplitODESolver
{
private:
Vector k1; Vector k2;
public:
void Init(SplitTimeDependentOperator &f_) override;
void Step(Vector &x, real_t &t, real_t &dt) override;
};
/**IMEX RK2 Method from "On the Stability of IMEX Upwind gSBP Schemes for 1D Linear AdvectionDifusion Equations" by Sigrun Ortleb.
* Same as (2,2,2) from "Implicit-explicit Runge-Kutta
methods for time-dependent partial differential equations" by Ascher, Ruuth
and Spiteri, Applied Numerical Mathematics (1997).**/
class IMEXRK2 : public SplitODESolver
{
private:
Vector k1_exp; Vector k2_exp; Vector k2_imp; Vector k3_imp;
//helper vectors
Vector y; Vector z;
public:
void Init(SplitTimeDependentOperator &f_) override;
void Step(Vector &x, real_t &t, real_t &dt) override;
};
/**(2,3,2) from "Implicit-explicit Runge-Kutta
methods for time-dependent partial differential equations" by Ascher, Ruuth
and Spiteri, Applied Numerical Mathematics (1997).**/
class IMEXRK2_3StageExplicit : public SplitODESolver
{
private:
Vector k1_exp; Vector k2_exp; Vector k3_exp; Vector k2_imp; Vector k3_imp;
//helper vectors
Vector y; Vector z; Vector w;
public:
void Init(SplitTimeDependentOperator &f_) override;
void Step(Vector &x, real_t &t, real_t &dt) override;
};
/**(3,4,3) from "Implicit-explicit Runge-Kutta
methods for time-dependent partial differential equations" by Ascher, Ruuth
and Spiteri, Applied Numerical Mathematics (1997).**/
class IMEX_DIRK_RK3 : public SplitODESolver
{
private:
Vector k1_exp; Vector k2_exp; Vector k3_exp; Vector k4_exp;
Vector k2_imp; Vector k3_imp; Vector k4_imp;
//helper vectors
Vector y; Vector z; Vector w; Vector u; Vector v;
public:
void Init(SplitTimeDependentOperator &f_) override;
void Step(Vector &x, real_t &t, real_t &dt) override;
};
}
#endif
#endif
+24 -1
View File
@@ -365,6 +365,29 @@ void SecondOrderTimeDependentOperator::ImplicitSolve(const real_t dt0,
mfem_error("SecondOrderTimeDependentOperator::ImplicitSolve() is not overridden!");
}
void SplitTimeDependentOperator::Mult1(const Vector &, Vector &) const
{
mfem_error("SplitTimeDependentOperator::Mult1() is not overridden!");
}
void SplitTimeDependentOperator::ImplicitSolve1(const real_t, const Vector &,
Vector &)
{
mfem_error("SplitTimeDependentOperator::ImplicitSolve1() is not overridden!");
}
void SplitTimeDependentOperator::Mult2(const Vector &, Vector &) const
{
mfem_error("SplitTimeDependentOperator::Mult2() is not overridden!");
}
void SplitTimeDependentOperator::ImplicitSolve2(const real_t, const Vector &,
Vector &)
{
mfem_error("SplitTimeDependentOperator::ImplicitSolve2() is not overridden!");
}
SumOperator::SumOperator(const Operator *A, const real_t alpha,
const Operator *B, const real_t beta,
bool ownA, bool ownB)
@@ -911,4 +934,4 @@ real_t PowerMethod::EstimateLargestEigenvalue(Operator& opr, Vector& v0,
return eigenvalue;
}
}
}
+32 -1
View File
@@ -786,6 +786,37 @@ public:
virtual ~SecondOrderTimeDependentOperator() { }
};
// F(y,\frac{dy}{dt},t) = G1(y,t) + G2(y,t)
class SplitTimeDependentOperator : public TimeDependentOperator
{
// virtual void Mult(...) sum of Mult1 and Mult2
public:
/** @brief Construct a "square" SplitTimeDependentOperator
y = F(x,dxdt,t), where x, dxdt and y have the same dimension @a n. */
explicit SplitTimeDependentOperator(int n = 0, real_t t_ = 0.0,
Type type_ = EXPLICIT)
: TimeDependentOperator(n, t_,type_) { }
/** @brief Construct a SplitTimeDependentOperator y = F(x,dxdt,t),
where x, dxdt and y have the same dimension @a n. */
SplitTimeDependentOperator(int h, int w, real_t t_ = 0.0,
Type type_ = EXPLICIT)
: TimeDependentOperator(h, w, t_,type_) { }
virtual void Mult1(const Vector &u, Vector &k) const; //M^{-1} G1
virtual void Mult2(const Vector &u, Vector &k) const; //M^{-1} G2
virtual void ImplicitSolve1(const real_t gamma, const Vector &u,
Vector &k); //F(u + gamma k, k, t) = G1(u + gamma k, t)
virtual void ImplicitSolve2(const real_t gamma, const Vector &u,
Vector &k); //F(u + gamma k, k, t) = G2(u + gamma k, t)
virtual ~SplitTimeDependentOperator() { }
// - Fully explicit, need Mult1 and Mult2 (or just plain Mult)
// - IMEX, need Mult1 and ImplicitSolve2
// - "Split implicit", need ImplicitSolve1 and ImplicitSolve2
// - Fully implicit, need ImplicitSolve
};
/// Base class for solvers
class Solver : public Operator
@@ -1176,4 +1207,4 @@ public:
}
#endif
#endif
+2 -2
View File
@@ -13257,7 +13257,7 @@ void Mesh::ScaleElements(real_t sf)
delete [] vn;
}
void Mesh::Transform(void (*f)(const Vector&, Vector&))
void Mesh::Transform(std::function<void(const Vector &, Vector&)> f)
{
// TODO: support for different new spaceDim.
if (Nodes == NULL)
@@ -13270,7 +13270,7 @@ void Mesh::Transform(void (*f)(const Vector&, Vector&))
vold(j) = vertices[i](j);
}
vnew.SetData(vertices[i]());
(*f)(vold, vnew);
f(vold, vnew);
}
}
else
+1 -1
View File
@@ -2254,7 +2254,7 @@ public:
void ScaleSubdomains (real_t sf);
void ScaleElements (real_t sf);
void Transform(void (*f)(const Vector&, Vector&));
void Transform(std::function<void(const Vector &, Vector&)> f);
void Transform(VectorCoefficient &deformation);
/** @brief This function should be called after the mesh node coordinates
+1 -3
View File
@@ -36,6 +36,4 @@ add_subdirectory(parelag)
add_subdirectory(tribol)
add_subdirectory(hooke)
add_subdirectory(dpg)
add_subdirectory(hdiv-linear-solver)
add_subdirectory(dfem)
add_subdirectory(diag-smoothers)
add_subdirectory(hdiv-linear-solver)
+7 -8
View File
@@ -10,22 +10,21 @@
# CONTRIBUTING.md for details.
list(APPEND SEQMTOP_COMMON_SOURCES
paramnonlinearform.cpp
mtop_integrators.cpp)
darcy_heat_transfer_ex.cpp)
list(APPEND SEQMTOP_COMMON_HEADERS
paramnonlinearform.hpp
mtop_integrators.hpp)
# list(APPEND SEQMTOP_COMMON_HEADERS
# paramnonlinearform.hpp
# mtop_integrators.hpp)
convert_filenames_to_full_paths(SEQMTOP_COMMON_SOURCES)
convert_filenames_to_full_paths(SEQMTOP_COMMON_HEADERS)
//convert_filenames_to_full_paths(SEQMTOP_COMMON_HEADERS)
set(SEQMTOP_COMMON_FILES
EXTRA_SOURCES ${SEQMTOP_COMMON_SOURCES}
EXTRA_HEADERS ${SEQMTOP_COMMON_HEADERS})
add_mfem_miniapp(seqheat
MAIN seqheat.cpp
MAIN darcy_heat_transfer_ex.cpp
${SEQMTOP_COMMON_FILES}
LIBRARIES mfem)
@@ -51,4 +50,4 @@ add_mfem_miniapp(parheat
${PARMTOP_COMMON_FILES}
LIBRARIES mfem)
endif ()
endif ()
+758
View File
@@ -0,0 +1,758 @@
// MFEM Darcy Test Run
//
// Compile with: make darcy_heat_transfer_ex
//
//
// Description: This code performs the forward and backward adjoint solve for advection diffusion, where the velocity field is given by Darcy
#include "mfem.hpp"
#include <fstream>
#include <iostream>
using namespace std;
using namespace mfem;
// *****Funtion definitions for the Advection-Diffusion solve******
// Velocity coefficient
void velocity_function(const Vector &x, Vector &v);
// Initial condition
double theta0_function(const Vector &x);
// true solution
real_t theta_exact(const Vector &x, real_t t);
// rhs
double forcing_function(const Vector &x, real_t t);
// inflow
double inflow_function(const Vector &x);
real_t f_natural(const Vector & x);
// Mesh bounding box
Vector bb_min, bb_max;
class DG_Solver : public Solver
{
private:
SparseMatrix &M, &K, &S, A;
CGSolver linear_solver;
BlockILU prec;
real_t dt;
public:
DG_Solver(SparseMatrix &M_, SparseMatrix &K_, SparseMatrix &S_,
const FiniteElementSpace &fes)
: M(M_),
K(K_),
S(S_),
prec(fes.GetTypicalFE()->GetDof(),
BlockILU::Reordering::MINIMUM_DISCARDED_FILL),
dt(1.0)
{
linear_solver.iterative_mode = false;
linear_solver.SetRelTol(1e-9);
linear_solver.SetAbsTol(0.0);
linear_solver.SetMaxIter(100);
linear_solver.SetPrintLevel(0);
linear_solver.SetPreconditioner(prec);
}
void SetTimeStep(real_t dt_)
{
if (dt_ != dt)
{
dt = dt_;
// Form operator A = M + dt*S
A = S;
A *= dt;
A += M;
// this will also call SetOperator on the preconditioner
linear_solver.SetOperator(A);
}
}
void SetOperator(const Operator &op) override
{
linear_solver.SetOperator(op);
}
void Mult(const Vector &x, Vector &y) const override
{
linear_solver.Mult(x, y);
}
};
/** A time-dependent operator for the right-hand side of the ODE. The DG weak
form of the advection-diffusion equation is (M + dt S) du/dt = Su - K u + b, where M and K are the mass
and advection matrices, and b describes the flow on the boundary. In the case of IMEX evolution, the diffusion term is treated
implicitly, and the advection term is treated explicitly. */
class IMEX_Evolution : public SplitTimeDependentOperator
{
private:
BilinearForm &M, &K, &S;
const Vector &b;
unique_ptr<Solver> M_prec;
CGSolver M_solver;
unique_ptr<DG_Solver> dg_solver;
mutable Vector z;
public:
IMEX_Evolution(BilinearForm &M_, BilinearForm &K_, BilinearForm &S_,
const Vector &b_);
void Mult1(const Vector &x, Vector &y) const;
void ImplicitSolve2(const real_t dt, const Vector &x, Vector &k) override;
};
// *****Define the analytical solution and forcing terms / boundary conditions for Darcy*****
void uFun_ex(const Vector & x, Vector & u);
real_t pFun_ex(const Vector & x);
void fFun(const Vector & x, Vector & f);
real_t gFun(const Vector & x);
int main(int argc, char *argv[])
{
// 1. Parse command-line options.
const char *mesh_file =
"square-extended.mesh"; //reference square, but extended to be [-1, 1] x [-1, 1]
int order_darcy = 1;
int ref_levels = 2;
int order_ad = 3;
int ode_solver_type = 55;
double t_final = 10.0;
double d_coef = 0.01;
double dt = 0.01;
double sigma = -1.0;
double kappa = -1.0;
bool visualization = true;
bool visit = false;
bool binary = false;
int vis_steps = 5;
bool paraview = false;
int precision = 16;
const char *device_config = "cpu";
cout.precision(precision);
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&ref_levels, "-r", "--refine",
"Number of times to refine the mesh uniformly.");
args.AddOption(&order_darcy, "-od", "--order_darcy",
"Order (degree) of the finite elements for darcy solve.");
args.AddOption(&order_ad, "-oad", "--order_ad",
"Order (degree) of the finite elements for advection diffusion.");
args.AddOption(&ode_solver_type, "-s", "--ode-solver",
"55 - Forward Backward Euler, 56 - IMEXRK2(2,2,2), 57 - IMEXRK2(2,3,2), 58 - IMEX_DIRK_RK3\n");
args.AddOption(&t_final, "-tf", "--t-final",
"Final time; start time is 0.");
args.AddOption(&dt, "-dt", "--time-step",
"Time step.");
args.AddOption(&d_coef, "-d", "--diff-coef",
"Diffusion coefficient.");
args.AddOption(&sigma, "-s", "--sigma",
"One of the two DG penalty parameters, typically +1/-1."
" See the documentation of class DGDiffusionIntegrator.");
args.AddOption(&kappa, "-k", "--kappa",
"One of the two DG penalty parameters, should be positive."
" Negative values are replaced with (order+1)^2.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&visit, "-visit", "--visit-datafiles", "-no-visit",
"--no-visit-datafiles",
"Save data files for VisIt (visit.llnl.gov) visualization.");
args.AddOption(&binary, "-binary", "--binary-datafiles", "-ascii",
"--ascii-datafiles",
"Use binary (Sidre) or ascii format for VisIt data files.");
args.AddOption(&vis_steps, "-vs", "--visualization-steps",
"Visualize every n-th timestep.");
args.AddOption(&paraview, "-paraview", "--paraview-datafiles", "-no-paraview",
"--no-paraview-datafiles",
"Save data files for ParaView (paraview.org) visualization.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(cout);
return 1;
}
if (kappa < 0)
{
kappa = (order_ad+1)*(order_ad+1);
}
args.PrintOptions(cout);
Device device(device_config);
device.Print();
// 2. Define the ODE solver used for time integration. Several explicit, implicit and IMEX
// Runge-Kutta methods are available.
unique_ptr<SplitODESolver> ode_solver = SplitODESolver::Select(ode_solver_type);
unique_ptr<SplitODESolver> ode_solver_adj = SplitODESolver::Select(
ode_solver_type);
// 3. Read the mesh from the given mesh file.
Mesh mesh(mesh_file, 1, 1);
int dim = mesh.Dimension();
// 4. Refine the mesh in serial to increase the resolution. In this example
// we do 'ser_ref_levels' of uniform refinement, where 'ser_ref_levels' is
// a command-line parameter.
for (int lev = 0; lev < ref_levels; lev++) {mesh.UniformRefinement();}
if (mesh.NURBSext) {mesh.SetCurvature(max(order_ad, 1));}
mesh.GetBoundingBox(bb_min, bb_max, max(order_ad, 1));
// ********DARCY SOLVE
// 5. Define a finite element space on the mesh. Here we use the
// Raviart-Thomas finite elements of the specified order.
FiniteElementCollection *hdiv_coll(new RT_FECollection(order_darcy, dim));
FiniteElementCollection *l2_coll(new L2_FECollection(order_darcy, dim));
FiniteElementSpace *R_space = new FiniteElementSpace(&mesh, hdiv_coll);
FiniteElementSpace *W_space = new FiniteElementSpace(&mesh, l2_coll);
// 6. Define the BlockStructure of the problem, i.e. define the array of
// offsets for each variable. The last component of the Array is the sum
// of the dimensions of each block.
Array<int> block_offsets(3); // number of variables + 1
block_offsets[0] = 0;
block_offsets[1] = R_space->GetVSize();
block_offsets[2] = W_space->GetVSize();
block_offsets.PartialSum();
std::cout << "***********************************************************\n";
std::cout << "dim(R) = " << block_offsets[1] - block_offsets[0] << "\n";
std::cout << "dim(W) = " << block_offsets[2] - block_offsets[1] << "\n";
std::cout << "dim(R+W) = " << block_offsets.Last() << "\n";
std::cout << "***********************************************************\n";
// 7. Define the coefficients, analytical solution, and rhs of the Darcy PDE.
ConstantCoefficient one(1.0);
VectorFunctionCoefficient fcoeff(dim, fFun);
FunctionCoefficient fnatcoeff(f_natural);
FunctionCoefficient gcoeff(gFun);
VectorFunctionCoefficient ucoeff(dim, uFun_ex);
FunctionCoefficient pcoeff(pFun_ex);
// 8. Allocate memory for solution and rhs of Darcy
MemoryType mt = device.GetMemoryType();
BlockVector x(block_offsets, mt), rhs(block_offsets, mt);
LinearForm *fform(new LinearForm);
fform->Update(R_space, rhs.GetBlock(0), 0);
fform->AddDomainIntegrator(new VectorFEDomainLFIntegrator(fcoeff));
fform->AddBoundaryIntegrator(new VectorFEBoundaryFluxLFIntegrator(fnatcoeff));
fform->Assemble();
fform->SyncAliasMemory(rhs);
LinearForm *gform(new LinearForm);
gform->Update(W_space, rhs.GetBlock(1), 0);
gform->AddDomainIntegrator(new DomainLFIntegrator(gcoeff));
gform->Assemble();
gform->SyncAliasMemory(rhs);
// 9. Assemble the finite element matrices for the Darcy operator
//
// D = [ M B^T ]
// [ B 0 ]
// where:
//
// M = \int_\Omega k u_h \cdot v_h d\Omega u_h, v_h \in R_h
// B = -\int_\Omega \div u_h q_h d\Omega u_h \in R_h, q_h \in W_h
BilinearForm *mVarf(new BilinearForm(R_space));
mVarf->AddDomainIntegrator(new VectorFEMassIntegrator(one));
mVarf->Assemble();
MixedBilinearForm *bVarf(new MixedBilinearForm(R_space, W_space));
bVarf->AddDomainIntegrator(new VectorFEDivergenceIntegrator);
bVarf->Assemble();
mVarf->Finalize();
bVarf->Finalize();
BlockOperator darcyOp(block_offsets);
TransposeOperator *Bt = NULL;
SparseMatrix &M(mVarf->SpMat());
SparseMatrix &B(bVarf->SpMat());
B *= -1.;
Bt = new TransposeOperator(&B);
darcyOp.SetBlock(0,0, &M);
darcyOp.SetBlock(0,1, Bt);
darcyOp.SetBlock(1,0, &B);
// 10. Construct the operators for preconditioner
//
// P = [ diag(M) 0 ]
// [ 0 B diag(M)^-1 B^T ]
//
// Here we use Symmetric Gauss-Seidel to approximate the inverse of the
// pressure Schur Complement
SparseMatrix *MinvBt = NULL;
Vector Md(mVarf->Height());
BlockDiagonalPreconditioner darcyPrec(block_offsets);
Solver *invM, *invS;
SparseMatrix *S = NULL;
// SparseMatrix &M(mVarf->SpMat());
M.GetDiag(Md);
Md.HostReadWrite();
// SparseMatrix &B(bVarf->SpMat());
MinvBt = Transpose(B);
for (int i = 0; i < Md.Size(); i++)
{
MinvBt->ScaleRow(i, 1./Md(i));
}
S = Mult(B, *MinvBt);
invM = new DSmoother(M);
#ifndef MFEM_USE_SUITESPARSE
invS = new GSSmoother(*S);
#else
invS = new UMFPackSolver(*S);
#endif
invM->iterative_mode = false;
invS->iterative_mode = false;
darcyPrec.SetDiagonalBlock(0, invM);
darcyPrec.SetDiagonalBlock(1, invS);
// 11. Solve the linear system with MINRES.
// Check the norm of the unpreconditioned residual.
int maxIter(1000);
real_t rtol(1.e-6);
real_t atol(1.e-10);
MINRESSolver solver;
solver.SetAbsTol(atol);
solver.SetRelTol(rtol);
solver.SetMaxIter(maxIter);
solver.SetOperator(darcyOp);
solver.SetPreconditioner(darcyPrec);
solver.SetPrintLevel(1);
x = 0.0;
solver.Mult(rhs, x);
if (solver.GetConverged())
{
std::cout << "MINRES converged in " << solver.GetNumIterations()
<< " iterations with a residual norm of "
<< solver.GetFinalNorm() << ".\n";
}
else
{
std::cout << "MINRES did not converge in " << solver.GetNumIterations()
<< " iterations. Residual norm is " << solver.GetFinalNorm()
<< ".\n";
}
// 12. Create the grid functions u and p. Compute the L2 error norms.
GridFunction u, p;
u.MakeRef(R_space, x.GetBlock(0), 0);
p.MakeRef(W_space, x.GetBlock(1), 0);
int order_quad = max(2, 2*order_darcy+1);
const IntegrationRule *irs[Geometry::NumGeom];
for (int i=0; i < Geometry::NumGeom; ++i)
{
irs[i] = &(IntRules.Get(i, order_quad));
}
real_t err_u = u.ComputeL2Error(ucoeff, irs);
real_t norm_u = ComputeLpNorm(2., ucoeff, mesh, irs);
real_t err_p = p.ComputeL2Error(pcoeff, irs);
real_t norm_p = ComputeLpNorm(2., pcoeff, mesh, irs);
std::cout << "|| u_h - u_ex || / || u_ex || = " << err_u / norm_u << "\n";
std::cout << "|| p_h - p_ex || / || p_ex || = " << err_p / norm_p << "\n";
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream u_sock(vishost, visport);
u_sock.precision(8);
u_sock << "solution\n" << mesh << u << "window_title 'Velocity'" << endl;
socketstream p_sock(vishost, visport);
p_sock.precision(8);
p_sock << "solution\n" << mesh << p << "window_title 'Pressure'" << endl;
}
// ******Forward Advection-Diffusion solve
// 13. Define the DG finite element space on the
// refined mesh of the given polynomial order.
DG_FECollection fec(order_ad, dim, BasisType::GaussLobatto);
FiniteElementSpace fes(&mesh, &fec);
int num_dofs = fes.GetNDofs();
cout << "Number of unknowns (advection diffusion problem): " << fes.GetVSize()
<< endl;
// 14. Set up and assemble the parallel bilinear and linear forms (and the
// parallel hypre matrices) corresponding to the DG discretization. The
// DGTraceIntegrator involves integrals over mesh interior faces.
const GridFunction* u_pointer = &u;
VectorGridFunctionCoefficient velocity(u_pointer);
FunctionCoefficient inflow(inflow_function);
ConstantCoefficient diff_coef(d_coef);
BilinearForm m(&fes);
m.AddDomainIntegrator(new MassIntegrator);
BilinearForm k(&fes);
k.AddDomainIntegrator(new ConvectionIntegrator(velocity, -1.0));
k.AddInteriorFaceIntegrator(new NonconservativeDGTraceIntegrator(velocity,
-1.0));
k.AddBdrFaceIntegrator(new NonconservativeDGTraceIntegrator(velocity, -1.0));
BilinearForm s(&fes);
s.AddDomainIntegrator(new DiffusionIntegrator(diff_coef));
s.AddInteriorFaceIntegrator(new DGDiffusionIntegrator(diff_coef, sigma, kappa));
s.AddBdrFaceIntegrator(new DGDiffusionIntegrator(diff_coef, sigma, kappa));
LinearForm b(&fes);
b.AddBdrFaceIntegrator(new BoundaryFlowIntegrator(inflow, velocity, -1.0));
//b.AddBdrFaceIntegrator(new DGDirichletLFIntegrator(U, diff_coef, sigma, kappa));
int skip_zeros = 0;
m.Assemble(skip_zeros);
k.Assemble(skip_zeros);
s.Assemble(skip_zeros);
b.Assemble();
m.Finalize(skip_zeros);
k.Finalize(skip_zeros);
s.Finalize(skip_zeros);
// 15. Define the initial conditions, save the corresponding grid function to
// a file and (optionally) save data in the VisIt format and initialize
// GLVis visualization.
FunctionCoefficient theta0(theta0_function);
GridFunction theta(&fes);
theta.ProjectCoefficient(theta0);
// Set up visualization, if desired.
ParaViewDataCollection *pd_forward = NULL;
if (paraview)
{
pd_forward = new ParaViewDataCollection("darcy-adv-diff-forward", &mesh);
pd_forward->SetPrefixPath("ParaView");
pd_forward->RegisterField("solution_forward", &theta);
pd_forward->SetLevelsOfDetail(order_ad);
pd_forward->SetDataFormat(VTKFormat::BINARY);
pd_forward->SetHighOrderOutput(true);
pd_forward->SetCycle(0);
pd_forward->SetTime(0.0);
pd_forward->Save();
}
// 16. Define the time-dependent evolution operator describing the ODE
// right-hand side, and perform time-integration (looping over the time
// iterations, ti, with a time-step dt).
IMEX_Evolution adv(m, k, s, b);
real_t t = 0.0;
adv.SetTime(t);
ode_solver->Init(adv);
int n_steps = (int)ceil(t_final / dt);
double dt_real = t_final / n_steps;
// Vector err_vec(n_steps-1);
std::vector<GridFunction> theta_gf_vector;
theta_gf_vector.push_back(theta);
for (int ti = 0; ti < n_steps; ti++)
{
ode_solver->Step(theta, t, dt_real);
theta_gf_vector.push_back(theta);
if (ti % vis_steps == 0 || ti == n_steps -1)
{
cout << "time step: " << ti << ", time: " << t << endl;
if (paraview)
{
pd_forward->SetCycle(ti);
pd_forward->SetTime(t);
pd_forward->Save();
}
}
}
// ******Backward Advection-Diffusion solve
// 17. Define the DG finite element space on the
// refined mesh of the given polynomial order.
DG_FECollection fec_adjoint(order_ad, dim);
FiniteElementSpace fes_adjoint(&mesh, &fec_adjoint);
// 18. Set up and assemble the parallel bilinear and linear forms (and the
// parallel hypre matrices) corresponding to the DG discretization. The
// DGTraceIntegrator involves integrals over mesh interior faces.
ConstantCoefficient zero(0.0);
GridFunctionCoefficient theta_coeff(&(theta_gf_vector[n_steps-1]));
FunctionCoefficient inflow_adj(inflow_function); //zero for now
ConstantCoefficient diff_coef_adj(-d_coef);
// FunctionCoefficient theta_exact_coeff(theta_exact);
BilinearForm m_adj(&fes_adjoint);
m_adj.AddDomainIntegrator(new MassIntegrator);
BilinearForm k_adj(&fes_adjoint);
k_adj.AddDomainIntegrator(new ConvectionIntegrator(velocity, -1.0));
k_adj.AddInteriorFaceIntegrator(new NonconservativeDGTraceIntegrator(velocity,
-1.0));
k_adj.AddBdrFaceIntegrator(new NonconservativeDGTraceIntegrator(velocity,
-1.0));
BilinearForm s_adj(&fes_adjoint);
s_adj.AddDomainIntegrator(new DiffusionIntegrator(diff_coef_adj));
s_adj.AddInteriorFaceIntegrator(new DGDiffusionIntegrator(diff_coef_adj, sigma,
kappa));
s_adj.AddBdrFaceIntegrator(new DGDiffusionIntegrator(diff_coef_adj, sigma,
kappa));
LinearForm b_adj(&fes_adjoint);
b_adj.AddDomainIntegrator(new DomainLFIntegrator(theta_coeff));
//b.AddBdrFaceIntegrator(new DGDirichletLFIntegrator(zero, diff_coef, sigma, kappa));
//int skip_zeros = 0;
m_adj.Assemble(skip_zeros);
m_adj.Finalize(skip_zeros);
k_adj.Assemble(skip_zeros);
k_adj.Finalize(skip_zeros);
s_adj.Assemble(skip_zeros);
s_adj.Finalize(skip_zeros);
b_adj.Assemble();
// 19. Define the initial conditions, save the corresponding grid function to
// a file and (optionally) save data in the VisIt format and initialize
// GLVis visualization.
GridFunction lam(&fes_adjoint);
lam.ProjectCoefficient(zero);
ParaViewDataCollection *pd_backward = NULL;
if (paraview)
{
pd_backward = new ParaViewDataCollection("darcy-adv-diff-backward", &mesh);
pd_backward->SetPrefixPath("ParaView");
pd_backward->RegisterField("solution-backward", &lam);
pd_backward->SetLevelsOfDetail(order_ad);
pd_backward->SetDataFormat(VTKFormat::BINARY);
pd_backward->SetHighOrderOutput(true);
pd_backward->SetCycle(0);
pd_backward->SetTime(t_final);
pd_backward->Save();
}
// 20. Define the time-dependent evolution operator describing the ODE
// right-hand side, and perform time-integration (looping over the time
// iterations, ti, with a time-step dt).
IMEX_Evolution adv_adj(m_adj, k_adj, s_adj, b_adj);
real_t t_adj = t_final;
adv_adj.SetTime(t_adj);
ode_solver_adj->Init(adv_adj);
// int n_steps = (int)ceil(t_final / dt);
double dt_real_adj = -dt;
std::cout << "dt back = " << dt_real_adj << std::endl;
//Vector err_vec(n_steps-1);
for (int ti = 0; ti < n_steps; ti++)
{
ode_solver_adj->Step(lam, t_adj, dt_real_adj);
Vector lam_vals(num_dofs);
Vector theta_values(num_dofs);
const GridFunction* theta_gf = theta_coeff.GetGridFunction();
theta_gf->GetTrueDofs(theta_values);
lam.GetTrueDofs(lam_vals);
theta_coeff = *(new GridFunctionCoefficient(&(theta_gf_vector[n_steps - ti -
1])));
b_adj = *(new LinearForm(&fes_adjoint));
b_adj.AddDomainIntegrator(new DomainLFIntegrator(theta_coeff));
b_adj.Assemble();
if (ti % vis_steps == 0 || ti == n_steps - 1)
{
cout << "time step: " << ti << ", time: " << t_adj << endl;
if (paraview)
{
pd_backward->SetCycle(ti);
pd_backward->SetTime(t_adj);
pd_backward->Save();
}
}
}
// 21. Free the used memory.
// delete &ode_solver;
// delete &adv;
// delete &adv_adj;
delete fform;
delete gform;
delete invM;
delete invS;
delete S;
delete Bt;
delete MinvBt;
delete mVarf;
delete bVarf;
delete W_space;
delete R_space;
delete l2_coll;
delete hdiv_coll;
// delete &b_adj;
// delete &theta_coeff;
return 0;
}
void uFun_ex(const Vector & x, Vector & u)
{
real_t xi(x(0));
real_t yi(x(1));
real_t zi(0.0);
if (x.Size() == 3)
{
zi = x(2);
}
u(0) = - exp(xi)*sin(yi)*cos(zi);
u(1) = - exp(xi)*cos(yi)*cos(zi);
if (x.Size() == 3)
{
u(2) = exp(xi)*sin(yi)*sin(zi);
}
}
// Change if needed
real_t pFun_ex(const Vector & x)
{
real_t xi(x(0));
real_t yi(x(1));
real_t zi(0.0);
if (x.Size() == 3)
{
zi = x(2);
}
return exp(xi)*sin(yi)*cos(zi);
}
void fFun(const Vector & x, Vector & f)
{
f = 0.0;
}
real_t gFun(const Vector & x)
{
if (x.Size() == 3)
{
return -pFun_ex(x);
}
else
{
return 0;
}
}
real_t f_natural(const Vector & x)
{
return (-pFun_ex(x));
}
// Implementation of class IMEX_Evolution
IMEX_Evolution::IMEX_Evolution(BilinearForm &M_, BilinearForm &K_,
BilinearForm &S_, const Vector &b_)
: SplitTimeDependentOperator(M_.FESpace()->GetTrueVSize()),
M(M_), K(K_), S(S_), b(b_), z(height)
{
Array<int> ess_tdof_list;
if (M.GetAssemblyLevel() == AssemblyLevel::LEGACY)
{
M_prec = make_unique<DSmoother>(M.SpMat());
M_solver.SetOperator(M.SpMat());
dg_solver = make_unique<DG_Solver>(M.SpMat(), K.SpMat(), S.SpMat(),
*M.FESpace());
}
else
{
M_prec = make_unique<OperatorJacobiSmoother>(M, ess_tdof_list);
M_solver.SetOperator(M);
dg_solver = NULL;
}
M_solver.SetPreconditioner(*M_prec);
M_solver.iterative_mode = false;
M_solver.SetRelTol(1e-9);
M_solver.SetAbsTol(0.0);
M_solver.SetMaxIter(100);
M_solver.SetPrintLevel(0);
}
void IMEX_Evolution::Mult1(const Vector &x, Vector &y) const
{
// Perform the explicit step
// y = M^{-1} (K x + b)
K.Mult(x, z);
z += b;
M_solver.Mult(z, y);
}
void IMEX_Evolution::ImplicitSolve2(const real_t dt, const Vector &x, Vector &k)
{
// Perform the implicit step
// solve for k, k = -(M+dt S)^{-1} S x
MFEM_VERIFY(dg_solver != NULL,
"Implicit time integration is not supported with partial assembly");
S.Mult(x, z);
z*= -1.0;
dg_solver->SetTimeStep(dt);
dg_solver->Mult(z, k);
}
// Initial condition
double theta0_function(const Vector &x)
{
int dim = x.Size();
// map to the reference [-1,1] domain
Vector X(dim);
// for (int i = 0; i < dim; i++)
// {
// double center = (bb_min[i] + bb_max[i]) * 0.5;
// X(i) = 2 * (x(i) - center) / (bb_max[i] - bb_min[i]);
// }
double rx = 0.45, ry = 0.25, cx = 0., cy = -0.2, w = 10.;
if (dim == 3)
{
const double s = (1. + 0.25*cos(2*M_PI*x(2)));
rx *= s;
ry *= s;
}
return ( erfc(w*(x(0)-cx-rx))*erfc(-w*(x(0)-cx+rx))*erfc(w*(x(1)-cy-ry))*erfc(
-w*(x(1)-cy+ry)) )/16;
}
//forcing term
real_t forcing_function(const Vector &x, real_t t)
{
int dim = x.Size();
//map to the reference [-1,1] domain
Vector X(dim);
for (int i = 0; i < dim; i++)
{
double center = (bb_min[i] + bb_max[i]) * 0.5;
X(i) = 2 * (x(i) - center) / (bb_max[i] - bb_min[i]);
}
return 0.0;
}
// Inflow boundary condition (zero for the problems considered in this example)
double inflow_function(const Vector &x)
{
return 0.0;
}
+7 -7
View File
@@ -12,10 +12,11 @@
# Use the MFEM build directory
MFEM_DIR ?= ../..
MFEM_BUILD_DIR ?= ../..
MFEM_INSTALL_DIR ?= ../../mfem
SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/mtop/,)
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
CONFIG_MK = $(MFEM_BUILD_DIR)/config/config.mk
# Use the MFEM install directory
# MFEM_INSTALL_DIR = ../../mfem
# CONFIG_MK = $(MFEM_INSTALL_DIR)/share/mfem/config.mk
# Include defaults.mk to get XLINKER
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
@@ -24,11 +25,10 @@ include $(DEFAULTS_MK)
MFEM_LIB_FILE = mfem_is_not_built
-include $(CONFIG_MK)
MTOP_COMMON_SRC = mtop_integrators.cpp paramnonlinearform.cpp pparamnonlinearform.cpp
MTOP_COMMON_SRC = darcy_heat_transfer_ex.cpp
MTOP_COMMON_OBJ = $(MTOP_COMMON_SRC:.cpp=.o)
SEQ_MINIAPPS = seqheat
SEQ_MINIAPPS = seqheat darcy_heat_transfer_ex
PAR_MINIAPPS = parheat
ifeq ($(MFEM_USE_MPI),NO)
MINIAPPS = $(SEQ_MINIAPPS)
@@ -76,4 +76,4 @@ clean-build:
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@rm -rf SeqHeat* ParHeat*
@rm -rf SeqHeat* ParHeat* for_adv_diff_solve* darcy_heat_transfer_ex*
-390
View File
@@ -1,390 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "mtop_integrators.hpp"
namespace mfem
{
real_t ParametricLinearDiffusion::GetElementEnergy(const
Array<const FiniteElement *> &el,
const Array<const FiniteElement *> &pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &pelfun)
{
int dof_u0 = el[0]->GetDof();
int dof_r0 = pel[0]->GetDof();
int dim = el[0]->GetDim();
int spaceDim = Tr.GetSpaceDim();
if (dim != spaceDim)
{
mfem::mfem_error("ParametricLinearDiffusion::GetElementEnergy"
" is not defined on manifold meshes");
}
// shape functions
Vector shu0(dof_u0);
Vector shr0(dof_r0);
DenseMatrix dsu0(dof_u0,dim);
DenseMatrix B(dof_u0, 4);
B=0.0;
real_t w;
Vector param(1); param=0.0;
Vector uu(4); uu=0.0;
real_t energy =0.0;
const IntegrationRule *ir;
{
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0])
+pel[0]->GetOrder();
ir=&IntRules.Get(Tr.GetGeometryType(),order);
}
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
Tr.SetIntPoint(&ip);
w=Tr.Weight();
w = ip.weight * w;
el[0]->CalcPhysDShape(Tr,dsu0);
el[0]->CalcPhysShape(Tr,shu0);
pel[0]->CalcPhysShape(Tr,shr0);
param[0]=shr0*(*pelfun[0]);
// set the matrix B
for (int jj=0; jj<dim; jj++)
{
B.SetCol(jj,dsu0.GetColumn(jj));
}
B.SetCol(3,shu0);
B.MultTranspose(*elfun[0],uu);
energy=energy+w * qfun.QEnergy(Tr,ip,param,uu);
}
return energy;
}
void ParametricLinearDiffusion::AssembleElementVector(const
Array<const FiniteElement *> &el,
const Array<const FiniteElement *> &pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &pelfun,
const Array<Vector *> &elvec)
{
int dof_u0 = el[0]->GetDof();
int dof_r0 = pel[0]->GetDof();
int dim = el[0]->GetDim();
elvec[0]->SetSize(dof_u0);
*elvec[0]=0.0;
int spaceDim = Tr.GetSpaceDim();
if (dim != spaceDim)
{
mfem::mfem_error("ParametricLinearDiffusion::AssembleElementVector"
" is not defined on manifold meshes");
}
// shape functions
Vector shu0(dof_u0);
Vector shr0(dof_r0);
DenseMatrix dsu0(dof_u0,dim);
DenseMatrix B(dof_u0, 4);
B=0.0;
real_t w;
Vector param(1); param=0.0;
Vector uu(4); uu=0.0;
Vector rr(4);
Vector lvec; lvec.SetSize(dof_u0);
const IntegrationRule *ir = nullptr;
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0])
+pel[0]->GetOrder();
ir=&IntRules.Get(Tr.GetGeometryType(),order);
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
Tr.SetIntPoint(&ip);
w=Tr.Weight();
w = ip.weight * w;
el[0]->CalcPhysDShape(Tr,dsu0);
el[0]->CalcPhysShape(Tr,shu0);
pel[0]->CalcPhysShape(Tr,shr0);
param[0]=shr0*(*pelfun[0]);
// set the matrix B
for (int jj=0; jj<dim; jj++)
{
B.SetCol(jj,dsu0.GetColumn(jj));
}
B.SetCol(3,shu0);
B.MultTranspose(*elfun[0],uu);
qfun.QResidual(Tr,ip,param, uu, rr);
B.Mult(rr,lvec);
elvec[0]->Add(w,lvec);
}
}
void ParametricLinearDiffusion::AssembleElementGrad(const
Array<const FiniteElement *> &el,
const Array<const FiniteElement *> &pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &pelfun,
const Array2D<DenseMatrix *> &elmats)
{
int dof_u0 = el[0]->GetDof();
int dof_r0 = pel[0]->GetDof();
int dim = el[0]->GetDim();
DenseMatrix* K=elmats(0,0);
K->SetSize(dof_u0,dof_u0);
(*K)=0.0;
int spaceDim = Tr.GetSpaceDim();
if (dim != spaceDim)
{
mfem::mfem_error("ParametricLinearDiffusion::AssembleElementGrad"
" is not defined on manifold meshes");
}
// shape functions
Vector shu0(dof_u0);
Vector shr0(dof_r0);
DenseMatrix dsu0(dof_u0,dim);
DenseMatrix B(dof_u0, 4);
DenseMatrix A(dof_u0, 4);
B=0.0;
real_t w;
Vector param(1); param=0.0;
Vector uu(4); uu=0.0;
DenseMatrix hh(4,4);
Vector lvec; lvec.SetSize(dof_u0);
const IntegrationRule *ir = nullptr;
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0])
+pel[0]->GetOrder();
ir=&IntRules.Get(Tr.GetGeometryType(),order);
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
Tr.SetIntPoint(&ip);
w = Tr.Weight();
w = ip.weight * w;
el[0]->CalcPhysDShape(Tr,dsu0);
el[0]->CalcPhysShape(Tr,shu0);
pel[0]->CalcPhysShape(Tr,shr0);
param[0]=shr0*(*pelfun[0]);
// set the matrix B
for (int jj=0; jj<dim; jj++)
{
B.SetCol(jj,dsu0.GetColumn(jj));
}
B.SetCol(3,shu0);
B.MultTranspose(*elfun[0],uu);
qfun.QGradResidual(Tr,ip,param,uu,hh);
Mult(B,hh,A);
AddMult_a_ABt(w,A,B,*K);
}
}
void ParametricLinearDiffusion::AssemblePrmElementVector(
const Array<const FiniteElement *> &el,
const Array<const FiniteElement *> &pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &alfun,
const Array<const Vector *> &pelfun,
const Array<Vector *> &elvec)
{
int dof_u0 = el[0]->GetDof();
int dof_r0 = pel[0]->GetDof();
int dim = el[0]->GetDim();
Vector& e0 = *(elvec[0]);
e0.SetSize(dof_r0);
e0=0.0;
int spaceDim = Tr.GetSpaceDim();
if (dim != spaceDim)
{
mfem::mfem_error("ParametricLinearDiffusion::AssemblePrmElementVector"
" is not defined on manifold meshes");
}
// shape functions
Vector shu0(dof_u0);
Vector shr0(dof_r0);
DenseMatrix dsu0(dof_u0,dim);
DenseMatrix B(dof_u0, 4);
B=0.0;
real_t w;
Vector param(1); param=0.0;
Vector uu(4); uu=0.0;
Vector aa(4); aa=0.0;
Vector rr(1);
Vector lvec0; lvec0.SetSize(dof_r0);
const IntegrationRule *ir;
{
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0])
+pel[0]->GetOrder();
ir=&IntRules.Get(Tr.GetGeometryType(),order);
}
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
Tr.SetIntPoint(&ip);
w=Tr.Weight();
w = ip.weight * w;
el[0]->CalcPhysDShape(Tr,dsu0);
el[0]->CalcPhysShape(Tr,shu0);
pel[0]->CalcPhysShape(Tr,shr0);
param[0]=shr0*(*pelfun[0]);
// set the matrix B
for (int jj=0; jj<dim; jj++)
{
B.SetCol(jj,dsu0.GetColumn(jj));
}
B.SetCol(3,shu0);
B.MultTranspose(*elfun[0],uu);
B.MultTranspose(*alfun[0],aa);
qfun.AQResidual(Tr, ip, param, uu, aa, rr);
lvec0=shr0;
lvec0*=rr[0];
e0.Add(w,lvec0);
}
}
real_t DiffusionObjIntegrator::GetElementEnergy(const
Array<const FiniteElement *> &el,
ElementTransformation &Tr,
const Array<const Vector *> &elfun)
{
int dof_u0 = el[0]->GetDof();
int dim = el[0]->GetDim();
int spaceDim = Tr.GetSpaceDim();
if (dim != spaceDim)
{
mfem::mfem_error("DiffusionObjIntegrator::GetElementEnergy"
" is not defined on manifold meshes");
}
// shape functions
Vector shu0(dof_u0);
real_t w;
real_t val;
real_t energy = 0.0;
const IntegrationRule *ir;
{
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0]);
ir=&IntRules.Get(Tr.GetGeometryType(),order);
}
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
Tr.SetIntPoint(&ip);
w=Tr.Weight();
w = ip.weight * w;
el[0]->CalcPhysShape(Tr,shu0);
val=shu0*(*elfun[0]);
energy=energy + w * val * val;
}
return 0.5*energy;
}
void DiffusionObjIntegrator::AssembleElementVector(const
Array<const FiniteElement *> &el,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<Vector *> &elvec)
{
int dof_u0 = el[0]->GetDof();
int dim = el[0]->GetDim();
int spaceDim = Tr.GetSpaceDim();
elvec[0]->SetSize(dof_u0);
*elvec[0]=0.0;
if (dim != spaceDim)
{
mfem::mfem_error("DiffusionObjIntegrator::GetElementEnergy"
" is not defined on manifold meshes");
}
// shape functions
Vector shu0(dof_u0);
real_t w;
real_t val;
const IntegrationRule *ir;
{
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0]);
ir=&IntRules.Get(Tr.GetGeometryType(),order);
}
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
Tr.SetIntPoint(&ip);
w=Tr.Weight();
w = ip.weight * w;
el[0]->CalcPhysShape(Tr,shu0);
val=shu0*(*elfun[0]);
elvec[0]->Add(w*val,shu0);
}
}
} // end mfem namespace
-233
View File
@@ -1,233 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MTOPINTEGRATORS_HPP
#define MTOPINTEGRATORS_HPP
#include "mfem.hpp"
#include "paramnonlinearform.hpp"
#include <map>
namespace mfem
{
/// Base class for representing function at integration points.
class BaseQFunction
{
public:
virtual ~BaseQFunction() {}
/// Returns a user defined string identifying the function.
virtual std::string GetType()=0;
// Returns the energy at an integration point.
virtual
real_t QEnergy(ElementTransformation &T, const IntegrationPoint &ip,
mfem::Vector &dd, mfem::Vector &uu)
{
return 0.0;
}
// Returns the residual at an integration point.
virtual
void QResidual(ElementTransformation &T, const IntegrationPoint &ip,
mfem::Vector &dd, mfem::Vector &uu, mfem::Vector &rr)=0;
/// Returns the gradient of the residual at a integration point.
virtual
void QGradResidual(ElementTransformation &T, const IntegrationPoint &ip,
mfem::Vector &dd, mfem::Vector &uu, mfem::DenseMatrix &hh)=0;
/// Returns the gradient of the residual with respect to the design
/// parameters, multiplied by the adjoint.
virtual
void AQResidual(ElementTransformation &T, const IntegrationPoint &ip,
mfem::Vector &dd, mfem::Vector &uu,
mfem::Vector &aa, mfem::Vector &rr)=0;
};
/* QLinearDiffusion implements methods for computing the energy, the residual,
* gradient of the residual and the product of the adjoint fields with the
* derivative of the residual with respect to the parameters. All computations
* are performed at a integration point. Therefore the vectors (vv,uu,aa,rr ..)
* hold the fields' values and the fields' derivatives at the integration
* point. For example for a single scalar parametric field representing the
* density in topology optimization the vector dd will have size one and the
* element will be the density at the integration point. The map between state
* and parameter is not fixed and depends on the implementation of the QFunction
* class. */
class QLinearDiffusion:public BaseQFunction
{
public:
QLinearDiffusion(mfem::Coefficient& diffco, mfem::Coefficient& hsrco,
real_t pp=1.0, real_t minrho=1e-7, real_t betac=4.0, real_t etac=0.5):
diff(diffco),load(hsrco), powerc(pp), rhomin(minrho), beta(betac), eta(etac)
{
}
std::string GetType() override
{
return "QLinearDiffusion";
}
real_t QEnergy(ElementTransformation &T, const IntegrationPoint &ip,
Vector &dd, Vector &uu) override
{
// dd[0] - density
// uu[0] - grad_x
// uu[1] - grad_y
// uu[2] - grad_z
// uu[3] - temperature/scalar field
real_t di=diff.Eval(T,ip);
real_t ll=load.Eval(T,ip);
// Computes the physical density using projection.
real_t rz=0.5+0.5*std::tanh(beta*(dd[0]-eta)); //projection
// Computes the diffusion coefficient at the integration point.
real_t fd=di*(std::pow(rz,powerc)+rhomin);
// Computes the sum of the energy and the product of the temperature and
// the external input at the integration point.
real_t rez = 0.5*(uu[0]*uu[0]+uu[1]*uu[1]+uu[2]*uu[2])*fd-uu[3]*ll;
return rez;
}
/// Returns the derivative of QEnergy with respect to the state vector uu.
void QResidual(ElementTransformation &T, const IntegrationPoint &ip,
Vector &dd, Vector &uu, Vector &rr) override
{
real_t di=diff.Eval(T,ip);
real_t ll=load.Eval(T,ip);
real_t rz=0.5+0.5*std::tanh(beta*(dd[0]-eta));
real_t fd=di*(std::pow(rz,powerc)+rhomin);
rr[0]=uu[0]*fd;
rr[1]=uu[1]*fd;
rr[2]=uu[2]*fd;
rr[3]=-ll;
}
// Returns the derivative, with respect to the density, of the product of
// the adjoint field with the residual at the integration point ip.
void AQResidual(ElementTransformation &T, const IntegrationPoint &ip,
Vector &dd, Vector &uu, Vector &aa, Vector &rr) override
{
real_t di=diff.Eval(T,ip);
real_t tt=std::tanh(beta*(dd[0]-eta));
real_t rz=0.5+0.5*tt;
real_t fd=di*powerc*std::pow(rz,powerc-1.0)*0.5*(1.0-tt*tt)*beta;
rr[0] = -(aa[0]*uu[0]+aa[1]*uu[1]+aa[2]*uu[2])*fd;
}
// Returns the gradient of the residual with respect to the state vector at
// the integration point ip.
void QGradResidual(ElementTransformation &T, const IntegrationPoint &ip,
Vector &dd, Vector &uu, DenseMatrix &hh) override
{
real_t di=diff.Eval(T,ip);
real_t tt=std::tanh(beta*(dd[0]-eta));
real_t rz=0.5+0.5*tt;
real_t fd=di*(std::pow(rz,powerc)+rhomin);
hh=0.0;
hh(0,0)=fd;
hh(1,1)=fd;
hh(2,2)=fd;
hh(3,3)=0.0;
}
private:
mfem::Coefficient& diff; //diffusion coefficient
mfem::Coefficient& load; //load coefficient
real_t powerc; //penalization coefficient
real_t rhomin; //lower bound for the density
real_t beta; //controls the sharpness of the projection
real_t eta; //projection threshold for tanh
};
/// Provides implementation of an integrator for linear diffusion with
/// parametrization provided by a density field. The setup is standard for
/// topology optimization problems.
class ParametricLinearDiffusion: public ParametricBNLFormIntegrator
{
public:
ParametricLinearDiffusion(BaseQFunction& qfunm): qfun(qfunm)
{
}
/// Computes the local energy.
real_t GetElementEnergy(const Array<const FiniteElement *> &el,
const Array<const FiniteElement *> &pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &pelfun) override;
/// Computes the element's residual.
void AssembleElementVector(const Array<const FiniteElement *> &el,
const Array<const FiniteElement *> &pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &pelfun,
const Array<Vector *> &elvec) override;
/// Computes the stiffness/tangent matrix.
void AssembleElementGrad(const Array<const FiniteElement *> &el,
const Array<const FiniteElement *> &pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &pelfun,
const Array2D<DenseMatrix *> &elmats) override;
/// Computes the product of the adjoint solution and the derivative of the
/// residual with respect to the parametric fields.
void AssemblePrmElementVector(const Array<const FiniteElement *> &el,
const Array<const FiniteElement *> &pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &alfun,
const Array<const Vector *> &pelfun,
const Array<Vector *> &elvec) override;
private:
BaseQFunction& qfun;
};
/// Computes an example of nonlinear objective
/// $\int \rm{field}*\rm{field}*\rm{weight})\rm{d}\Omega_e$.
class DiffusionObjIntegrator:public BlockNonlinearFormIntegrator
{
public:
DiffusionObjIntegrator()
{
}
/// Returns the objective contribution at element level.
real_t GetElementEnergy(const Array<const FiniteElement *> &el,
ElementTransformation &Tr,
const Array<const Vector *> &elfun) override;
/// Returns the gradient of the objective contribution at element level.
void AssembleElementVector(const Array<const FiniteElement *> &el,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<Vector *> &elvec) override;
};
}
#endif
File diff suppressed because it is too large Load Diff
-300
View File
@@ -1,300 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_PRMNONLINEARFORM
#define MFEM_PRMNONLINEARFORM
#include "mfem.hpp"
namespace mfem
{
/** The abstract base class ParametricBNLFormIntegrator is a generalization of
the BlockNonlinearFormIntegrator class suitable for block state and
parameter vectors. */
class ParametricBNLFormIntegrator
{
public:
/// Compute the local energy
virtual real_t GetElementEnergy(const Array<const FiniteElement *>&el,
const Array<const FiniteElement *>&pel,
ElementTransformation &Tr,
const Array<const Vector *>&elfun,
const Array<const Vector *>&pelfun);
/// Perform the local action of the BlockNonlinearFormIntegrator
virtual void AssembleElementVector(const Array<const FiniteElement *> &el,
const Array<const FiniteElement *>&pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *>&pelfun,
const Array<Vector *> &elvec);
/// Perform the local action of the BlockNonlinearFormIntegrator on element
/// faces
virtual void AssembleFaceVector(const Array<const FiniteElement *> &el1,
const Array<const FiniteElement *> &el2,
const Array<const FiniteElement *> &pel1,
const Array<const FiniteElement *> &pel2,
FaceElementTransformations &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *>&pelfun,
const Array<Vector *> &elvect);
/// Perform the local action on the parameters of the BNLFormIntegrator
virtual void AssemblePrmElementVector(const Array<const FiniteElement *> &el,
const Array<const FiniteElement *>&pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &alfun,
const Array<const Vector *>&pelfun,
const Array<Vector *> &pelvec);
/// Perform the local action on the parameters of the BNLFormIntegrator on
/// faces
virtual void AssemblePrmFaceVector(const Array<const FiniteElement *> &el1,
const Array<const FiniteElement *> &el2,
const Array<const FiniteElement *> &pel1,
const Array<const FiniteElement *> &pel2,
FaceElementTransformations &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *> &alfun,
const Array<const Vector *>&pelfun,
const Array<Vector *> &pelvect);
/// Assemble the local gradient matrix
virtual void AssembleElementGrad(const Array<const FiniteElement*> &el,
const Array<const FiniteElement *>&pel,
ElementTransformation &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *>&pelfun,
const Array2D<DenseMatrix *> &elmats);
/// Assemble the local gradient matrix on faces of the elements
virtual void AssembleFaceGrad(const Array<const FiniteElement *>&el1,
const Array<const FiniteElement *>&el2,
const Array<const FiniteElement *> &pel1,
const Array<const FiniteElement *> &pel2,
FaceElementTransformations &Tr,
const Array<const Vector *> &elfun,
const Array<const Vector *>&pelfun,
const Array2D<DenseMatrix *> &elmats);
virtual ~ParametricBNLFormIntegrator() { }
};
/** @brief A class representing a general parametric block nonlinear operator
defined on the Cartesian product of multiple FiniteElementSpace%s. */
class ParametricBNLForm : public Operator
{
protected:
/// FE spaces on which the form lives.
Array<FiniteElementSpace*> fes;
/// FE spaces for the parametric fields
Array<FiniteElementSpace*> paramfes;
int paramheight;
int paramwidth;
/// Set of Domain Integrators to be assembled (added).
Array<ParametricBNLFormIntegrator*> dnfi;
/// Set of interior face Integrators to be assembled (added).
Array<ParametricBNLFormIntegrator*> fnfi;
/// Set of Boundary Face Integrators to be assembled (added).
Array<ParametricBNLFormIntegrator*> bfnfi;
Array<Array<int>*> bfnfi_marker;
/** Auxiliary block-vectors for wrapping input and output vectors or holding
GridFunction-like block-vector data (e.g. in parallel). */
mutable BlockVector xs, ys;
mutable BlockVector prmxs, prmys;
/** Auxiliary block-vectors for holding GridFunction-like block-vector data
(e.g. in parallel). */
mutable BlockVector xsv;
/** Auxiliary block-vectors for holding GridFunction-like block-vector data
for the parameter fields (e.g. in parallel). */
mutable BlockVector xdv;
/** Auxiliary block-vectors for holding GridFunction-like block-vector data
for the adjoint fields (e.g. in parallel). */
mutable BlockVector adv;
mutable Array2D<SparseMatrix*> Grads, cGrads;
mutable BlockOperator *BlockGrad;
// A list of the offsets
Array<int> block_offsets;
Array<int> block_trueOffsets;
// A list with the offsets for the parametric fields
Array<int> paramblock_offsets;
Array<int> paramblock_trueOffsets;
// Array of Arrays of tdofs for each space in 'fes'
Array<Array<int> *> ess_tdofs;
// Array of Arrays of tdofs for each space in 'paramfes'
Array<Array<int> *> paramess_tdofs;
/// Array of pointers to the prolongation matrix of fes, may be NULL
Array<const Operator *> P;
/// Array of pointers to the prolongation matrix of paramfes, may be NULL
Array<const Operator *> Pparam;
/// Array of results of dynamic-casting P to SparseMatrix pointer
Array<const SparseMatrix *> cP;
/// Array of results of dynamic-casting Pparam to SparseMatrix pointer
Array<const SparseMatrix *> cPparam;
/// Indicator if the Operator is part of a parallel run
bool is_serial = true;
/// Indicator if the Operator needs prolongation on assembly
bool needs_prolongation = false;
/// Indicator if the Operator needs prolongation on assembly
bool prmneeds_prolongation = false;
mutable BlockVector aux1, aux2;
mutable BlockVector prmaux1, prmaux2;
const BlockVector &Prolongate(const BlockVector &bx) const;
const BlockVector &ParamProlongate(const BlockVector &bx) const;
real_t GetEnergyBlocked(const BlockVector &bx, const BlockVector &dx) const;
/// Specialized version of Mult() for BlockVector%s
/// Block L-Vector to Block L-Vector
void MultBlocked(const BlockVector &bx, const BlockVector &dx,
BlockVector &by) const;
/// Specialized version of Mult() for BlockVector%s
/// Block L-Vector to Block L-Vector
/// bx - state vector, ax - adjoint vector, dx - parametric fields
/// dy = ax' d(residual(bx))/d(dx)
void MultParamBlocked(const BlockVector &bx, const BlockVector & ax,
const BlockVector &dx, BlockVector &dy) const;
/// Specialized version of GetGradient() for BlockVector
void ComputeGradientBlocked(const BlockVector &bx, const BlockVector &dx) const;
public:
/// Construct an empty BlockNonlinearForm. Initialize with SetSpaces().
ParametricBNLForm();
/// Construct a BlockNonlinearForm on the given set of FiniteElementSpace%s.
ParametricBNLForm(Array<FiniteElementSpace *> &statef,
Array<FiniteElementSpace *> &paramf);
/// Return the @a k-th FE space of the ParametricBNLForm.
FiniteElementSpace *FESpace(int k) { return fes[k]; }
/// Return the @a k-th parametric FE space of the ParametricBNLForm.
FiniteElementSpace *ParamFESpace(int k) { return paramfes[k]; }
/// Return the @a k-th FE space of the BlockNonlinearForm (const version).
const FiniteElementSpace *FESpace(int k) const { return fes[k]; }
/// Return the @a k-th parametric FE space of the BlockNonlinearForm (const
/// version).
const FiniteElementSpace *ParamFESpace(int k) const { return paramfes[k]; }
/// Return the integrators
Array<ParametricBNLFormIntegrator*>& GetDNFI() { return dnfi;}
/// (Re)initialize the ParametricBNLForm.
/** After a call to SetSpaces(), the essential b.c. must be set again. */
void SetSpaces(Array<FiniteElementSpace *> &statef,
Array<FiniteElementSpace *> &paramf);
/// Return the regular dof offsets.
const Array<int> &GetBlockOffsets() const { return block_offsets; }
/// Return the true-dof offsets.
const Array<int> &GetBlockTrueOffsets() const { return block_trueOffsets; }
/// Return the regular dof offsets for the parameters.
const Array<int> &ParamGetBlockOffsets() const { return paramblock_offsets; }
/// Return the true-dof offsets for the parameters.
const Array<int> &ParamGetBlockTrueOffsets() const { return paramblock_trueOffsets; }
/// Adds new Domain Integrator.
void AddDomainIntegrator(ParametricBNLFormIntegrator *nlfi)
{ dnfi.Append(nlfi); }
/// Adds new Interior Face Integrator.
void AddInteriorFaceIntegrator(ParametricBNLFormIntegrator *nlfi)
{ fnfi.Append(nlfi); }
/// Adds new Boundary Face Integrator.
void AddBdrFaceIntegrator(ParametricBNLFormIntegrator *nlfi)
{ bfnfi.Append(nlfi); bfnfi_marker.Append(NULL); }
/** @brief Adds new Boundary Face Integrator, restricted to specific boundary
attributes. */
void AddBdrFaceIntegrator(ParametricBNLFormIntegrator *nlfi,
Array<int> &bdr_marker);
/// Set the essential boundary conditions.
virtual void SetEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
Array<Vector *> &rhs);
/// Set the essential boundary conditions on the parametric fields.
virtual void SetParamEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
Array<Vector *> &rhs);
/// Computes the energy for a state vector x.
virtual real_t GetEnergy(const Vector &x) const;
/// Method is only called in serial, the parallel version calls MultBlocked
/// directly.
void Mult(const Vector &x, Vector &y) const override;
/// Method is only called in serial, the parallel version calls MultBlocked
/// directly.
virtual void ParamMult(const Vector &x, Vector &y) const;
/// Method is only called in serial, the parallel version calls
/// GetGradientBlocked directly.
BlockOperator &GetGradient(const Vector &x) const override;
/// Set the state fields
virtual void SetStateFields(const Vector &xv) const;
/// Set the adjoint fields
virtual void SetAdjointFields(const Vector &av) const;
/// Set the parameters/design fields
virtual void SetParamFields(const Vector &dv) const;
/// Destructor.
virtual ~ParametricBNLForm();
};
}
#endif
-354
View File
@@ -1,354 +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.
//
// ----------------------------------------------------------------
// ParHeat Miniapp: Gradients of PDE constrained objective function
// ----------------------------------------------------------------
// (Parallel Version)
//
// The following example computes the gradients of a specified objective
// function with respect to parametric fields. The objective function is having
// the following form f(u(\rho)) where u(\rho) is a solution of a specific state
// problem (in the example that is the diffusion equation), and \rho is a
// parametric field discretized by finite elements. The parametric field (also
// called density in topology optimization) controls the coefficients of the
// state equation. For the considered case, the density controls the diffusion
// coefficient within the computational domain.
//
// For more information, the users are referred to:
//
// Hinze, M.; Pinnau, R.; Ulbrich, M. & Ulbrich, S.
// Optimization with PDE Constraints
// Springer Netherlands, 2009
//
// Bendsøe, M. P. & Sigmund, O.
// Topology Optimization - Theory, Methods and Applications
// Springer Verlag, Berlin Heidelberg, 2003
//
// Compile with: make parheat
//
// Sample runs:
//
// mpirun -np 4 parheat --visualization
// mpirun -np 4 parheat --visualization -m ../../data/beam-quad.mesh
#include "mfem.hpp"
#include <fstream>
#include <iostream>
#include "pparamnonlinearform.hpp"
#include "mtop_integrators.hpp"
using namespace mfem;
int main(int argc, char *argv[])
{
// 1. Initialize MPI and HYPRE.
mfem::Mpi::Init(argc, argv);
int myrank = mfem::Mpi::WorldRank();
mfem::Hypre::Init();
// Parse command-line options.
const char *mesh_file = "../../data/star.mesh";
int order = 1;
bool static_cond = false;
int ser_ref_levels = 1;
int par_ref_levels = 1;
real_t newton_rel_tol = 1e-7;
real_t newton_abs_tol = 1e-12;
int newton_iter = 10;
int print_level = 1;
bool visualization = false;
mfem::OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&ser_ref_levels,
"-rs",
"--refine-serial",
"Number of times to refine the mesh uniformly in serial.");
args.AddOption(&par_ref_levels,
"-rp",
"--refine-parallel",
"Number of times to refine the mesh uniformly in parallel.");
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree) or -1 for"
" isoparametric space.");
args.AddOption(&visualization,
"-vis",
"--visualization",
"-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&static_cond, "-sc", "--static-condensation", "-no-sc",
"--no-static-condensation", "Enable static condensation.");
args.AddOption(&newton_rel_tol,
"-rel",
"--relative-tolerance",
"Relative tolerance for the Newton solve.");
args.AddOption(&newton_abs_tol,
"-abs",
"--absolute-tolerance",
"Absolute tolerance for the Newton solve.");
args.AddOption(&newton_iter,
"-it",
"--newton-iterations",
"Maximum iterations for the Newton solve.");
args.Parse();
if (!args.Good())
{
if (myrank == 0)
{
args.PrintUsage(std::cout);
}
return 1;
}
if (myrank == 0)
{
args.PrintOptions(std::cout);
}
// Read the (serial) mesh from the given mesh file on all processors. We
// can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
// and volume meshes with the same code.
mfem::Mesh mesh(mesh_file, 1, 1);
int dim = mesh.Dimension();
// Refine the serial mesh on all processors to increase the resolution. In
// this example we do 'ref_levels' of uniform refinement. We choose
// 'ref_levels' to be the largest number that gives a final mesh with no
// more than 10,000 elements.
{
int ref_levels =
(int)floor(log(10000./mesh.GetNE())/log(2.)/dim);
for (int l = 0; l < ref_levels; l++)
{
mesh.UniformRefinement();
}
}
// Define a parallel mesh by a partitioning of the serial mesh. Refine
// this mesh further in parallel to increase the resolution. Once the
// parallel mesh is defined, the serial mesh can be deleted.
mfem::ParMesh pmesh(MPI_COMM_WORLD, mesh);
mesh.Clear();
{
for (int l = 0; l < par_ref_levels; l++)
{
pmesh.UniformRefinement();
}
}
// Define the Diffusion coefficient.
mfem::ConstantCoefficient* diffco=new mfem::ConstantCoefficient(1.0);
// Define the Heat source.
mfem::ConstantCoefficient* loadco=new mfem::ConstantCoefficient(1.0);
// Define the q-function.
mfem::QLinearDiffusion* qfun=new mfem::QLinearDiffusion(*diffco,*loadco,1.0,
1e-7,4.0,0.5);
// Define FE collection and space for the state solution.
mfem::H1_FECollection sfec(order, dim);
mfem::ParFiniteElementSpace* sfes=new mfem::ParFiniteElementSpace(&pmesh,&sfec,
1);
// Define FE collection and space for the density field.
mfem::L2_FECollection pfec(order, dim);
mfem::ParFiniteElementSpace* pfes=new mfem::ParFiniteElementSpace(&pmesh,&pfec,
1);
// Define the arrays for the nonlinear form.
mfem::Array<mfem::ParFiniteElementSpace*> asfes;
mfem::Array<mfem::ParFiniteElementSpace*> apfes;
asfes.Append(sfes);
apfes.Append(pfes);
// Define parametric block nonlinear form using single scalar H1 field
// and L2 scalar density field.
mfem::ParParametricBNLForm* nf=new mfem::ParParametricBNLForm(asfes,apfes);
// Add a parametric integrator.
nf->AddDomainIntegrator(new mfem::ParametricLinearDiffusion(*qfun));
// Define true block vectors for state, adjoint, resudual.
mfem::BlockVector solbv; solbv.Update(nf->GetBlockTrueOffsets()); solbv=0.0;
mfem::BlockVector adjbv; adjbv.Update(nf->GetBlockTrueOffsets()); adjbv=0.0;
mfem::BlockVector resbv; resbv.Update(nf->GetBlockTrueOffsets()); resbv=0.0;
// Define true block vectors for parametric field and gradients.
mfem::BlockVector prmbv; prmbv.Update(nf->ParamGetBlockTrueOffsets());
prmbv=0.0;
mfem::BlockVector grdbv; grdbv.Update(nf->ParamGetBlockTrueOffsets());
grdbv=0.0;
// Set the BCs for the physics.
mfem::Array<mfem::Array<int> *> ess_bdr;
mfem::Array<mfem::Vector*> ess_rhs;
ess_bdr.Append(new mfem::Array<int>(pmesh.bdr_attributes.Max()));
ess_rhs.Append(nullptr);
(*ess_bdr[0]) = 1;
nf->SetEssentialBC(ess_bdr,ess_rhs);
delete ess_bdr[0];
// Set the density field to 0.5.
prmbv=0.5;
// Set the density as parametric field in the parametric BNLForm.
nf->SetParamFields(prmbv); //set the density
// Compute the stiffness/tangent matrix for density prmbv=0.5.
mfem::BlockOperator *A = &nf->GetGradient(solbv);
mfem::HypreBoomerAMG* prec=new mfem::HypreBoomerAMG();
prec->SetPrintLevel(print_level);
// Use only block (0,0) as in this case we have a single field.
prec->SetOperator(A->GetBlock(0,0));
// Construct block preconditioner for the BNLForm.
mfem::BlockDiagonalPreconditioner *blpr = new mfem::BlockDiagonalPreconditioner(
nf->GetBlockTrueOffsets());
blpr->SetDiagonalBlock(0,prec);
// Define the solvers.
mfem::GMRESSolver *gmres;
gmres = new mfem::GMRESSolver(MPI_COMM_WORLD);
gmres->SetAbsTol(newton_abs_tol/10);
gmres->SetRelTol(newton_rel_tol/10);
gmres->SetMaxIter(100);
gmres->SetPrintLevel(print_level);
gmres->SetPreconditioner(*blpr);
gmres->SetOperator(*A);
// Solve the problem.
solbv=0.0;
nf->Mult(solbv,resbv); resbv.Neg(); //compute RHS
gmres->Mult(resbv, solbv);
// Compute the energy of the state system.
real_t energy = nf->GetEnergy(solbv);
if (myrank==0)
{
std::cout << "energy =" << energy << std::endl;
}
// Define the block nonlinear form utilized for representing the objective -
// use the state array from the BNLForm.
mfem::ParBlockNonlinearForm* ob=new mfem::ParBlockNonlinearForm(asfes);
// Add the integrator for the objective.
ob->AddDomainIntegrator(new mfem::DiffusionObjIntegrator());
// Compute the objective.
real_t obj=ob->GetEnergy(solbv);
if (myrank==0)
{
std::cout << "Objective =" << obj << std::endl;
}
// Solve the adjoint.
{
mfem::BlockVector adjrhs; adjrhs.Update(nf->GetBlockTrueOffsets()); adjrhs=0.0;
// Compute the RHS for the adjoint, i.e., the gradients with respect to
// the parametric fields.
ob->Mult(solbv, adjrhs);
// Get the tangent matrix from the state problem. We do not need to
// transpose the operator for diffusion. Compute the adjoint solution.
gmres->Mult(adjrhs, adjbv);
}
// Compute gradients.
// First set the adjoint field.
nf->SetAdjointFields(adjbv);
// Set the state field.
nf->SetStateFields(solbv);
// Call the parametric Mult.
nf->ParamMult(prmbv, grdbv);
// Dump out the data.
if (visualization)
{
mfem::ParaViewDataCollection *dacol=new mfem::ParaViewDataCollection("ParHeat",
&pmesh);
mfem::ParGridFunction gfgrd(pfes); gfgrd.SetFromTrueDofs(grdbv.GetBlock(0));
mfem::ParGridFunction gfdns(pfes); gfdns.SetFromTrueDofs(prmbv.GetBlock(0));
// Define state grid function.
mfem::ParGridFunction gfsol(sfes); gfsol.SetFromTrueDofs(solbv.GetBlock(0));
mfem::ParGridFunction gfadj(sfes); gfadj.SetFromTrueDofs(adjbv.GetBlock(0));
dacol->SetLevelsOfDetail(order);
dacol->RegisterField("sol", &gfsol);
dacol->RegisterField("adj", &gfadj);
dacol->RegisterField("dns", &gfdns);
dacol->RegisterField("grd", &gfgrd);
dacol->SetTime(1.0);
dacol->SetCycle(1);
dacol->Save();
delete dacol;
}
// FD check
{
mfem::BlockVector prtbv;
mfem::BlockVector tmpbv;
prtbv.Update(nf->ParamGetBlockTrueOffsets());
tmpbv.Update(nf->ParamGetBlockTrueOffsets());
prtbv.GetBlock(0).Randomize();
prtbv*=1.0;
real_t lsc=1.0;
real_t gQoI=ob->GetEnergy(solbv);
real_t lQoI;
real_t nd=mfem::InnerProduct(MPI_COMM_WORLD,prtbv,prtbv);
real_t td=mfem::InnerProduct(MPI_COMM_WORLD,prtbv,grdbv);
td=td/nd;
for (int l = 0; l < 10; l++)
{
lsc/=10.0;
prtbv/=10.0;
add(prmbv,prtbv,tmpbv);
nf->SetParamFields(tmpbv);
// Solve the physics.
solbv=0.0;
nf->Mult(solbv,resbv); resbv.Neg(); //compute RHS
A = &nf->GetGradient(solbv);
prec->SetPrintLevel(0);
prec->SetOperator(A->GetBlock(0,0));
gmres->SetOperator(*A);
gmres->SetPrintLevel(0);
gmres->Mult(resbv,solbv);
// Compute the objective.
lQoI=ob->GetEnergy(solbv);
real_t ld=(lQoI-gQoI)/lsc;
if (myrank==0)
{
std::cout << "dx=" << lsc <<" FD approximation=" << ld/nd
<< " adjoint gradient=" << td
<< " err=" << std::fabs(ld/nd-td) << std::endl;
}
}
}
delete ob;
delete gmres;
delete blpr;
delete prec;
delete nf;
delete pfes;
delete sfes;
delete qfun;
delete loadco;
delete diffco;
return 0;
}
-362
View File
@@ -1,362 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "mfem.hpp"
#include "pparamnonlinearform.hpp"
#ifdef MFEM_USE_MPI
namespace mfem
{
ParParametricBNLForm::ParParametricBNLForm(Array<ParFiniteElementSpace *>
&statef,
Array<ParFiniteElementSpace *> &paramf)
:ParametricBNLForm()
{
pBlockGrad = nullptr;
SetParSpaces(statef,paramf);
}
void ParParametricBNLForm::SetParSpaces(Array<ParFiniteElementSpace *> &statef,
Array<ParFiniteElementSpace *> &paramf)
{
delete pBlockGrad;
pBlockGrad = nullptr;
for (int s1=0; s1<fes.Size(); ++s1)
{
for (int s2=0; s2<fes.Size(); ++s2)
{
delete phBlockGrad(s1,s2);
}
}
Array<FiniteElementSpace *> serialSpaces(statef.Size());
Array<FiniteElementSpace *> prmserialSpaces(paramf.Size());
for (int s=0; s<statef.Size(); s++)
{
serialSpaces[s] = (FiniteElementSpace *) statef[s];
}
for (int s=0; s<paramf.Size(); s++)
{
prmserialSpaces[s] = (FiniteElementSpace *) paramf[s];
}
SetSpaces(serialSpaces,prmserialSpaces);
phBlockGrad.SetSize(fes.Size(), fes.Size());
for (int s1=0; s1<fes.Size(); ++s1)
{
for (int s2=0; s2<fes.Size(); ++s2)
{
phBlockGrad(s1,s2) = new OperatorHandle(Operator::Hypre_ParCSR);
}
}
}
ParFiniteElementSpace * ParParametricBNLForm::ParFESpace(int k)
{
return (ParFiniteElementSpace *)fes[k];
}
const ParFiniteElementSpace *ParParametricBNLForm::ParFESpace(int k) const
{
return (const ParFiniteElementSpace *)fes[k];
}
ParFiniteElementSpace * ParParametricBNLForm::ParParamFESpace(int k)
{
return (ParFiniteElementSpace *)paramfes[k];
}
const ParFiniteElementSpace *ParParametricBNLForm::ParParamFESpace(int k) const
{
return (const ParFiniteElementSpace *)paramfes[k];
}
// Here, rhs is a true dof vector
void ParParametricBNLForm::SetEssentialBC(const
Array<Array<int> *>&bdr_attr_is_ess,
Array<Vector *> &rhs)
{
Array<Vector *> nullarray(fes.Size());
nullarray = NULL;
ParametricBNLForm::SetEssentialBC(bdr_attr_is_ess, nullarray);
for (int s = 0; s < fes.Size(); ++s)
{
if (rhs[s])
{
rhs[s]->SetSubVector(*ess_tdofs[s], 0.0);
}
}
}
void ParParametricBNLForm::SetParamEssentialBC(const
Array<Array<int> *>&bdr_attr_is_ess,
Array<Vector *> &rhs)
{
Array<Vector *> nullarray(fes.Size());
nullarray = NULL;
ParametricBNLForm::SetParamEssentialBC(bdr_attr_is_ess, nullarray);
for (int s = 0; s < paramfes.Size(); ++s)
{
if (rhs[s])
{
rhs[s]->SetSubVector(*paramess_tdofs[s], 0.0);
}
}
}
real_t ParParametricBNLForm::GetEnergy(const Vector &x) const
{
xs_true.Update(const_cast<Vector&>(x), block_trueOffsets);
xs.Update(block_offsets);
for (int s = 0; s < fes.Size(); ++s)
{
fes[s]->GetProlongationMatrix()->Mult(xs_true.GetBlock(s), xs.GetBlock(s));
}
real_t enloc = ParametricBNLForm::GetEnergyBlocked(xs,xdv);
real_t englo = 0.0;
MPI_Allreduce(&enloc, &englo, 1, MPITypeMap<real_t>::mpi_type, MPI_SUM,
ParFESpace(0)->GetComm());
return englo;
}
void ParParametricBNLForm::Mult(const Vector &x, Vector &y) const
{
xs_true.Update(const_cast<Vector&>(x), block_trueOffsets);
ys_true.Update(y, block_trueOffsets);
xs.Update(block_offsets);
ys.Update(block_offsets);
for (int s=0; s<fes.Size(); ++s)
{
fes[s]->GetProlongationMatrix()->Mult(
xs_true.GetBlock(s), xs.GetBlock(s));
}
ParametricBNLForm::MultBlocked(xs, xdv, ys);
if (fnfi.Size() > 0)
{
MFEM_ABORT("TODO: assemble contributions from shared face terms");
}
for (int s=0; s<fes.Size(); ++s)
{
fes[s]->GetProlongationMatrix()->MultTranspose(
ys.GetBlock(s), ys_true.GetBlock(s));
ys_true.GetBlock(s).SetSubVector(*ess_tdofs[s], 0.0);
}
}
/// Block T-Vector to Block T-Vector
void ParParametricBNLForm::ParamMult(const Vector &x, Vector &y) const
{
xs_true.Update(const_cast<Vector&>(x), paramblock_trueOffsets);
ys_true.Update(y, paramblock_trueOffsets);
prmxs.Update(paramblock_offsets);
prmys.Update(paramblock_offsets);
for (int s=0; s<paramfes.Size(); ++s)
{
paramfes[s]->GetProlongationMatrix()->Mult(
xs_true.GetBlock(s), prmxs.GetBlock(s));
}
ParametricBNLForm::MultParamBlocked(xsv,adv,xdv,prmys);
if (fnfi.Size() > 0)
{
MFEM_ABORT("TODO: assemble contributions from shared face terms");
}
for (int s=0; s<paramfes.Size(); ++s)
{
paramfes[s]->GetProlongationMatrix()->MultTranspose(
prmys.GetBlock(s), ys_true.GetBlock(s));
ys_true.GetBlock(s).SetSubVector(*paramess_tdofs[s], 0.0);
}
}
/// Return the local gradient matrix for the given true-dof vector x
const BlockOperator & ParParametricBNLForm::GetLocalGradient(
const Vector &x) const
{
xs_true.Update(const_cast<Vector&>(x), block_trueOffsets);
xs.Update(block_offsets);
for (int s=0; s<fes.Size(); ++s)
{
fes[s]->GetProlongationMatrix()->Mult(
xs_true.GetBlock(s), xs.GetBlock(s));
}
ParametricBNLForm::ComputeGradientBlocked(xs,
xdv); // (re)assemble Grad with b.c.
delete BlockGrad;
BlockGrad = new BlockOperator(block_offsets);
for (int i = 0; i < fes.Size(); ++i)
{
for (int j = 0; j < fes.Size(); ++j)
{
BlockGrad->SetBlock(i, j, Grads(i, j));
}
}
return *BlockGrad;
}
// Set the operator type id for the parallel gradient matrix/operator.
void ParParametricBNLForm::SetGradientType(Operator::Type tid)
{
for (int s1=0; s1<fes.Size(); ++s1)
{
for (int s2=0; s2<fes.Size(); ++s2)
{
phBlockGrad(s1,s2)->SetType(tid);
}
}
}
BlockOperator & ParParametricBNLForm::GetGradient(const Vector &x) const
{
if (pBlockGrad == NULL)
{
pBlockGrad = new BlockOperator(block_trueOffsets);
}
Array<const ParFiniteElementSpace *> pfes(fes.Size());
for (int s1=0; s1<fes.Size(); ++s1)
{
pfes[s1] = ParFESpace(s1);
for (int s2=0; s2<fes.Size(); ++s2)
{
phBlockGrad(s1,s2)->Clear();
}
}
GetLocalGradient(x); // gradients are stored in 'Grads'
if (fnfi.Size() > 0)
{
MFEM_ABORT("TODO: assemble contributions from shared face terms");
}
for (int s1=0; s1<fes.Size(); ++s1)
{
for (int s2=0; s2<fes.Size(); ++s2)
{
OperatorHandle dA(phBlockGrad(s1,s2)->Type()),
Ph(phBlockGrad(s1,s2)->Type()),
Rh(phBlockGrad(s1,s2)->Type());
if (s1 == s2)
{
dA.MakeSquareBlockDiag(pfes[s1]->GetComm(), pfes[s1]->GlobalVSize(),
pfes[s1]->GetDofOffsets(), Grads(s1,s1));
Ph.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
phBlockGrad(s1,s1)->MakePtAP(dA, Ph);
OperatorHandle Ae;
Ae.EliminateRowsCols(*phBlockGrad(s1,s1), *ess_tdofs[s1]);
}
else
{
dA.MakeRectangularBlockDiag(pfes[s1]->GetComm(),
pfes[s1]->GlobalVSize(),
pfes[s2]->GlobalVSize(),
pfes[s1]->GetDofOffsets(),
pfes[s2]->GetDofOffsets(),
Grads(s1,s2));
Rh.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
Ph.ConvertFrom(pfes[s2]->Dof_TrueDof_Matrix());
phBlockGrad(s1,s2)->MakeRAP(Rh, dA, Ph);
phBlockGrad(s1,s2)->EliminateRows(*ess_tdofs[s1]);
phBlockGrad(s1,s2)->EliminateCols(*ess_tdofs[s2]);
}
pBlockGrad->SetBlock(s1, s2, phBlockGrad(s1,s2)->Ptr());
}
}
return *pBlockGrad;
}
ParParametricBNLForm::~ParParametricBNLForm()
{
delete pBlockGrad;
for (int s1=0; s1<fes.Size(); ++s1)
{
for (int s2=0; s2<fes.Size(); ++s2)
{
delete phBlockGrad(s1,s2);
}
}
}
void ParParametricBNLForm::SetStateFields(const Vector &xv) const
{
xs_true.Update(const_cast<Vector&>(xv), block_trueOffsets);
xsv.Update(block_offsets);
for (int s=0; s<fes.Size(); ++s)
{
fes[s]->GetProlongationMatrix()->Mult(
xs_true.GetBlock(s), xsv.GetBlock(s));
}
}
void ParParametricBNLForm::SetAdjointFields(const Vector &av) const
{
xs_true.Update(const_cast<Vector&>(av), block_trueOffsets);
adv.Update(block_offsets);
for (int s=0; s<fes.Size(); ++s)
{
fes[s]->GetProlongationMatrix()->Mult(
xs_true.GetBlock(s), adv.GetBlock(s));
}
}
void ParParametricBNLForm::SetParamFields(const Vector &dv) const
{
xs_true.Update(const_cast<Vector&>(dv),paramblock_trueOffsets);
xdv.Update(paramblock_offsets);
for (int s=0; s<paramfes.Size(); ++s)
{
paramfes[s]->GetProlongationMatrix()->Mult(
xs_true.GetBlock(s), xdv.GetBlock(s));
}
}
}
#endif
-114
View File
@@ -1,114 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_PPRMNONLINEARFORM
#define MFEM_PPRMNONLINEARFORM
#ifdef MFEM_USE_MPI
#include "mfem.hpp"
#include "paramnonlinearform.hpp"
namespace mfem
{
/** @brief A class representing a general parametric parallel block nonlinear
operator defined on the Cartesian product of multiple
ParFiniteElementSpace%s. */
/** The ParParametricBNLForm takes as input, and returns as output, vectors on
the true dofs. */
class ParParametricBNLForm : public ParametricBNLForm
{
protected:
mutable BlockVector xs_true, ys_true;
mutable Array2D<OperatorHandle *> phBlockGrad;
mutable BlockOperator *pBlockGrad;
public:
/// Computes the energy of the system
real_t GetEnergy(const Vector &x) const override;
/// Construct an empty ParParametricBNLForm. Initialize with SetParSpaces().
ParParametricBNLForm() : pBlockGrad(nullptr) { }
/** @brief Construct a ParParametricBNLForm on the given set of
parametric and state ParFiniteElementSpace%s. */
ParParametricBNLForm(Array<ParFiniteElementSpace *> &statef,
Array<ParFiniteElementSpace *> &paramf);
/// Return the @a k-th parallel FE state space of the ParParametricBNLForm.
ParFiniteElementSpace *ParFESpace(int k);
/** @brief Return the @a k-th parallel FE state space of the
ParParametricBNLForm (const version). */
const ParFiniteElementSpace *ParFESpace(int k) const;
/// Return the @a k-th parallel FE parameters space of the
/// ParParametricBNLForm.
ParFiniteElementSpace *ParParamFESpace(int k);
/** @brief Return the @a k-th parallel FE parameters space of the
ParParametricBNLForm (const version). */
const ParFiniteElementSpace *ParParamFESpace(int k) const;
/** @brief Set the parallel FE spaces for the state and the parametric
* fields. After a call to SetParSpaces(), the essential b.c. and the
* gradient-type (if different from the default) must be set again. */
void SetParSpaces(Array<ParFiniteElementSpace *> &statef,
Array<ParFiniteElementSpace *> &paramf);
/// Set the state essential BCs. Here, rhs is a true dof vector!
void SetEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
Array<Vector *> &rhs) override;
// Set the essential BCs for the parametric fields. Here, rhs is a true dof
// vector!
void SetParamEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
Array<Vector *> &rhs) override;
/** @brief Calculates the residual for a state input given by block T-Vector.
* The result is Block T-Vector! The parametric fields should be set in
* advance by calling SetParamFields(). */
void Mult(const Vector &x, Vector &y) const override;
/** @brief Calculates the product of the adjoint field and the derivative of
* the state residual with respect to the parametric fields. The adjoint and
* the state fields should be set in advance by calling SetAdjointFields()
* and SetStateFields(). The input and the result are block T-Vectors!*/
void ParamMult(const Vector &x, Vector &y) const override;
/// Return the local block gradient matrix for the given true-dof vector x
const BlockOperator &GetLocalGradient(const Vector &x) const;
/// Return the block gradient matrix for the given true-dof vector x
BlockOperator &GetGradient(const Vector &x) const override;
/** @brief Set the operator type id for the blocks of the parallel gradient
matrix/operator. The default type is Operator::Hypre_ParCSR. */
void SetGradientType(Operator::Type tid);
/// Destructor.
virtual ~ParParametricBNLForm();
/// Set the state fields
void SetStateFields(const Vector &xv) const override;
/// Set the adjoint fields
void SetAdjointFields(const Vector &av) const override;
/// Set the parameters/design fields
void SetParamFields(const Vector &dv) const override;
};
}
#endif
#endif
-308
View File
@@ -1,308 +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.
//
// ----------------------------------------------------------------
// SeqHeat Miniapp: Gradients of PDE constrained objective function
// ----------------------------------------------------------------
// (Sequential Version)
//
// The following example computes the gradients of a specified objective
// function with respect to parametric fields. The objective function is having
// the following form f(u(\rho)) where u(\rho) is a solution of a specific state
// problem (in the example that is the diffusion equation), and \rho is a
// parametric field discretized by finite elements. The parametric field (also
// called density in topology optimization) controls the coefficients of the
// state equation. For the considered case, the density controls the diffusion
// coefficient within the computational domain.
//
// For more information, the users are referred to:
//
// Hinze, M.; Pinnau, R.; Ulbrich, M. & Ulbrich, S.
// Optimization with PDE Constraints
// Springer Netherlands, 2009
//
// Bendsøe, M. P. & Sigmund, O.
// Topology Optimization - Theory, Methods and Applications
// Springer Verlag, Berlin Heidelberg, 2003
//
// Compile with: make seqheat
//
// Sample runs:
//
// seqheat -m ../../data/star-mixed.mesh
// seqheat --visualization
#include "mfem.hpp"
#include <fstream>
#include <iostream>
#include "mtop_integrators.hpp"
using namespace mfem;
int main(int argc, char *argv[])
{
const char *mesh_file = "../../data/star.vtk";
int ser_ref_levels = 1;
int order = 2;
bool visualization = false;
real_t newton_rel_tol = 1e-4;
real_t newton_abs_tol = 1e-6;
int newton_iter = 10;
int print_level = 0;
mfem::OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
args.AddOption(&ser_ref_levels,
"-rs",
"--refine-serial",
"Number of times to refine the mesh uniformly in serial.");
args.AddOption(&order,
"-o",
"--order",
"Order (degree) of the finite elements.");
args.AddOption(&visualization,
"-vis",
"--visualization",
"-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&newton_rel_tol,
"-rel",
"--relative-tolerance",
"Relative tolerance for the Newton solve.");
args.AddOption(&newton_abs_tol,
"-abs",
"--absolute-tolerance",
"Absolute tolerance for the Newton solve.");
args.AddOption(&newton_iter,
"-it",
"--newton-iterations",
"Maximum iterations for the Newton solve.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(std::cout);
return 1;
}
args.PrintOptions(std::cout);
// Read the (serial) mesh from the given mesh file on all processors. We
// can handle triangular, quadrilateral, tetrahedral and hexahedral meshes
// with the same code.
mfem::Mesh *mesh = new mfem::Mesh(mesh_file, 1, 1);
int dim = mesh->Dimension();
// Refine the mesh in serial to increase the resolution. In this example
// we do 'ser_ref_levels' of uniform refinement, where 'ser_ref_levels' is
// a command-line parameter.
for (int lev = 0; lev < ser_ref_levels; lev++)
{
mesh->UniformRefinement();
}
// Diffusion coefficient
mfem::ConstantCoefficient* diffco=new mfem::ConstantCoefficient(1.0);
// Heat source
mfem::ConstantCoefficient* loadco=new mfem::ConstantCoefficient(1.0);
// Define the q-function
mfem::QLinearDiffusion* qfun=new mfem::QLinearDiffusion(*diffco,*loadco,1.0,
1e-7,4.0,0.5);
// Define FE collection and space for the state solution
mfem::H1_FECollection sfec(order, dim);
mfem::FiniteElementSpace* sfes=new mfem::FiniteElementSpace(mesh,&sfec,1);
// Define FE collection and space for the density field
mfem::L2_FECollection pfec(order, dim);
mfem::FiniteElementSpace* pfes=new mfem::FiniteElementSpace(mesh,&pfec,1);
// Define the arrays for the nonlinear form
mfem::Array<mfem::FiniteElementSpace*> asfes;
mfem::Array<mfem::FiniteElementSpace*> apfes;
asfes.Append(sfes);
apfes.Append(pfes);
// Define parametric block nonlinear form using single scalar H1 field
// and L2 scalar density field
mfem::ParametricBNLForm* nf=new mfem::ParametricBNLForm(asfes,apfes);
// Add the parametric integrator
nf->AddDomainIntegrator(new mfem::ParametricLinearDiffusion(*qfun));
// Define true block vectors for state, adjoint, residual
mfem::BlockVector solbv; solbv.Update(nf->GetBlockTrueOffsets()); solbv=0.0;
mfem::BlockVector adjbv; adjbv.Update(nf->GetBlockTrueOffsets()); adjbv=0.0;
mfem::BlockVector resbv; resbv.Update(nf->GetBlockTrueOffsets()); resbv=0.0;
// Define true block vectors for parametric field and gradients
mfem::BlockVector prmbv; prmbv.Update(nf->ParamGetBlockTrueOffsets());
prmbv=0.0;
mfem::BlockVector grdbv; grdbv.Update(nf->ParamGetBlockTrueOffsets());
grdbv=0.0;
// Set the BC for the physics
mfem::Array<mfem::Array<int> *> ess_bdr;
mfem::Array<mfem::Vector*> ess_rhs;
ess_bdr.Append(new mfem::Array<int>(mesh->bdr_attributes.Max()));
ess_rhs.Append(nullptr);
(*ess_bdr[0]) = 1;
nf->SetEssentialBC(ess_bdr,ess_rhs);
delete ess_bdr[0];
// Define the linear solvers
mfem::GMRESSolver *gmres;
gmres = new mfem::GMRESSolver();
gmres->SetAbsTol(newton_abs_tol/10);
gmres->SetRelTol(newton_rel_tol/10);
gmres->SetMaxIter(300);
gmres->SetPrintLevel(print_level);
// Define the Newton solver
mfem::NewtonSolver *ns;
ns = new mfem::NewtonSolver();
ns->iterative_mode = true;
ns->SetSolver(*gmres);
ns->SetOperator(*nf);
ns->SetPrintLevel(print_level);
ns->SetRelTol(newton_rel_tol);
ns->SetAbsTol(newton_abs_tol);
ns->SetMaxIter(newton_iter);
// Solve the problem
// Set the density to 0.5
prmbv=0.5;
nf->SetParamFields(prmbv); // Set the density
// Define the RHS
mfem::Vector b;
solbv=0.0;
// Newton solve
ns->Mult(b, solbv);
// Compute the residual
nf->Mult(solbv,resbv);
std::cout<<"Norm residual="<<resbv.Norml2()<<std::endl;
// Compute the energy of the state system
real_t energy = nf->GetEnergy(solbv);
std::cout<<"energy ="<< energy<<std::endl;
// Define the block nonlinear form utilized for representing the
// objective. The input is the state array asfes defined earlier.
mfem::BlockNonlinearForm* ob=new mfem::BlockNonlinearForm(asfes);
// Add the integrator for the objective
ob->AddDomainIntegrator(new mfem::DiffusionObjIntegrator());
// Compute the objective
real_t obj=ob->GetEnergy(solbv);
std::cout<<"Objective ="<<obj<<std::endl;
// Solve the adjoint
{
mfem::BlockVector adjrhs; adjrhs.Update(nf->GetBlockTrueOffsets()); adjrhs=0.0;
// Compute the RHS for the adjoint
ob->Mult(solbv, adjrhs);
// Get the tangent matrix from the state problem
mfem::BlockOperator& A=nf->GetGradient(solbv);
// We do not need to transpose the operator for diffusion
gmres->SetOperator(A.GetBlock(0,0));
// Compute the adjoint solution
gmres->Mult(adjrhs.GetBlock(0), adjbv.GetBlock(0));
}
// Compute gradients
nf->SetAdjointFields(adjbv);
nf->SetStateFields(solbv);
nf->ParamMult(prmbv, grdbv);
// Dump out the data
if (visualization)
{
mfem::ParaViewDataCollection *dacol=new mfem::ParaViewDataCollection("SeqHeat",
mesh);
mfem::GridFunction gfgrd(pfes); gfgrd.SetFromTrueDofs(grdbv.GetBlock(0));
mfem::GridFunction gfdns(pfes); gfdns.SetFromTrueDofs(prmbv.GetBlock(0));
// Define state grid function
mfem::GridFunction gfsol(sfes); gfsol.SetFromTrueDofs(solbv.GetBlock(0));
mfem::GridFunction gfadj(sfes); gfadj.SetFromTrueDofs(adjbv.GetBlock(0));
dacol->SetLevelsOfDetail(order);
dacol->RegisterField("sol", &gfsol);
dacol->RegisterField("adj", &gfadj);
dacol->RegisterField("dns", &gfdns);
dacol->RegisterField("grd", &gfgrd);
dacol->SetTime(1.0);
dacol->SetCycle(1);
dacol->Save();
delete dacol;
}
// FD check
{
// Perturbation vector
mfem::BlockVector prtbv;
mfem::BlockVector tmpbv;
prtbv.Update(nf->ParamGetBlockTrueOffsets());
tmpbv.Update(nf->ParamGetBlockTrueOffsets());
// Generate the perturbation
prtbv.GetBlock(0).Randomize();
prtbv*=1.0;
// Scaling parameter
real_t lsc=1.0;
// Compute initial objective
real_t gQoI=ob->GetEnergy(solbv);
real_t lQoI;
// Norm of the perturbation
real_t nd=mfem::InnerProduct(prtbv,prtbv);
// Projection of the adjoint gradient on the perturbation
real_t td=mfem::InnerProduct(prtbv,grdbv);
// Normalize the directional derivative
td=td/nd;
for (int l = 0; l < 10; l++)
{
lsc/=10.0;
// Scale the perturbation
prtbv/=10.0;
// Add the perturbation to the original density
add(prmbv,prtbv,tmpbv);
nf->SetParamFields(tmpbv);
// Solve the physics
ns->Mult(b,solbv);
// Compute the objective
lQoI=ob->GetEnergy(solbv);
// FD approximation
real_t ld=(lQoI-gQoI)/lsc;
std::cout << "dx=" << lsc << " FD gradient=" << ld/nd
<< " adjoint gradient=" << td
<< " err=" << std::fabs(ld/nd-td) << std::endl;
}
}
delete ob;
delete ns;
delete gmres;
delete nf;
delete pfes;
delete sfes;
delete qfun;
delete loadco;
delete diffco;
delete mesh;
return 0;
}
+35
View File
@@ -0,0 +1,35 @@
MFEM mesh v1.0
#
# MFEM Geometry Types (see fem/geom.hpp):
#
# POINT = 0
# SEGMENT = 1
# TRIANGLE = 2
# SQUARE = 3
# TETRAHEDRON = 4
# CUBE = 5
# PRISM = 6
#
dimension
2
elements
1
1 3 0 1 2 3
boundary
4
1 1 0 1
2 1 1 2
3 1 2 3
4 1 3 0
vertices
4
2
-1 -1
1 -1
1 1
-1 1
+5 -2
View File
@@ -105,8 +105,11 @@ MFEM_PERF_CXXFLAGS_xlc = -mcpu=native
# - Clang extra options:
ifeq ($(MFEM_MACHINE),riscv64)
MFEM_PERF_CXXFLAGS_clang += -march=rv64gc
else ifneq ($(MFEM_MACHINE),arm64)
# -march=native is unavailable on clang/ARM64 as of 05/2021: support could be added later.
else ifneq (,$(findstring ppc,$(MFEM_MACHINE)))
MFEM_PERF_CXXFLAGS_clang += -mcpu=native -mtune=native
else ifeq ($(MFEM_MACHINE),arm64)
MFEM_PERF_CXXFLAGS_clang += -mcpu=native -mtune=native
else
MFEM_PERF_CXXFLAGS_clang += -march=native
endif
MFEM_PERF_CXXFLAGS_clang += $(PEDANTIC_FLAG) -Wall
+3 -2
View File
@@ -109,7 +109,7 @@ int main(int argc, char *argv[])
else if (string(fe) == "l") { L2 = true; }
else { MFEM_ABORT("Bad FE type. Must be 'h', 'n', 'r', or 'l'."); }
real_t kappa = (order+1)*(order+1); // Penalty used for DG discretizations
real_t kappa = 10*(order+1)*(order+1); // Penalty used for DG discretizations
Mesh mesh(mesh_file, 1, 1);
const int dim = mesh.Dimension();
@@ -156,7 +156,7 @@ int main(int argc, char *argv[])
}
// Partial assembly not currently supported for DG or for surface meshes with
// vector finite elements (ND or RT).
if (!L2 && (H1 || sdim == dim)) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
if (H1 || sdim == dim) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
a.Assemble();
LinearForm b(&fes);
@@ -167,6 +167,7 @@ int main(int argc, char *argv[])
// DG boundary conditions are enforced weakly with this integrator.
b.AddBdrFaceIntegrator(new DGDirichletLFIntegrator(u_coeff, -1.0, kappa));
}
if (H1) { b.UseFastAssembly(true); }
b.Assemble();
GridFunction x(&fes);
+3 -2
View File
@@ -112,7 +112,7 @@ int main(int argc, char *argv[])
else if (string(fe) == "l") { L2 = true; }
else { MFEM_ABORT("Bad FE type. Must be 'h', 'n', 'r', or 'l'."); }
real_t kappa = (order+1)*(order+1); // Penalty used for DG discretizations
real_t kappa = 10*(order+1)*(order+1); // Penalty used for DG discretizations
Mesh serial_mesh(mesh_file, 1, 1);
const int dim = serial_mesh.Dimension();
@@ -166,7 +166,7 @@ int main(int argc, char *argv[])
}
// Partial assembly not currently supported for DG or for surface meshes with
// vector finite elements (ND or RT).
if (!L2 && (H1 || sdim == dim)) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
if (H1 || sdim == dim) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
a.Assemble();
ParLinearForm b(&fes);
@@ -177,6 +177,7 @@ int main(int argc, char *argv[])
// DG boundary conditions are enforced weakly with this integrator.
b.AddBdrFaceIntegrator(new DGDirichletLFIntegrator(u_coeff, -1.0, kappa));
}
if (H1) { b.UseFastAssembly(true); }
b.Assemble();
ParGridFunction x(&fes);
+2
View File
@@ -63,6 +63,7 @@ set(UNIT_TESTS_SRCS
mesh/test_vtu.cpp
mesh/test_nurbs.cpp
mesh/test_exodus_writer.cpp
fem/make_permuted_mesh.cpp
fem/test_1d_bilininteg.cpp
fem/test_2d_bilininteg.cpp
fem/test_3d_bilininteg.cpp
@@ -107,6 +108,7 @@ set(UNIT_TESTS_SRCS
fem/test_linearform_ext.cpp
fem/test_lor.cpp
fem/test_lor_batched.cpp
fem/test_lor_dg.cpp
fem/test_nonlinearform.cpp
fem/test_operatorjacobismoother.cpp
fem/test_oscillation.cpp
+162
View File
@@ -0,0 +1,162 @@
// 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 "make_permuted_mesh.hpp"
namespace mfem
{
Mesh Mesh2D_Orientation(int face_perm_1, int face_perm_2)
{
static const int dim = 2;
static const int nv = 6;
static const int nel = 2;
Mesh mesh(dim, nv, nel);
real_t x[dim];
x[0] = 0.0; x[1] = 0.0;
mesh.AddVertex(x);
x[0] = 1.0; x[1] = 0.0;
mesh.AddVertex(x);
x[0] = 2.0; x[1] = 0.0;
mesh.AddVertex(x);
x[0] = 0.0; x[1] = 1.0;
mesh.AddVertex(x);
x[0] = 1.0; x[1] = 1.0;
mesh.AddVertex(x);
x[0] = 2.0; x[1] = 1.0;
mesh.AddVertex(x);
int el[4];
el[0] = 0;
el[1] = 1;
el[2] = 4;
el[3] = 3;
std::rotate(&el[0], &el[face_perm_1], &el[3] + 1);
mesh.AddQuad(el);
el[0] = 1;
el[1] = 2;
el[2] = 5;
el[3] = 4;
std::rotate(&el[0], &el[face_perm_2], &el[3] + 1);
mesh.AddQuad(el);
mesh.FinalizeQuadMesh(true);
mesh.GenerateBoundaryElements();
mesh.Finalize();
return mesh;
}
void Rotation3DVertices(int *v, int ref_face, int rot)
{
std::vector<int> face_1, face_2;
switch (ref_face/2)
{
case 0:
face_1 = {v[0], v[1], v[2], v[3]};
face_2 = {v[4], v[5], v[6], v[7]};
break;
case 1:
face_1 = {v[1], v[5], v[6], v[2]};
face_2 = {v[0], v[4], v[7], v[3]};
break;
case 2:
face_1 = {v[4], v[5], v[1], v[0]};
face_2 = {v[7], v[6], v[2], v[3]};
break;
}
if (ref_face % 2 == 0)
{
std::reverse(face_1.begin(), face_1.end());
std::reverse(face_2.begin(), face_2.end());
std::swap(face_1, face_2);
}
std::rotate(face_1.begin(), face_1.begin() + rot, face_1.end());
std::rotate(face_2.begin(), face_2.begin() + rot, face_2.end());
for (int i=0; i<4; ++i)
{
v[i] = face_1[i];
v[i+4] = face_2[i];
}
}
Mesh Mesh3D_Orientation(int face_perm_1, int face_perm_2)
{
static const int dim = 3;
static const int nv = 12;
static const int nel = 2;
Mesh mesh(dim, nv, nel);
real_t x[dim];
x[0] = 0.0; x[1] = 0.0; x[2] = 0.0;
mesh.AddVertex(x);
x[0] = 1.0; x[1] = 0.0; x[2] = 0.0;
mesh.AddVertex(x);
x[0] = 2.0; x[1] = 0.0; x[2] = 0.0;
mesh.AddVertex(x);
x[0] = 0.0; x[1] = 1.0; x[2] = 0.0;
mesh.AddVertex(x);
x[0] = 1.0; x[1] = 1.0; x[2] = 0.0;
mesh.AddVertex(x);
x[0] = 2.0; x[1] = 1.0; x[2] = 0.0;
mesh.AddVertex(x);
x[0] = 0.0; x[1] = 0.0; x[2] = 1.0;
mesh.AddVertex(x);
x[0] = 1.0; x[1] = 0.0; x[2] = 1.0;
mesh.AddVertex(x);
x[0] = 2.0; x[1] = 0.0; x[2] = 1.0;
mesh.AddVertex(x);
x[0] = 0.0; x[1] = 1.0; x[2] = 1.0;
mesh.AddVertex(x);
x[0] = 1.0; x[1] = 1.0; x[2] = 1.0;
mesh.AddVertex(x);
x[0] = 2.0; x[1] = 1.0; x[2] = 1.0;
mesh.AddVertex(x);
int el[8];
el[0] = 0;
el[1] = 1;
el[2] = 4;
el[3] = 3;
el[4] = 6;
el[5] = 7;
el[6] = 10;
el[7] = 9;
Rotation3DVertices(el, face_perm_1/4, face_perm_1%4);
mesh.AddHex(el);
el[0] = 1;
el[1] = 2;
el[2] = 5;
el[3] = 4;
el[4] = 7;
el[5] = 8;
el[6] = 11;
el[7] = 10;
Rotation3DVertices(el, face_perm_2/4, face_perm_2%4);
mesh.AddHex(el);
mesh.FinalizeHexMesh(true);
mesh.Finalize();
return mesh;
}
Mesh MeshOrientation(int dim, int o1, int o2)
{
if (dim == 2) { return Mesh2D_Orientation(o1, o2); }
else if (dim == 3) { return Mesh3D_Orientation(o1, o2); }
else { MFEM_ABORT("Unsupported dimension."); }
}
}
+25
View File
@@ -0,0 +1,25 @@
// 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_MAKE_PERMUTED_MESH_HPP
#define MFEM_MAKE_PERMUTED_MESH_HPP
#include "mfem.hpp"
#include "unit_tests.hpp"
namespace mfem
{
Mesh MeshOrientation(int dim, int o1, int o2);
}
#endif
+3 -3
View File
@@ -235,7 +235,7 @@ void test_derefine_L2_element(int order, Element::Type el_type, int basis_type)
}
}
TEST_CASE("AMR Coarsen L2 Element","[AMR][Coarsen]")
TEST_CASE("AMR Coarsen L2 Element","[AMR][Coarsen][CUDA]")
{
std::vector<int> orders_1d{0,1,2,3};
std::vector<int> orders_2d{0,1,2,3};
@@ -420,7 +420,7 @@ void stress_parallel_coarsen(int order, Element::Type el_type, int basis_type)
#endif
}
TEST_CASE("Parallel AMR Coarsen Stress Test", "[AMR][Coarsen][Parallel]")
TEST_CASE("Parallel AMR Coarsen Stress Test", "[AMR][Coarsen][Parallel][CUDA]")
{
std::vector<int> orders_1d{0,1,2,3};
std::vector<int> orders_2d{0,1,2,3};
@@ -482,7 +482,7 @@ TEST_CASE("Parallel AMR Coarsen Stress Test", "[AMR][Coarsen][Parallel]")
}
TEST_CASE("ParDerefine", "[Parallel]")
TEST_CASE("ParDerefine", "[Parallel][CUDA]")
{
for (dimension = 2; dimension <= 3; ++dimension)
{
+5 -145
View File
@@ -11,148 +11,10 @@
#include "mfem.hpp"
#include "unit_tests.hpp"
#include "make_permuted_mesh.hpp"
using namespace mfem;
Mesh *mesh_2d_orientation(int face_perm_1, int face_perm_2)
{
static const int dim = 2;
static const int nv = 6;
static const int nel = 2;
Mesh *mesh = new Mesh(dim, nv, nel);
real_t x[dim];
x[0] = 0.0; x[1] = 0.0;
mesh->AddVertex(x);
x[0] = 1.0; x[1] = 0.0;
mesh->AddVertex(x);
x[0] = 2.0; x[1] = 0.0;
mesh->AddVertex(x);
x[0] = 0.0; x[1] = 1.0;
mesh->AddVertex(x);
x[0] = 1.0; x[1] = 1.0;
mesh->AddVertex(x);
x[0] = 2.0; x[1] = 1.0;
mesh->AddVertex(x);
int el[4];
el[0] = 0;
el[1] = 1;
el[2] = 4;
el[3] = 3;
std::rotate(&el[0], &el[face_perm_1], &el[3] + 1);
mesh->AddQuad(el);
el[0] = 1;
el[1] = 2;
el[2] = 5;
el[3] = 4;
std::rotate(&el[0], &el[face_perm_2], &el[3] + 1);
mesh->AddQuad(el);
mesh->FinalizeQuadMesh(true);
mesh->GenerateBoundaryElements();
mesh->Finalize();
return mesh;
}
void rotate_3d_vertices(int *v, int ref_face, int rot)
{
std::vector<int> face_1, face_2;
switch (ref_face/2)
{
case 0:
face_1 = {v[0], v[1], v[2], v[3]};
face_2 = {v[4], v[5], v[6], v[7]};
break;
case 1:
face_1 = {v[1], v[5], v[6], v[2]};
face_2 = {v[0], v[4], v[7], v[3]};
break;
case 2:
face_1 = {v[4], v[5], v[1], v[0]};
face_2 = {v[7], v[6], v[2], v[3]};
break;
}
if (ref_face % 2 == 0)
{
std::reverse(face_1.begin(), face_1.end());
std::reverse(face_2.begin(), face_2.end());
std::swap(face_1, face_2);
}
std::rotate(face_1.begin(), face_1.begin() + rot, face_1.end());
std::rotate(face_2.begin(), face_2.begin() + rot, face_2.end());
for (int i=0; i<4; ++i)
{
v[i] = face_1[i];
v[i+4] = face_2[i];
}
}
Mesh *mesh_3d_orientation(int face_perm_1, int face_perm_2)
{
static const int dim = 3;
static const int nv = 12;
static const int nel = 2;
Mesh *mesh = new Mesh(dim, nv, nel);
real_t x[dim];
x[0] = 0.0; x[1] = 0.0; x[2] = 0.0;
mesh->AddVertex(x);
x[0] = 1.0; x[1] = 0.0; x[2] = 0.0;
mesh->AddVertex(x);
x[0] = 2.0; x[1] = 0.0; x[2] = 0.0;
mesh->AddVertex(x);
x[0] = 0.0; x[1] = 1.0; x[2] = 0.0;
mesh->AddVertex(x);
x[0] = 1.0; x[1] = 1.0; x[2] = 0.0;
mesh->AddVertex(x);
x[0] = 2.0; x[1] = 1.0; x[2] = 0.0;
mesh->AddVertex(x);
x[0] = 0.0; x[1] = 0.0; x[2] = 1.0;
mesh->AddVertex(x);
x[0] = 1.0; x[1] = 0.0; x[2] = 1.0;
mesh->AddVertex(x);
x[0] = 2.0; x[1] = 0.0; x[2] = 1.0;
mesh->AddVertex(x);
x[0] = 0.0; x[1] = 1.0; x[2] = 1.0;
mesh->AddVertex(x);
x[0] = 1.0; x[1] = 1.0; x[2] = 1.0;
mesh->AddVertex(x);
x[0] = 3.0; x[1] = 1.0; x[2] = 1.0;
mesh->AddVertex(x);
int el[8];
el[0] = 0;
el[1] = 1;
el[2] = 4;
el[3] = 3;
el[4] = 6;
el[5] = 7;
el[6] = 10;
el[7] = 9;
rotate_3d_vertices(el, face_perm_1/4, face_perm_1%4);
mesh->AddHex(el);
el[0] = 1;
el[1] = 2;
el[2] = 5;
el[3] = 4;
el[4] = 7;
el[5] = 8;
el[6] = 11;
el[7] = 10;
rotate_3d_vertices(el, face_perm_2/4, face_perm_2%4);
mesh->AddHex(el);
mesh->FinalizeHexMesh(true);
mesh->GenerateBoundaryElements();
mesh->Finalize();
return mesh;
}
real_t x_fn(const Vector &xvec) { return xvec[0]; }
real_t y_fn(const Vector &xvec) { return xvec[1]; }
real_t z_fn(const Vector &xvec) { return xvec[2]; }
@@ -214,10 +76,9 @@ TEST_CASE("2D Face Permutation", "[Face Permutation]")
{
for (int fp1=0; fp1<4; ++fp1)
{
Mesh *mesh = mesh_2d_orientation(fp1, fp2);
real_t error = TestFaceRestriction(*mesh, order);
Mesh mesh = MeshOrientation(2, fp1, fp2);
real_t error = TestFaceRestriction(mesh, order);
max_err = std::max(max_err, error);
delete mesh;
}
}
REQUIRE(max_err < 1e-15);
@@ -231,10 +92,9 @@ TEST_CASE("3D Face Permutation", "[Face Permutation]")
{
for (int fp1=0; fp1<24; ++fp1)
{
Mesh *mesh = mesh_3d_orientation(fp1, fp2);
real_t error = TestFaceRestriction(*mesh, order);
Mesh mesh = MeshOrientation(3, fp1, fp2);
real_t error = TestFaceRestriction(mesh, order);
max_err = std::max(max_err, error);
delete mesh;
}
}
REQUIRE(max_err < 1e-15);
+1 -65
View File
@@ -11,6 +11,7 @@
#include "mfem.hpp"
#include "unit_tests.hpp"
#include "../linalg/test_same_matrices.hpp"
#include "../../fem/lor/lor_ads.hpp"
#include "../../fem/lor/lor_ams.hpp"
#include <memory>
@@ -25,47 +26,6 @@ using namespace mfem;
namespace lor_batched
{
void TestSameMatrices(SparseMatrix &A1, const SparseMatrix &A2,
HYPRE_BigInt *cmap1=nullptr,
std::unordered_map<HYPRE_BigInt,int> *cmap2inv=nullptr)
{
REQUIRE(A1.Height() == A2.Height());
int n = A1.Height();
const int *I1 = A1.HostReadI();
const int *J1 = A1.HostReadJ();
const real_t *V1 = A1.HostReadData();
A2.HostReadI();
A2.HostReadJ();
A2.HostReadData();
real_t error = 0.0;
for (int i=0; i<n; ++i)
{
for (int jj=I1[i]; jj<I1[i+1]; ++jj)
{
int j = J1[jj];
if (cmap1)
{
if (cmap2inv->count(cmap1[j]) > 0)
{
j = (*cmap2inv)[cmap1[j]];
}
else
{
error = std::max(error, std::fabs(V1[jj]));
continue;
}
}
error = std::max(error, std::fabs(V1[jj] - A2(i,j)));
}
}
REQUIRE(error == MFEM_Approx(0.0, 1e-10));
}
template <typename FE_COLL>
FE_COLL *NewLOR_FE_Collection(int order, int dim)
{
@@ -157,30 +117,6 @@ TEST_CASE("LOR Batched RT", "[LOR][BatchedLOR][GPU]")
#ifdef MFEM_USE_MPI
void TestSameMatrices(HypreParMatrix &A1, const HypreParMatrix &A2)
{
HYPRE_BigInt *cmap1, *cmap2;
SparseMatrix diag1, offd1, diag2, offd2;
A1.GetDiag(diag1);
A2.GetDiag(diag2);
A1.GetOffd(offd1, cmap1);
A2.GetOffd(offd2, cmap2);
TestSameMatrices(diag1, diag2);
if (cmap1)
{
std::unordered_map<HYPRE_BigInt,int> cmap2inv;
for (int i=0; i<offd2.Width(); ++i) { cmap2inv[cmap2[i]] = i; }
TestSameMatrices(offd1, offd2, cmap1, &cmap2inv);
}
else
{
TestSameMatrices(offd1, offd2);
}
}
template <typename FE_COLL, typename INTEG_1, typename INTEG_2>
void ParTestBatchedLOR()
{
+382
View File
@@ -0,0 +1,382 @@
// 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 "mfem.hpp"
#include "unit_tests.hpp"
#include "make_permuted_mesh.hpp"
#include "../linalg/test_same_matrices.hpp"
using namespace mfem;
class DG_LOR_DiffusionPreconditioner : public BilinearFormIntegrator
{
Mesh &mesh;
double kappa;
int p;
IntegrationRule gl_p, gl_pp1;
Vector shape1, shape2, nor;
public:
DG_LOR_DiffusionPreconditioner(Mesh &mesh_, int p_, double kappa_)
: mesh(mesh_), kappa(kappa_), p(p_)
{
QuadratureFunctions1D::GaussLobatto(p+1, &gl_p);
QuadratureFunctions1D::GaussLobatto(p+2, &gl_pp1);
}
double PenaltyFactor(int idx1, int idx2)
{
int pp1 = p + 1;
int x1 = idx1 % pp1;
int y1 = (idx1 / pp1) % pp1;
int z1 = (idx1 / pp1) / pp1;
int x2 = idx2 % pp1;
int y2 = (idx2 / pp1) % pp1;
int z2 = (idx2 / pp1) / pp1;
int dim = mesh.Dimension();
auto compute_factor = [&](int i1, int i2)
{
int j = std::min(i1, i2);
if (i1 == i2)
{
double w = gl_p[j].weight;
double k = gl_pp1[i1+1].x - gl_pp1[i1].x;
return w/k;
}
else
{
double h = gl_p[j+1].x - gl_p[j].x;
double k1 = gl_pp1[i1+1].x - gl_pp1[i1].x;
double k2 = gl_pp1[i2+1].x - gl_pp1[i2].x;
double avg = 0.5*k1 + 0.5*k2;
return avg/h;
}
};
double factor = compute_factor(x1, x2);
if (dim >= 2) { factor *= compute_factor(y1, y2); }
if (dim == 3) { factor *= compute_factor(z1, z2); }
return factor;
}
double BdrPenaltyFactor(int idx, int f)
{
int pp1 = p+1;
int x = idx % pp1;
int y = (idx / pp1) % pp1;
int z = (idx / pp1) / pp1;
int dim = mesh.Dimension();
auto subcell_size = [&](int i)
{
return gl_pp1[i+1].x - gl_pp1[i].x;
};
double factor = (p+1)*(p+1);
if (dim == 1)
{
factor *= subcell_size(x);
}
else if (dim == 2)
{
int ni, nj;
ni = (f == 1 || f == 3) ? x : y;
nj = (f == 1 || f == 3) ? y : x;
factor *= subcell_size(ni)/subcell_size(nj)*gl_p[nj].weight;
}
else if (dim == 3)
{
int ni, nj, nk;
if (f == 2 || f == 4) { ni = x; nj = y; nk = z; }
else if (f == 1 || f == 3) { ni = y; nj = x; nk = z; }
else { ni = z; nj = x; nk = y; }
factor *= subcell_size(ni)/subcell_size(nj)/subcell_size(nk);
factor *= gl_p[nj].weight*gl_p[nk].weight;
}
return factor;
}
using BilinearFormIntegrator::AssembleFaceMatrix;
virtual void AssembleFaceMatrix(const FiniteElement &el1,
const FiniteElement &el2,
FaceElementTransformations &Trans,
DenseMatrix &elmat)
{
int dim, ndof1, ndof2, ndofs;
double w, wq = 0.0;
dim = el1.GetDim();
ndof1 = el1.GetDof();
nor.SetSize(dim);
shape1.SetSize(ndof1);
if (Trans.Elem2No >= 0)
{
ndof2 = el2.GetDof();
shape2.SetSize(ndof2);
}
else
{
ndof2 = 0;
}
int face_no;
if (ndof2) { face_no = Trans.ElementNo; }
else { face_no = mesh.GetBdrElementFaceIndex(Trans.ElementNo); }
int info1, info2;
mesh.GetFaceInfos(face_no, &info1, &info2);
int local_face = info1/64;
const CoarseFineTransformations &cftr = mesh.GetRefinementTransforms();
double factor;
bool interior = false;
if (Trans.Elem2No >= 0 && Trans.Elem2No < mesh.GetNE())
{
int parent_el1 = cftr.embeddings[Trans.Elem1No].parent;
int parent_el2 = cftr.embeddings[Trans.Elem2No].parent;
if (parent_el1 == parent_el2)
{
interior = true;
factor = PenaltyFactor(cftr.embeddings[Trans.Elem1No].matrix,
cftr.embeddings[Trans.Elem2No].matrix);
}
}
if (!interior)
{
factor = kappa*BdrPenaltyFactor(cftr.embeddings[Trans.Elem1No].matrix,
local_face);
}
ndofs = ndof1 + ndof2;
elmat.SetSize(ndofs);
elmat = 0.0;
const IntegrationRule *ir = IntRule;
if (ir == NULL) { ir = &IntRules.Get(Trans.GetGeometryType(), 1); }
for (int q = 0; q < ir->GetNPoints(); q++)
{
const IntegrationPoint &ip = ir->IntPoint(q);
Trans.SetAllIntPoints(&ip);
const IntegrationPoint &eip1 = Trans.GetElement1IntPoint();
const IntegrationPoint &eip2 = Trans.GetElement2IntPoint();
if (dim == 1) { nor(0) = 2*eip1.x - 1.0; }
else { CalcOrtho(Trans.Jacobian(), nor); }
el1.CalcShape(eip1, shape1);
w = ip.weight;
double h_face = nor.Norml2();
double h_el = Trans.Elem1->Weight();
double h = h_el/h_face; // perpendicular element size
if (ndof2)
{
el2.CalcShape(eip2, shape2);
double h_el_2 = Trans.Elem2->Weight();
h = 0.5*h + 0.5*h_el_2/h_face; // average both element sizes
}
if (interior)
{
wq = w*factor*h_face/h;
}
else
{
wq = w*factor*h_face/h;
}
for (int i = 0; i < ndof1; i++)
{
const double wsi = wq*shape1(i);
for (int j = 0; j < ndof1; j++)
{
elmat(i, j) += wsi * shape1(j);
}
}
if (ndof2)
{
for (int i = 0; i < ndof2; i++)
{
const double wsi = wq*shape2(i);
for (int j = 0; j < ndof1; j++)
{
elmat(ndof1 + i, j) -= wsi * shape1(j);
elmat(j, ndof1 + i) -= wsi * shape1(j);
}
for (int j = 0; j < ndof2; j++)
{
elmat(ndof1 + i, ndof1 + j) += wsi * shape2(j);
}
}
}
}
}
};
class DG_LOR_MassPreconditioner : public BilinearFormIntegrator
{
Mesh &mesh_ho, &mesh_lor;
const int p;
IntegrationRule gll;
Coefficient *Q;
public:
DG_LOR_MassPreconditioner(Mesh &mesh_ho_,
Mesh &mesh_lor_,
int p_,
Coefficient *Q_)
: mesh_ho(mesh_ho_),
mesh_lor(mesh_lor_),
p(p_),
Q(Q_)
{
QuadratureFunctions1D::GaussLobatto(p+1, &gll);
}
void AssembleElementMatrix(const FiniteElement &el,
ElementTransformation &Tr,
DenseMatrix &elmat)
{
const CoarseFineTransformations &cftr = mesh_lor.GetRefinementTransforms();
const int parent_el = cftr.embeddings[Tr.ElementNo].parent;
// We use the point matrix index to identify the local LOR element index
// within the high-order coarse element.
const int lor_index = cftr.embeddings[Tr.ElementNo].matrix;
// Assuming piecewise constant
elmat.SetSize(1);
const int dim = mesh_ho.Dimension();
IntegrationPoint ip;
if (dim == 2)
{
const int iy = lor_index / (p + 1);
const int ix = lor_index % (p + 1);
ip.x = gll[ix].x;
ip.y = gll[iy].x;
elmat(0,0) = gll[ix].weight * gll[iy].weight;
}
else if (dim == 3)
{
const int iz = lor_index / (p + 1) / (p + 1);
const int iy = (lor_index / (p + 1)) % (p + 1);
const int ix = lor_index % (p + 1);
ip.x = gll[ix].x;
ip.y = gll[iy].x;
ip.z = gll[iz].x;
elmat(0,0) = gll[ix].weight * gll[iy].weight * gll[iz].weight;
}
ElementTransformation &Tr_ho = *mesh_ho.GetElementTransformation(parent_el);
Tr_ho.SetIntPoint(&ip);
const real_t detJ = Tr_ho.Weight();
elmat(0,0) *= detJ;
if (Q)
{
elmat(0,0) *= Q->Eval(Tr_ho, ip);
}
}
};
static void TestBatchedLOR_DG(Mesh &mesh, int order)
{
DG_FECollection fec(order, mesh.Dimension(), BasisType::GaussLobatto);
FiniteElementSpace fespace(&mesh, &fec);
// Set up some coefficients using H1 grid functions
H1_FECollection h1fec(2, mesh.Dimension());
FiniteElementSpace h1fes(&mesh, &h1fec);
GridFunction gf1(&h1fes), gf2(&h1fes);
gf1.Randomize(1);
gf2.Randomize(2);
GridFunctionCoefficient mass_coeff(&gf1);
GridFunctionCoefficient diff_coeff(&gf2);
ConstantCoefficient one(1.0);
constexpr real_t sigma = -1.0;
const int eta = 2;
const int kappa = eta * (order + 1) * (order + 1);
BilinearForm a(&fespace);
a.AddDomainIntegrator(new DiffusionIntegrator);
a.AddInteriorFaceIntegrator(new DGDiffusionIntegrator(one, sigma, kappa));
a.AddBdrFaceIntegrator(new DGDiffusionIntegrator(one, sigma, kappa));
Array<int> ess_dofs; // Empty
LORDiscretization lor(fespace);
lor.AssembleSystem(a, ess_dofs);
SparseMatrix &A1 = lor.GetAssembledMatrix();
FiniteElementSpace &fes_lor = lor.GetFESpace();
Mesh &mesh_lor = *fes_lor.GetMesh();
BilinearForm a_lor(&fes_lor);
a_lor.AddBdrFaceIntegrator(new DG_LOR_DiffusionPreconditioner(
mesh_lor, order, eta));
a_lor.AddInteriorFaceIntegrator(new DG_LOR_DiffusionPreconditioner(
mesh_lor, order, eta));
a_lor.Assemble();
a_lor.Finalize();
SparseMatrix &A2 = a_lor.SpMat();
TestSameMatrices(A1, A2);
}
TEST_CASE("LOR Batched DG Orientation", "[LOR][BatchedLOR][CUDA]")
{
const int order = 3;
const int dim = launch_all_non_regression_tests ? GENERATE(2, 3) : 2;
const int orientation1 = GENERATE_COPY(range(0, dim == 2 ? 4 : 24));
const int orientation2 = GENERATE_COPY(range(0, dim == 2 ? 4 : 24));
CAPTURE(order, dim, orientation1, orientation2);
Mesh mesh = MeshOrientation(dim, orientation1, orientation2);
TestBatchedLOR_DG(mesh, order);
}
TEST_CASE("LOR Batched DG", "[LOR][BatchedLOR][CUDA]")
{
const int order = 3;
const auto mesh_fname = GENERATE(
"../../data/beam-quad.mesh",
"../../data/l-shape.mesh",
"../../data/beam-hex.mesh",
"../../data/fichera.mesh"
);
CAPTURE(mesh_fname);
Mesh mesh = Mesh::LoadFromFile(mesh_fname);
mesh.Transform([](const Vector &xin, Vector &xout)
{
for (int d = 0; d < xin.Size(); ++d)
{
xout[d] = xin[d] * (1.0 + d / 3.0);
}
});
TestBatchedLOR_DG(mesh, order);
}
+96
View File
@@ -0,0 +1,96 @@
// 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_TEST_SAME_MATRICES_HPP
#define MFEM_TEST_SAME_MATRICES_HPP
#include "mfem.hpp"
namespace mfem
{
#ifndef MFEM_USE_MPI
#define HYPRE_BigInt int
#endif // MFEM_USE_MPI
inline void TestSameMatrices(
SparseMatrix &A1, const SparseMatrix &A2,
HYPRE_BigInt *cmap1=nullptr,
std::unordered_map<HYPRE_BigInt,int> *cmap2inv=nullptr)
{
REQUIRE(A1.Height() == A2.Height());
int n = A1.Height();
const int *I1 = A1.HostReadI();
const int *J1 = A1.HostReadJ();
const real_t *V1 = A1.HostReadData();
A2.HostReadI();
A2.HostReadJ();
A2.HostReadData();
real_t error = 0.0;
for (int i=0; i<n; ++i)
{
for (int jj=I1[i]; jj<I1[i+1]; ++jj)
{
int j = J1[jj];
if (cmap1)
{
if (cmap2inv->count(cmap1[j]) > 0)
{
j = (*cmap2inv)[cmap1[j]];
}
else
{
error = std::max(error, std::fabs(V1[jj]));
continue;
}
}
error = std::max(error, std::fabs(V1[jj] - A2(i,j)));
}
}
REQUIRE(error == MFEM_Approx(0.0, 1e-10));
}
#ifdef MFEM_USE_MPI
inline void TestSameMatrices(HypreParMatrix &A1, const HypreParMatrix &A2)
{
HYPRE_BigInt *cmap1, *cmap2;
SparseMatrix diag1, offd1, diag2, offd2;
A1.GetDiag(diag1);
A2.GetDiag(diag2);
A1.GetOffd(offd1, cmap1);
A2.GetOffd(offd2, cmap2);
TestSameMatrices(diag1, diag2);
if (cmap1)
{
std::unordered_map<HYPRE_BigInt,int> cmap2inv;
for (int i=0; i<offd2.Width(); ++i) { cmap2inv[cmap2[i]] = i; }
TestSameMatrices(offd1, offd2, cmap1, &cmap2inv);
}
else
{
TestSameMatrices(offd1, offd2);
}
}
#endif // MFEM_USE_MPI
} // namespace mfem
#endif