Compare commits

...
Author SHA1 Message Date
camierjs 3a319b653a Merge branch 'master' into hpcftools/flowSolver 2025-06-30 13:32:48 -07:00
Veselin Dobrev c53a016d06 Merge pull request #4818 from mfem/mesh-transform-dev
Adding an affine transformation to mesh-explorer
2025-06-28 07:58:55 -07:00
Veselin Dobrev ea2b42c49d Merge pull request #4903 from mfem/fix-umpire-introspection-off
better umpire device deallocation
2025-06-28 07:57:55 -07:00
John Camier c7e066a0d3 Merge branch 'master' into fix-umpire-introspection-off 2025-06-28 06:26:17 -07:00
Veselin Dobrev 0a5730eac3 Merge pull request #4769 from mfem/dev/abs-diag-smoothers
Abs-Val-Jacobi-type of preconditions/smoothers for PA operators
2025-06-26 09:36:30 -07:00
Veselin Dobrev 477c475c1a Merge pull request #4893 from mfem/gpu-omp
Use device priorities for vector dot, min and max
2025-06-24 12:33:16 -07:00
Veselin Dobrev 34fdcddb8d Merge pull request #4810 from mfem/plbound
Bounding high-order FEM functions
2025-06-24 12:31:47 -07:00
Veselin Dobrev 91fa9936ef Merge pull request #4835 from mfem/fix-nurbsext-constructor
NURBSExtension Constructor and edge_to_knot mapping
2025-06-24 12:29:14 -07:00
Veselin Dobrev d95e6e0adc Merge pull request #4854 from mfem/pref-mixed-serial
Restore p-refinement on serial mixed meshes
2025-06-24 12:27:52 -07:00
John Camier 82d3b8b92e Merge branch 'master' into gpu-omp 2025-06-23 16:11:10 -07:00
John Camier 9c90e3830a Merge branch 'master' into gpu-omp 2025-06-22 20:25:27 -07:00
John Camier a8e83d2f0d Merge branch 'master' into fix-umpire-introspection-off 2025-06-22 20:23:40 -07:00
Gabriel Pinochet-Soto 288657ebbd Use SLI + Mass integrator as example 2025-06-21 13:11:25 -07:00
Gabriel Pinochet-Soto 5a26bd936c Merge branch 'master' of github.com:mfem/mfem into dev/abs-diag-smoothers 2025-06-21 13:05:49 -07:00
Gabriel Pinochet-Soto 05034b8917 Correct examples; vis is true 2025-06-20 16:58:45 -07:00
Tom Stitt 32aea6ed2d deallocate via the allocator instead of the resource manager. fixes use of device allocators with introspection off 2025-06-20 14:15:31 -07:00
Gabriel Pinochet-Soto 9407050e6a Revert "Update to use maps->Abs"
This reverts commit 0515209ffd.
2025-06-18 23:09:29 -07:00
Gabriel Pinochet-Soto 0515209ffd Update to use maps->Abs 2025-06-18 20:05:11 -07:00
Gabriel Pinochet-Soto 3446b46420 Merge branch 'master' of github.com:mfem/mfem into dev/abs-diag-smoothers 2025-06-18 19:37:54 -07:00
Gabriel Pinochet-Soto f8f18f8722 Remove warning 2025-06-18 19:33:37 -07:00
Gabriel Pinochet-Soto e7eabeb5e2 Remove TODOs bilinearform_ext.cpp 2025-06-18 18:55:22 -07:00
John Camier 57b5d23e4b Merge branch 'master' into gpu-omp 2025-06-18 10:21:40 -07:00
John Camier 4f3c73b3eb Merge branch 'master' into gpu-omp 2025-06-17 17:01:31 -07:00
Mittal, Ketan 8be13975b0 Merge branch 'plbound' of https://github.com/mfem/mfem into plbound 2025-06-17 11:49:40 -07:00
Mittal, Ketan 7fce328f12 fix spacing before sample run 2025-06-17 11:43:42 -07:00
Veselin Dobrev 56b1a1715a Use DofToQuad::Abs in a few places. Remove unused methods AddAbsMult*.
Small doxygen tweaks.
2025-06-17 01:16:34 -07:00
Veselin Dobrev 3514c0f0d4 Merge branch 'master' into dev/abs-diag-smoothers
Resolved conflicts:
   CHANGELOG
   makefile
   miniapps/CMakeLists.txt
2025-06-16 22:14:00 -07:00
camierjs 5791aa4629 Merge branch 'master' into gpu-omp 2025-06-13 13:43:40 -07:00
camierjs 18cff41dac Merge branch 'master' into gpu-omp 2025-06-13 11:51:56 -07:00
Tzanio Kolev b5366e3ad3 Merge branch 'master' into dev/abs-diag-smoothers 2025-06-13 10:56:52 -07:00
camierjs fb9449c47d Adjust use_dev code path, make style & const 2025-06-13 08:31:55 -07:00
camierjs 1555bfe3a5 Move reduction internals to reducers header 2025-06-12 11:56:50 -07:00
camierjs a3b12b6f97 Use device priorities for vector dot, min and max 2025-06-12 11:27:06 -07:00
Tzanio Kolev c82d9bd8a0 Merge branch 'master' into dev/abs-diag-smoothers 2025-06-11 20:09:17 -07:00
Tzanio Kolev 1e6ee60790 Merge branch 'master' into dev/abs-diag-smoothers 2025-06-11 10:14:46 -07:00
Veselin Dobrev b7ff8749a3 Fix out-of-source testing with GNU make.
Adjust a tolerance in miniapps/nurbs/nurbs_solenoidal.cpp for macOS.

Fix typos in the miniapps/nurbs/makefile in the nurbs_solenoidal tests.

Re-formatting some long lines.
2025-06-09 18:19:09 -07:00
Ketan Mittalandtarikdzanic a72d85fa6c Update fem/bounds.cpp
Co-authored-by: tarikdzanic <41964501+tarikdzanic@users.noreply.github.com>
2025-06-03 16:47:49 -07:00
Mittal, Ketan 0fd9dcceb3 change some raw ptrs to unique ptrs 2025-06-03 12:55:04 -07:00
Mittal, Ketan b66d56ee9b Merge branch 'plbound' of https://github.com/mfem/mfem into plbound 2025-06-02 10:35:55 -07:00
Mittal, Ketan 9852e93449 reviewer comment 2025-06-02 10:35:46 -07:00
Ketan Mittal 27db3a4121 Merge branch 'master' into plbound 2025-05-29 10:49:30 -07:00
Gabriel Pinochet-Soto a35ef68c2a Style correction... 2025-05-28 11:35:12 -07:00
Gabriel Pinochet-Soto 2fca844393 Implement L(p,q) elementwise, update CHANGELOG 2025-05-28 11:32:50 -07:00
Gabriel Pinochet-Soto 698183a8db Correct examples 2025-05-28 10:42:20 -07:00
Gabriel Pinochet-Soto fa61508248 Merge branch 'master' of github.com:mfem/mfem into dev/abs-diag-smoothers 2025-05-27 22:44:20 -07:00
Gabriel Pinochet-Soto d2baadad26 Update descriptions, add --device cuda example 2025-05-27 17:30:03 -07:00
Mittal, Ketan 0aa68f431f Merge branch 'master' of https://github.com/mfem/mfem into plbound 2025-05-27 11:58:58 -07:00
Mittal, Ketan d2288ef6fd another reviewer comment 2025-05-27 11:58:34 -07:00
Ketan MittalandWill Pazner 6f882ed87e Update fem/pgridfunc.cpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-27 11:23:26 -07:00
Mittal, Ketan 813a7de323 reviewer comments 2025-05-27 11:18:28 -07:00
Justin Laughlin cc4f5625e2 Merge branch 'master' into fix-nurbsext-constructor 2025-05-27 09:27:59 -07:00
Mittal, Ketan f4baef8b5d fix shadow declaration 2025-05-26 17:06:59 -07:00
Mittal, Ketan 0e4b0f7bda minor 2025-05-26 16:44:40 -07:00
Mittal, Ketan 7317d4d139 minor 2025-05-26 16:44:28 -07:00
Mittal, Ketan e9e684599f Merge branch 'plbound' of https://github.com/mfem/mfem into plbound 2025-05-26 14:12:44 -07:00
Mittal, Ketan 6c994fea99 minor 2025-05-26 14:12:31 -07:00
Gabriel Pinochet-Soto 3b05995fd1 Update some tests 2025-05-26 08:36:18 -07:00
Gabriel Pinochet-Soto 5e99ffc9a3 Relabel PC enum, other typos 2025-05-24 22:23:44 -07:00
Gabriel Pinochet-Soto bcbb24dce0 Remove redundant LEGACYFULL option 2025-05-24 19:06:51 -07:00
Gabriel Pinochet-Soto 797113ff71 Remove MG for HCurl 2025-05-24 18:53:44 -07:00
Gabriel Pinochet-Soto 41af83b10c Remove elast from miniapp 2025-05-24 18:42:22 -07:00
Gabriel Pinochet-Soto 4da2ea6f08 Remove abs elasticity integrators from source code 2025-05-24 18:41:43 -07:00
Gabriel Pinochet-Soto 9eb0f5c0c3 Merge branch 'master' of github.com:mfem/mfem into dev/abs-diag-smoothers 2025-05-24 16:06:51 -07:00
Ketan Mittal 4a1a5dfa55 Merge branch 'master' into plbound 2025-05-23 15:58:16 -07:00
Mittal, Ketan 59edd7255c separate out PLBound from gridfunc.hpp 2025-05-23 15:56:46 -07:00
Mittal, Ketan 6d01e152de reviewer comments 2025-05-23 12:26:06 -07:00
Will Pazner 0e22b182a6 Rename MultUnsigned to AbsMult in restriction classes
Deprecate 'MultUnsigned' and related functions
2025-05-22 10:49:28 -07:00
Justin Laughlin 49eb2715ba typos 2025-05-21 18:26:38 -07:00
Justin Laughlin 5d3be16590 Fix check for empty nodes/weights 2025-05-21 17:05:17 -07:00
Justin Laughlin 899a2fc7cb Fix typos, improve documentation of new nurbs meshes, add size != 0 check in unit test 2025-05-21 13:55:03 -07:00
Ketan MittalandWill Pazner bd0fa51539 Update fem/pgridfunc.cpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-21 13:40:59 -07:00
Ketan MittalandWill Pazner b151c909f3 Update miniapps/tools/gridfunction-bounds.cpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-21 13:40:29 -07:00
Ketan MittalandWill Pazner ce7f94ec1f Update miniapps/tools/gridfunction-bounds.cpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-21 13:40:18 -07:00
Ketan MittalandWill Pazner ee6b9fdc2f Update miniapps/meshing/mesh-bounding-boxes.cpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-21 13:40:06 -07:00
Justin Laughlin c418868c83 Fix typo 2025-05-21 11:24:24 -07:00
Mark L. Stowell 7a0344a6bb Merge branch 'master' into mesh-transform-dev 2025-05-21 10:19:18 -07:00
Justin Laughlin 51812480bd Merge branch 'master' into fix-nurbsext-constructor 2025-05-20 15:36:39 -07:00
Justin Laughlin 61cc19ca22 One final cleanup 2025-05-20 15:34:57 -07:00
Justin Laughlin 5e48080f3d Minor cleanup/comments 2025-05-20 15:24:42 -07:00
Justin Laughlin af73851cb2 Comment 2025-05-20 15:13:28 -07:00
Justin Laughlin 338e4288ca Add some comments and update nurbs getters 2025-05-20 15:04:58 -07:00
Justin Laughlin dca4cd510a Add inverse map rpkv_to_ukv for efficiency 2025-05-20 13:16:25 -07:00
Justin Laughlin 2b23f35ec7 Copy miniapps/nurbs/meshes with cmake 2025-05-20 12:48:26 -07:00
Justin Laughlin ea569c5806 Add documentation and fix name 2025-05-20 11:20:34 -07:00
Tzanio Kolev e2efc259b6 Merge branch 'master' into pref-mixed-serial 2025-05-19 16:51:16 -07:00
Tzanio Kolev b1468c14fd Merge pull request #4862 from mfem/dev/abs-diag-smoothers-mods
Additional proposed modification for PR #4769 (`dev/abs-diag-smoothers`)
2025-05-18 10:32:17 -07:00
Justin Laughlin d3281a8a86 Address some comments from Dylan - update path on nurbs test 2025-05-16 22:12:17 -07:00
Justin Laughlin a7d59d35e0 Fix pkv_map in GetEdgeToUniqueKnotvector 2025-05-16 21:39:50 -07:00
Justin Laughlin 70680d6187 Add more nurbs meshes to test with 2025-05-16 20:04:01 -07:00
Justin Laughlin 948462da6e minor cleanup 2025-05-16 18:07:12 -07:00
Justin Laughlin fddefa7838 Update GetEdgeToUniqueKnotvector - should handle edge cases better 2025-05-16 18:04:21 -07:00
Veselin Dobrev 62a01ee670 Re-format the sample runs in miniapps/diag-smoothers so that they follow the
formatting used in other places.
2025-05-16 16:54:53 -07:00
Veselin Dobrev e23768f5f7 Additional proposed modification for PR #4769 (dev/abs-diag-smoothers) 2025-05-16 16:13:36 -07:00
Justin Laughlin 53d4f78fdb Merge branch 'master' into fix-nurbsext-constructor 2025-05-14 18:57:40 -07:00
Justin Laughlin eeb71eee37 Add test 2025-05-14 18:10:30 -07:00
Justin Laughlin 3630a8f8a1 Add getters for testing; minor fix/formatting 2025-05-14 17:01:03 -07:00
Justin Laughlin 048904b731 Cleanup/formatting + implement Dylan's comments 2025-05-14 14:31:26 -07:00
Justin Laughlin 2496b33699 Fix Mesh::GetEdgeToUniqueKnotvector so it works for 1d 2025-05-14 11:39:04 -07:00
Dylan Copeland 1ff8b6811d Empty line 2025-05-09 11:03:09 -07:00
Dylan Copeland cf269700a8 Enable and add sample runs for p-refinement on serial mixed meshes. 2025-05-09 10:56:46 -07:00
Mark L. Stowell cb0c205bd1 Merge branch 'master' into mesh-transform-dev 2025-05-07 13:58:59 -07:00
Stowell, Mark L. 2ed6fdc85a Cleaning up compiler warnings 2025-05-07 13:12:53 -07:00
Justin Laughlin 15b35a01ba Merge branch 'master' into fix-nurbsext-constructor 2025-05-06 10:45:29 -07:00
Justin Laughlin ff746a8af6 Remove some getters that are unnecessary for this PR 2025-05-05 14:47:13 -07:00
Tzanio Kolev 41ea219782 Merge branch 'master' into plbound 2025-05-03 13:41:55 -07:00
Veselin Dobrev b54ee3537f In a few places, use std::fabs instead of std::abs or just abs 2025-05-02 10:21:04 -07:00
Gabriel Pinochet-Soto d46b421417 !!useAbs 2025-05-01 17:28:39 -07:00
Gabriel Pinochet-Soto 1d7029c5d6 Add Abs to DofToQuad, needs testing 2025-05-01 17:12:14 -07:00
Gabriel Pinochet-Soto 5596d38532 Docstring AbsPhyDer 2025-05-01 17:05:14 -07:00
Gabriel Pinochet-Soto 389580efef Replace mfem_error with MFEM_ABORT 2025-05-01 16:53:12 -07:00
Gabriel Pinochet-SotoandWill Pazner 2029636109 Update fem/bilinearform_ext.cpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-01 16:52:22 -07:00
Gabriel Pinochet-SotoandWill Pazner 1ca38f826c Update fem/bilinearform_ext.cpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-01 16:49:46 -07:00
Gabriel Pinochet-SotoandWill Pazner 4745e062f2 Docstring Array<T>::Abs
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-05-01 09:56:57 -07:00
Tzanio Kolev a631ab7e77 Merge branch 'master' into dev/abs-diag-smoothers 2025-05-01 09:46:00 -07:00
Tzanio Kolev 30f3e42232 Merge branch 'master' into fix-nurbsext-constructor 2025-04-30 10:49:45 -07:00
Justin Laughlin 4d92694fc5 Update GetNURBSPatches 2025-04-29 13:41:11 -07:00
Justin Laughlin a4214d22f4 fix typo 2025-04-28 18:40:02 -07:00
Justin Laughlin ce1576411c Cleanup demo script 2025-04-28 18:31:22 -07:00
Justin Laughlin 1c4f617fd5 Cleaup 2025-04-28 18:30:14 -07:00
Justin Laughlin b234475774 minor cleanup 2025-04-28 18:25:18 -07:00
Justin Laughlin 5da1d7ddf3 Update edge_to_knot (renamed edge_to_ukv) generator so it maps to unique knotvector indices 2025-04-28 18:18:18 -07:00
Justin Laughlin cff3c6cb6f Cleanup Mesh::LoadPatchTopo and Mesh::GetEdgeToKnotMapping 2025-04-28 16:32:56 -07:00
Justin Laughlin 7ef863b731 Add new algorithm to compute edge_to_knot map 2025-04-28 15:42:27 -07:00
Tzanio Kolev 09b31f0526 Merge branch 'master' into plbound 2025-04-26 12:32:38 -07:00
Gabriel Pinochet-Soto 5044d9cd45 Remove *Abs* functions in operator.xpp and related 2025-04-25 12:35:02 -07:00
Gabriel Pinochet-Soto 2edddb700e Remove unused functions in bilininteg.xpp 2025-04-25 12:21:08 -07:00
Gabriel Pinochet-Soto 1d79ab00ba Remove todo; cf. 3b49d70 2025-04-25 12:03:21 -07:00
Gabriel Pinochet-Soto 85e08b67a0 Update CHANGELOG 2025-04-25 11:55:04 -07:00
Gabriel Pinochet-Soto c513bb1276 Merge branch 'master' of github.com:mfem/mfem into dev/abs-diag-smoothers 2025-04-25 11:00:03 -07:00
Justin Laughlin 7559524573 separate GetEdgeToKnotMapping. Updating constructor WIP 2025-04-24 22:46:33 -07:00
Justin Laughlin 7ac34aab22 More testing - turns out mapping is more complicated because it is edges to unique knotvectors 2025-04-24 21:30:55 -07:00
Justin Laughlin 17edacb630 Fix orientation in edge_to_knot map of NURBSExtension constructor 2025-04-24 18:36:50 -07:00
Justin Laughlin 703ba47151 demo 2025-04-24 18:06:42 -07:00
Justin Laughlin 57caab145a Getters for NURBS patch data 2025-04-24 17:08:54 -07:00
Veselin Dobrev b294248b06 Update .gitignore 2025-04-22 13:08:44 -07:00
Stowell, Mark L. 83ca977d71 Adding an affine transformation option to mesh-explorer 2025-04-21 16:38:16 -07:00
Stowell, Mark L. 6739574668 Adding an affine mesh transformation coefficient 2025-04-21 16:37:54 -07:00
Ketan Mittal 9f153f9fcb Merge branch 'master' into plbound 2025-04-20 18:59:08 +12:00
Veselin Dobrev 00d35316b9 Merge branch 'master' into dev/abs-diag-smoothers 2025-04-18 13:43:42 -07:00
Mittal, Ketan 84a3fe000b fix gitignore 2025-04-18 13:32:57 -07:00
Stowell, Mark L. 7354e1ce6c Adding option to increase the space dimension of the mesh 2025-04-18 12:00:58 -07:00
Mittal, Ketan d5089996cc fix AddVertex usage 2025-04-18 11:55:13 -07:00
Mittal, Ketan 9787ad0d3b minor 2025-04-18 11:37:31 -07:00
Mittal, Ketan 26fb49960e add miniapp description 2025-04-18 11:12:32 -07:00
Mittal, Ketan 5cb2c82d59 fix makefile 2025-04-18 11:03:33 -07:00
Mittal, Ketan 9e7f8ce838 minor 2025-04-17 15:38:39 -07:00
Veselin Dobrev 41e4360d2e Fix the CUDA build and the out-of-source build with GNU make 2025-04-17 13:43:37 -07:00
Mittal, Ketan 263dd0f019 fix make clean 2025-04-17 12:11:02 -07:00
Mittal, Ketan 433f9a4c51 minor 2025-04-17 11:36:37 -07:00
Mittal, Ketan 08856158ce fix for some static constexpr definitions in hpp 2025-04-17 11:18:20 -07:00
Veselin Dobrev b21bf9a2d9 Merge branch 'master' into dev/abs-diag-smoothers 2025-04-16 22:22:56 -07:00
Veselin Dobrev 8d705f4230 Various formatting edits and other small tweaks 2025-04-16 22:21:33 -07:00
Mittal, Ketan 0a6c72c52d doxygen fix 2025-04-16 16:12:36 -07:00
Mittal, Ketan 8cefee8799 fix ParFESpace definition 2025-04-16 13:52:40 -07:00
Mittal, Ketan e9b631116f fix typo 2025-04-16 13:41:00 -07:00
Mittal, Ketan 6468880b2a minor 2025-04-16 12:58:07 -07:00
Mittal, Ketan 89a0e08f29 remove some unused methods 2025-04-16 12:57:50 -07:00
Mittal, Ketan 7471a22505 resolve conflicts 2025-04-16 12:56:40 -07:00
Mittal, Ketan 779ce337f4 miniapps 2025-04-16 12:45:55 -07:00
Mittal, Ketan 54b24da610 missing files 2025-04-16 12:45:30 -07:00
Mittal, Ketan 1a332939a0 initial capability 2025-04-16 12:45:11 -07:00
Veselin Dobrev bbd26e1835 In miniapps/diag-smoothers, fix the CMake tests and adjust mesh refinements 2025-04-14 16:41:24 -07:00
Veselin Dobrev e8842c506a Fix the build in miniapps/smoothers 2025-04-14 14:54:25 -07:00
Veselin Dobrev 214313291b Merge branch 'master' into dev/abs-diag-smoothers
Fixed conflicts:
  CHANGELOG
  fem/bilinearform_ext.cpp
  fem/bilinearform_ext.hpp
2025-04-14 13:56:38 -07:00
Gabriel Pinochet-Soto cfbaf5a6bf Header renaming 2025-03-29 11:02:27 -07:00
Gabriel Pinochet-Soto 2e2b8faba5 Modify abort msg in MMA::MMASubSvanberg 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto bf9ce2c6a4 Update gitignore and CHANGELOG 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 9f858378ca Add Examples 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto c2b948a036 AbsMult for Mass Integrs 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 0771f904c0 AbsMult for Diffusion 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 3b49d70f35 AbsMult for ElasticityInteg 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 7c077e656d CurlCurl kernels AbsApply 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto b9f146b46f Add AbsMult to QuadInterp 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 07438814ce Add interface in pfespace.hpp 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 328c914481 Add AbsMult for restriction operators; address code duplication comment 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 1766007d8a Add AbsMult interface to Integrators 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 6311548ac6 Use constexpr on (Abs)Mult cases 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 9d84c17e0b Add AbsMult to base class Operator 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 23c1fc6452 Add Hypre AbsMult 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 3117d0e8ab Implement Mult/AbsMult for DenseMatrix 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto a9b720b79d Update Mult Kernels
- Observation: This could be replaced with a lambda function of the
type `useAbs ? [](TA a) { return std::abs(a); } : ...`
2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 3fe878efaa Ommisions...
- Ommit hypre_parcsr.xpp implementation of L(p,q)
- Ommit solvers.xpp implementation of L(p,q)
- Ommit tests implementation of L(p,q)
2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 4c0def024c Monitor SLI (akin to CG) 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto bf7c26ebf9 Vector::Abs 2025-03-28 22:13:22 -07:00
Gabriel Pinochet-Soto 73014e34c5 Add Array<T>::Abs()
- static assert of arithmetic type of T
2025-03-28 22:13:22 -07:00
bslazarov 6507e421e2 ioip 2024-12-16 14:54:11 -08:00
bslazarov 7edb2ed7c4 hlkjlkjl 2024-12-05 15:13:58 -08:00
bslazarov 5d84aa5eb5 Merge branch 'hpcftools/flowSolver' of github.com:mfem/mfem into hpcftools/flowSolver 2024-12-04 14:18:58 -08:00
bslazarov 09988c9da3 ini stokes 2024-12-04 14:18:23 -08:00
Mathias Rainer Schmidt 74e4ad3e2c - updated flow solver
- added comments to Blf and Lf contributions
- split setup and step into vel, auxiliary and pressure part
2024-11-25 11:04:30 -08:00
Mathias Rainer Schmidt cf9fcd8dde Merge remote-tracking branch 'origin/master' into hpcftools/flowSolver 2024-11-14 13:52:27 -08:00
Mathias Rainer Schmidt fa8617ada3 - added partial assembly option 2024-11-01 13:20:29 -07:00
Mathias Rainer Schmidt f7e5db2cea - added ortho solver to phi field 2024-10-24 16:06:37 -07:00
Mathias Rainer Schmidt 60c11776b6 - added executable 2024-10-21 15:57:23 -07:00
Mathias Rainer Schmidt 6238f8ca76 - update solution 2024-10-16 16:00:08 -07:00
Mathias Rainer Schmidt 6a256db9aa - added linear solvers to step 2024-10-16 15:56:46 -07:00
Mathias Rainer Schmidt e0fb9658ca - added linear form integrators 2024-10-15 17:30:09 -07:00
Mathias Rainer Schmidt a1089efac3 - added BilinearForms 2024-10-15 13:46:36 -07:00
Mathias Rainer Schmidt 83f7f769dc - inital flow solver commit 2024-10-15 12:44:25 -07:00
90 changed files with 6915 additions and 953 deletions
+7
View File
@@ -232,6 +232,7 @@ miniapps/meshing/fit-node-position
miniapps/meshing/trimmer
miniapps/meshing/reflector
miniapps/meshing/ref321
miniapps/meshing/mesh-bounding-boxes
miniapps/meshing/mesh-optimizer
miniapps/meshing/pmesh-optimizer
miniapps/meshing/pmesh-fitting
@@ -262,6 +263,8 @@ miniapps/meshing/mesh.*
miniapps/meshing/order.*
miniapps/meshing/sol.*
miniapps/meshing/refined.mesh
miniapps/meshing/bounding-box*
miniapps/meshing/jacobian-determinant*
miniapps/mtop/parheat
miniapps/mtop/ParHeat*
@@ -336,6 +339,7 @@ miniapps/shifted/lsf_integral
miniapps/tools/display-basis
miniapps/tools/load-dc
miniapps/tools/convert-dc
miniapps/tools/gridfunction-bounds
miniapps/tools/lor-transfer
miniapps/tools/plor-transfer
miniapps/tools/get-values
@@ -402,6 +406,9 @@ miniapps/spde/ParaView
miniapps/tribol/contact-patch-test
miniapps/diag-smoothers/abs-l1-jacobi
miniapps/diag-smoothers/mg-abs-l1-jacobi
# Unit test binary and outputs
tests/unit/output_meshes
tests/unit/unit_tests
+10
View File
@@ -40,10 +40,20 @@ GPU computing
conditions. A new function Vector::SetSubVectorHost has been added in cases
where host execution is always needed (e.g. when the DOFs array is small).
New and updated examples and miniapps
-------------------------------------
- Added miniapps to demonstrate an implementation of the absolute-value
L(1)-Jacobi preconditioners in partially assembled operators. This includes
Multigrid wrapper to demonstrate the effectiveness of these Jacobi-type
operators as smoothers.
These miniapps can be found in `miniapps/diag-smoothers`.
API changes:
-----------
- mfem::internal::tensor and mfem::internal::dual have been moved to
mfem::future::tensor and mfem::future::dual.
- API addition: in class `Operator`, added virtual functions: `AbsMult`, and
`AbsMultTranspose`; in class `Vector`, added `Abs` and `Pow`.
Version 4.8, released on Apr 9, 2025
+1 -1
View File
@@ -115,7 +115,7 @@ vertices
nodes
FiniteElementSpace
FiniteElementCollection: Quadratic
FiniteElementCollection: H1_3D_P2
VDim: 3
Ordering: 0
+1 -1
View File
@@ -56,7 +56,7 @@ vertices
nodes
FiniteElementSpace
FiniteElementCollection: Quadratic
FiniteElementCollection: H1_3D_P2
VDim: 3
Ordering: 0
+1 -1
View File
@@ -227,7 +227,7 @@ vertices
nodes
FiniteElementSpace
FiniteElementCollection: Quadratic
FiniteElementCollection: H1_2D_P2
VDim: 2
Ordering: 0
+1 -1
View File
@@ -65,7 +65,7 @@ vertices
nodes
FiniteElementSpace
FiniteElementCollection: Quadratic
FiniteElementCollection: H1_2D_P2
VDim: 2
Ordering: 0
+2
View File
@@ -162,6 +162,7 @@ set(SRCS
transfer.cpp
hyperbolic.cpp
integrator.cpp
bounds.cpp
)
set(HDRS
@@ -272,6 +273,7 @@ set(HDRS
transfer.hpp
hyperbolic.hpp
integrator.hpp
bounds.hpp
)
if (MFEM_USE_SIDRE)
+208 -170
View File
@@ -78,7 +78,7 @@ void MFBilinearFormExtension::AssembleDiagonal(Vector &y) const
dynamic_cast<const ElementRestriction*>(elem_restrict);
if (H1elem_restrict)
{
H1elem_restrict->MultTransposeUnsigned(localY, y);
H1elem_restrict->AbsMultTranspose(localY, y);
}
else
{
@@ -456,7 +456,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
dynamic_cast<const ElementRestriction*>(elem_restrict);
if (H1elem_restrict)
{
H1elem_restrict->MultTransposeUnsigned(localY, y);
H1elem_restrict->AbsMultTranspose(localY, y);
}
else
{
@@ -491,7 +491,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
assemble_diagonal_with_markers(*bdr_integs[i], bdr_markers[i],
bdr_attributes, bdr_face_Y);
}
bdr_face_restrict_lex->AddMultTransposeUnsigned(bdr_face_Y, y);
bdr_face_restrict_lex->AddAbsMultTranspose(bdr_face_Y, y);
}
}
@@ -526,7 +526,8 @@ void PABilinearFormExtension::FormLinearSystem(const Array<int> &ess_tdof_list,
A.Reset(oper); // A will own oper
}
void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
const bool useAbs) const
{
Array<BilinearFormIntegrator*> &integrators = *a->GetDBFI();
@@ -558,11 +559,13 @@ void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
{
if (integrators[i]->Patchwise())
{
MFEM_ASSERT(!useAbs, "AbsMult not implemented with NURBS!")
integrators[i]->AddMultNURBSPA(x, y);
}
else
{
integrators[i]->AddMultPA(x, y);
if (useAbs) { integrators[i]->AddAbsMultPA(x, y); }
else { integrators[i]->AddMultPA(x, y); }
}
}
}
@@ -571,14 +574,30 @@ void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
if (iSz)
{
Array<Array<int>*> &elem_markers = *a->GetDBFI_Marker();
elem_restrict->Mult(x, localX);
auto H1elem_restrict =
dynamic_cast<const ElementRestriction*>(elem_restrict);
if (H1elem_restrict && useAbs)
{
H1elem_restrict->AbsMult(x, localX);
}
else
{
elem_restrict->Mult(x, localX);
}
localY = 0.0;
for (int i = 0; i < iSz; ++i)
{
AddMultWithMarkers(*integrators[i], localX, elem_markers[i],
elem_attributes, false, localY);
elem_attributes, false, localY, useAbs);
}
if (H1elem_restrict && useAbs)
{
H1elem_restrict->AbsMultTranspose(localY, y);
}
else
{
elem_restrict->MultTranspose(localY, y);
}
elem_restrict->MultTranspose(localY, y);
}
else
{
@@ -590,6 +609,7 @@ void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
const int iFISz = intFaceIntegrators.Size();
if (int_face_restrict_lex && iFISz>0)
{
MFEM_ASSERT(!useAbs, "AbsMult not implemented for face integrators!")
// When assembling interior face integrators for DG spaces, we need to
// exchange the face-neighbor information. This happens inside member
// functions of the 'int_face_restrict_lex'. To avoid repeated calls to
@@ -651,6 +671,7 @@ void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
const bool has_bdr_integs = (n_bdr_face_integs > 0 || n_bdr_integs > 0);
if (bdr_face_restrict_lex && has_bdr_integs)
{
MFEM_ASSERT(!useAbs, "AbsMult not implemented for bdr integrators!")
Array<Array<int>*> &bdr_markers = *a->GetBBFI_Marker();
Array<Array<int>*> &bdr_face_markers = *a->GetBFBFI_Marker();
bdr_face_restrict_lex->Mult(x, bdr_face_X);
@@ -828,22 +849,39 @@ void PABilinearFormExtension::AddMultWithMarkers(
const Array<int> *markers,
const Array<int> &attributes,
const bool transpose,
Vector &y) const
Vector &y,
const bool useAbs) const
{
if (markers)
{
tmp_evec.SetSize(y.Size());
tmp_evec = 0.0;
if (transpose) { integ.AddMultTransposePA(x, tmp_evec); }
else { integ.AddMultPA(x, tmp_evec); }
if (useAbs)
{
if (transpose) { integ.AddAbsMultTransposePA(x, tmp_evec); }
else { integ.AddAbsMultPA(x, tmp_evec); }
}
else
{
if (transpose) { integ.AddMultTransposePA(x, tmp_evec); }
else { integ.AddMultPA(x, tmp_evec); }
}
const int ne = attributes.Size();
const int nd = x.Size() / ne;
AddWithMarkers_(ne, nd, tmp_evec, *markers, attributes, y);
}
else
{
if (transpose) { integ.AddMultTransposePA(x, y); }
else { integ.AddMultPA(x, y); }
if (useAbs)
{
if (transpose) { integ.AddAbsMultTransposePA(x, y); }
else { integ.AddAbsMultPA(x, y); }
}
else
{
if (transpose) { integ.AddMultTransposePA(x, y); }
else { integ.AddMultPA(x, y); }
}
}
}
@@ -1010,8 +1048,13 @@ void EABilinearFormExtension::Assemble()
}
}
void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
void EABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
const bool useTranspose,
const bool useAbs) const
{
auto elemRest = dynamic_cast<const ElementRestriction*>(elem_restrict);
MFEM_ASSERT(useAbs?(elemRest!=nullptr):true,
"elem_restrict is not ElementRestriction*!")
// Apply the Element Restriction
const bool useRestrict = !DeviceCanUseCeed() && elem_restrict;
if (!useRestrict)
@@ -1019,6 +1062,11 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
y.UseDevice(true); // typically this is a large vector, so store on device
y = 0.0;
}
else if (useAbs)
{
elemRest->AbsMult(x, localX);
localY = 0.0;
}
else
{
elem_restrict->Mult(x, localX);
@@ -1026,25 +1074,55 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
}
// Apply the Element Matrices
{
Vector abs_ea_data;
if (useAbs)
{
abs_ea_data = ea_data;
abs_ea_data.Abs();
}
const int NDOFS = elemDofs;
auto X = Reshape(useRestrict?localX.Read():x.Read(), NDOFS, ne);
auto Y = Reshape(useRestrict?localY.ReadWrite():y.ReadWrite(), NDOFS, ne);
auto A = Reshape(ea_data.Read(), NDOFS, NDOFS, ne);
mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
auto A = Reshape(useAbs?abs_ea_data.Read():ea_data.Read(), NDOFS, NDOFS, ne);
if (!useTranspose)
{
const int e = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
res += A(i, j, e)*X(i, e);
}
Y(j, e) += res;
});
const int e = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A(i, j, e)*X(i, e);
}
Y(j, e) += res;
});
}
else
{
mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int e = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A(j, i, e)*X(i, e);
}
Y(j, e) += res;
});
}
// Apply the Element Restriction transposed
if (useRestrict)
{
elem_restrict->MultTranspose(localY, y);
if (useAbs)
{
elemRest->AbsMultTranspose(localY, y);
}
else
{
elem_restrict->MultTranspose(localY, y);
}
}
}
@@ -1053,6 +1131,7 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
const int iFISz = intFaceIntegrators.Size();
if (int_face_restrict_lex && iFISz>0)
{
MFEM_VERIFY(!useAbs, "AbsMult not implemented with Face integrators!")
// Apply the Interior Face Restriction
int_face_restrict_lex->Mult(x, int_face_X);
if (int_face_X.Size()>0)
@@ -1064,7 +1143,65 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
auto Y = Reshape(int_face_Y.ReadWrite(), NDOFS, 2, nf_int);
if (!factorize_face_terms)
{
auto A_int = Reshape(ea_data_int.Read(), NDOFS, NDOFS, 2, nf_int);
Vector abs_ea_data_int(ea_data_int.Size());
if (useAbs)
{
abs_ea_data_int = ea_data_int;
abs_ea_data_int.Abs();
}
auto A_int = Reshape(useAbs?abs_ea_data_int.Read():ea_data_int.Read(),
NDOFS, NDOFS, 2, nf_int);
if (!useTranspose)
{
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_int(i, j, 0, f)*X(i, 0, f);
}
Y(j, 0, f) += res;
res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_int(i, j, 1, f)*X(i, 1, f);
}
Y(j, 1, f) += res;
});
}
else
{
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_int(j, i, 0, f)*X(i, 0, f);
}
Y(j, 0, f) += res;
res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_int(j, i, 1, f)*X(i, 1, f);
}
Y(j, 1, f) += res;
});
}
}
Vector abs_ea_data_ext(ea_data_ext.Size());
if (useAbs)
{
abs_ea_data_ext = ea_data_ext;
abs_ea_data_ext.Abs();
}
auto A_ext = Reshape(useAbs?abs_ea_data_ext.Read():ea_data_ext.Read(),
NDOFS, NDOFS, 2, nf_int);
if (!useTranspose)
{
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int f = glob_j/NDOFS;
@@ -1072,35 +1209,37 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_int(i, j, 0, f)*X(i, 0, f);
res += A_ext(i, j, 0, f)*X(i, 0, f);
}
Y(j, 0, f) += res;
Y(j, 1, f) += res;
res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_int(i, j, 1, f)*X(i, 1, f);
res += A_ext(i, j, 1, f)*X(i, 1, f);
}
Y(j, 1, f) += res;
Y(j, 0, f) += res;
});
}
auto A_ext = Reshape(ea_data_ext.Read(), NDOFS, NDOFS, 2, nf_int);
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
else
{
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
res += A_ext(i, j, 0, f)*X(i, 0, f);
}
Y(j, 1, f) += res;
res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_ext(i, j, 1, f)*X(i, 1, f);
}
Y(j, 0, f) += res;
});
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_ext(j, i, 1, f)*X(i, 0, f);
}
Y(j, 1, f) += res;
res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_ext(j, i, 0, f)*X(i, 1, f);
}
Y(j, 0, f) += res;
});
}
// Apply the Interior Face Restriction transposed
int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y);
}
@@ -1109,7 +1248,9 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
// Treatment of boundary faces
if (!factorize_face_terms && bdr_face_restrict_lex && ea_data_bdr.Size() > 0)
{
MFEM_ASSERT(!useAbs, "AbsMult not implemented with Face integrators!")
// Apply the Boundary Face Restriction
// TODO: AbsMult if needed
bdr_face_restrict_lex->Mult(x, bdr_face_X);
bdr_face_Y = 0.0;
// Apply the boundary face matrices
@@ -1117,141 +1258,38 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
auto X = Reshape(bdr_face_X.Read(), NDOFS, nf_bdr);
auto Y = Reshape(bdr_face_Y.ReadWrite(), NDOFS, nf_bdr);
auto A = Reshape(ea_data_bdr.Read(), NDOFS, NDOFS, nf_bdr);
mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
if (!useTranspose)
{
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A(i, j, f)*X(i, f);
}
Y(j, f) += res;
});
// Apply the Boundary Face Restriction transposed
bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y);
}
}
void EABilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const
{
// Apply the Element Restriction
const bool useRestrict = !DeviceCanUseCeed() && elem_restrict;
if (!useRestrict)
{
y.UseDevice(true); // typically this is a large vector, so store on device
y = 0.0;
}
else
{
elem_restrict->Mult(x, localX);
localY = 0.0;
}
// Apply the Element Matrices transposed
{
const int NDOFS = elemDofs;
auto X = Reshape(useRestrict?localX.Read():x.Read(), NDOFS, ne);
auto Y = Reshape(useRestrict?localY.ReadWrite():y.ReadWrite(), NDOFS, ne);
auto A = Reshape(ea_data.Read(), NDOFS, NDOFS, ne);
mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int e = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A(j, i, e)*X(i, e);
}
Y(j, e) += res;
});
// Apply the Element Restriction transposed
if (useRestrict)
{
elem_restrict->MultTranspose(localY, y);
}
}
// Treatment of interior faces
Array<BilinearFormIntegrator*> &intFaceIntegrators = *a->GetFBFI();
const int iFISz = intFaceIntegrators.Size();
if (int_face_restrict_lex && iFISz>0)
{
// Apply the Interior Face Restriction
int_face_restrict_lex->Mult(x, int_face_X);
if (int_face_X.Size()>0)
{
int_face_Y = 0.0;
// Apply the interior face matrices transposed
const int NDOFS = faceDofs;
auto X = Reshape(int_face_X.Read(), NDOFS, 2, nf_int);
auto Y = Reshape(int_face_Y.ReadWrite(), NDOFS, 2, nf_int);
if (!factorize_face_terms)
{
auto A_int = Reshape(ea_data_int.Read(), NDOFS, NDOFS, 2, nf_int);
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_int(j, i, 0, f)*X(i, 0, f);
}
Y(j, 0, f) += res;
res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_int(j, i, 1, f)*X(i, 1, f);
}
Y(j, 1, f) += res;
});
}
auto A_ext = Reshape(ea_data_ext.Read(), NDOFS, NDOFS, 2, nf_int);
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
// TODO: useAbs
mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_ext(j, i, 1, f)*X(i, 0, f);
res += A(i, j, f)*X(i, f);
}
Y(j, 1, f) += res;
res = 0.0;
Y(j, f) += res;
});
}
else
{
// TODO: useAbs
mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A_ext(j, i, 0, f)*X(i, 1, f);
res += A(j, i, f)*X(i, f);
}
Y(j, 0, f) += res;
Y(j, f) += res;
});
// Apply the Interior Face Restriction transposed
int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y);
}
}
// Treatment of boundary faces
if (!factorize_face_terms && bdr_face_restrict_lex && ea_data_bdr.Size() > 0)
{
// Apply the Boundary Face Restriction
bdr_face_restrict_lex->Mult(x, bdr_face_X);
bdr_face_Y = 0.0;
// Apply the boundary face matrices transposed
const int NDOFS = faceDofs;
auto X = Reshape(bdr_face_X.Read(), NDOFS, nf_bdr);
auto Y = Reshape(bdr_face_Y.ReadWrite(), NDOFS, nf_bdr);
auto A = Reshape(ea_data_bdr.Read(), NDOFS, NDOFS, nf_bdr);
mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
{
const int f = glob_j/NDOFS;
const int j = glob_j%NDOFS;
real_t res = 0.0;
for (int i = 0; i < NDOFS; i++)
{
res += A(j, i, f)*X(i, f);
}
Y(j, f) += res;
});
// Apply the Boundary Face Restriction transposed
// TODO: AbsMultTranspose if needed
bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y);
}
}
@@ -1911,7 +1949,7 @@ void PAMixedBilinearFormExtension::AssembleDiagonal_ADAt(const Vector &D,
dynamic_cast<const ElementRestriction*>(elem_restrict_trial);
if (H1elem_restrict_trial)
{
H1elem_restrict_trial->MultUnsigned(D, localTrial);
H1elem_restrict_trial->AbsMult(D, localTrial);
}
else
{
@@ -1937,7 +1975,7 @@ void PAMixedBilinearFormExtension::AssembleDiagonal_ADAt(const Vector &D,
dynamic_cast<const ElementRestriction*>(elem_restrict_test);
if (H1elem_restrict_test)
{
H1elem_restrict_test->MultTransposeUnsigned(localTest, diag);
H1elem_restrict_test->AbsMultTranspose(localTest, diag);
}
else
{
@@ -1993,7 +2031,7 @@ void PADiscreteLinearOperatorExtension::Assemble()
dynamic_cast<const ElementRestriction*>(elem_restrict_test);
if (elem_restrict)
{
elem_restrict->MultTransposeUnsigned(ones, test_multiplicity);
elem_restrict->AbsMultTranspose(ones, test_multiplicity);
}
else
{
+22 -4
View File
@@ -91,12 +91,17 @@ public:
Vector &x, Vector &b,
OperatorHandle &A, Vector &X, Vector &B,
int copy_interior = 0) override;
void Mult(const Vector &x, Vector &y) const override;
void Mult(const Vector &x, Vector &y) const override
{ MultInternal(x,y); }
void AbsMult(const Vector &x, Vector &y) const override
{ MultInternal(x,y, true); }
void MultTranspose(const Vector &x, Vector &y) const override;
void Update() override;
protected:
void SetupRestrictionOperators(const L2FaceValues m);
void MultInternal(const Vector &x, Vector &y,
const bool useAbs = false) const;
/// @brief Accumulate the action (or transpose) of the integrator on @a x
/// into @a y, taking into account the (possibly null) @a markers array.
@@ -110,12 +115,14 @@ protected:
/// @param attributes Array of element or boundary element attributes.
/// @param transpose Compute the action or transpose of the integrator .
/// @param y Output E-vector
/// @param useAbs Apply absolute-value operator
void AddMultWithMarkers(const BilinearFormIntegrator &integ,
const Vector &x,
const Array<int> *markers,
const Array<int> &attributes,
const bool transpose,
Vector &y) const;
Vector &y,
const bool useAbs = false) const;
/// @brief Performs the same function as AddMultWithMarkers, but takes as
/// input and output face normal derivatives.
@@ -152,8 +159,15 @@ public:
EABilinearFormExtension(BilinearForm *form);
void Assemble() override;
void Mult(const Vector &x, Vector &y) const override;
void MultTranspose(const Vector &x, Vector &y) const override;
void Mult(const Vector &x, Vector &y) const override
{ MultInternal(x, y, false); }
void AbsMult(const Vector &x, Vector &y) const override
{ MultInternal(x, y, false, true); }
void MultTranspose(const Vector &x, Vector &y) const override
{ MultInternal(x, y, true); }
void AbsMultTranspose(const Vector &x, Vector &y) const override
{ MultInternal(x, y, true, true); }
/// @brief Populates @a element_matrices with the element matrices.
///
@@ -165,6 +179,10 @@ public:
void GetElementMatrices(DenseTensor &element_matrices,
ElementDofOrdering ordering,
bool add_bdr);
// This method needs to be public due to 'nvcc' restriction.
void MultInternal(const Vector &x, Vector &y, const bool useTranspose,
const bool useAbs = false) const;
};
/// Data and methods for fully-assembled bilinear forms
+29
View File
@@ -121,6 +121,12 @@ void BilinearFormIntegrator::AddMultPA(const Vector &, Vector &) const
" is not implemented for this class.");
}
void BilinearFormIntegrator::AddAbsMultPA(const Vector &, Vector &) const
{
MFEM_ABORT("BilinearFormIntegrator:AddAbsMultPA:(...)\n"
" is not implemented for this class.");
}
void BilinearFormIntegrator::AddMultNURBSPA(const Vector &, Vector &) const
{
MFEM_ABORT("BilinearFormIntegrator::AddMultNURBSPA(...)\n"
@@ -133,6 +139,13 @@ void BilinearFormIntegrator::AddMultTransposePA(const Vector &, Vector &) const
" is not implemented for this class.");
}
void BilinearFormIntegrator::AddAbsMultTransposePA(const Vector &,
Vector &) const
{
MFEM_ABORT("BilinearFormIntegrator::AddAbsMultTransposePA(...)\n"
" is not implemented for this class.");
}
void BilinearFormIntegrator::AssembleMF(const FiniteElementSpace &fes)
{
MFEM_ABORT("BilinearFormIntegrator::AssembleMF(...)\n"
@@ -418,6 +431,14 @@ void SumIntegrator::AddMultPA(const Vector& x, Vector& y) const
}
}
void SumIntegrator::AddAbsMultPA(const Vector& x, Vector& y) const
{
for (int i = 0; i < integrators.Size(); i++)
{
integrators[i]->AddAbsMultPA(x, y);
}
}
void SumIntegrator::AddMultTransposePA(const Vector &x, Vector &y) const
{
for (int i = 0; i < integrators.Size(); i++)
@@ -426,6 +447,14 @@ void SumIntegrator::AddMultTransposePA(const Vector &x, Vector &y) const
}
}
void SumIntegrator::AddAbsMultTransposePA(const Vector &x, Vector &y) const
{
for (int i = 0; i < integrators.Size(); i++)
{
integrators[i]->AddAbsMultTransposePA(x, y);
}
}
void SumIntegrator::AssembleMF(const FiniteElementSpace &fes)
{
for (int i = 0; i < integrators.Size(); i++)
+18
View File
@@ -78,6 +78,8 @@ public:
called. */
void AddMultPA(const Vector &x, Vector &y) const override;
virtual void AddAbsMultPA(const Vector &x, Vector &y) const;
/// Method for partially assembled action on NURBS patches.
virtual void AddMultNURBSPA(const Vector&x, Vector&y) const;
@@ -90,6 +92,8 @@ public:
called. */
virtual void AddMultTransposePA(const Vector &x, Vector &y) const;
virtual void AddAbsMultTransposePA(const Vector &x, Vector &y) const;
/// Method defining element assembly.
/** The result of the element assembly is added to the @a emat Vector if
@a add is true. Otherwise, if @a add is false, we set @a emat. */
@@ -496,8 +500,12 @@ public:
void AddMultTransposePA(const Vector &x, Vector &y) const override;
void AddAbsMultTransposePA(const Vector &x, Vector &y) const override;
void AddMultPA(const Vector& x, Vector& y) const override;
void AddAbsMultPA(const Vector& x, Vector& y) const override;
void AssembleMF(const FiniteElementSpace &fes) override;
void AddMultMF(const Vector &x, Vector &y) const override;
@@ -2320,8 +2328,12 @@ public:
void AddMultPA(const Vector&, Vector&) const override;
void AddAbsMultPA(const Vector&, Vector&) const override;
void AddMultTransposePA(const Vector&, Vector&) const override;
void AddAbsMultTransposePA(const Vector&, Vector&) const override;
void AddMultNURBSPA(const Vector&, Vector&) const override;
void AddMultPatchPA(const int patch, const Vector &x, Vector &y) const;
@@ -2419,8 +2431,12 @@ public:
void AddMultPA(const Vector&, Vector&) const override;
void AddAbsMultPA(const Vector&, Vector&) const override;
void AddMultTransposePA(const Vector&, Vector&) const override;
void AddAbsMultTransposePA(const Vector&, Vector&) const override;
static const IntegrationRule &GetRule(const FiniteElement &trial_fe,
const FiniteElement &test_fe,
const ElementTransformation &Trans);
@@ -2816,6 +2832,7 @@ public:
using BilinearFormIntegrator::AssemblePA;
void AssemblePA(const FiniteElementSpace &fes) override;
void AddMultPA(const Vector &x, Vector &y) const override;
void AddAbsMultPA(const Vector &x, Vector &y) const override;
void AssembleDiagonalPA(Vector& diag) override;
const Coefficient *GetCoefficient() const { return Q; }
@@ -2933,6 +2950,7 @@ public:
void AssemblePA(const FiniteElementSpace &trial_fes,
const FiniteElementSpace &test_fes) override;
void AddMultPA(const Vector &x, Vector &y) const override;
void AddAbsMultPA(const Vector &x, Vector &y) const override;
void AddMultTransposePA(const Vector &x, Vector &y) const override;
void AssembleDiagonalPA(Vector& diag) override;
void AssembleEA(const FiniteElementSpace &fes, Vector &emat,
+715
View File
@@ -0,0 +1,715 @@
// 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.
// Implementation of bounds
#include "bounds.hpp"
#include <limits>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
namespace mfem
{
using namespace std;
void PLBound::Setup(const int nb_i, const int ncp_i,
const int b_type_i, const int cp_type_i,
const real_t tol_i)
{
MFEM_VERIFY(b_type_i >= 0 && b_type_i <= 2, "Bases not supported. "
"Please read class description to see supported types.");
MFEM_VERIFY(cp_type_i == 0 || cp_type_i == 1,
"Control point type not supported. Please read class "
"description to see supported types.");
nb = nb_i;
ncp = ncp_i;
b_type = b_type_i;
cp_type = cp_type_i;
tol = tol_i;
lbound.SetSize(nb, ncp);
ubound.SetSize(nb, ncp);
nodes.SetSize(nb);
weights.SetSize(nb);
control_points.SetSize(ncp);
auto scalenodes = [](const Vector &in, const real_t a, const real_t b) -> Vector
{
Vector outVec(in.Size());
real_t maxv = in.Max();
real_t minv = in.Min();
for (int i = 0; i < in.Size(); i++)
{
outVec(i) = a + (b-a)*(in(i)-minv)/(maxv-minv);
}
return outVec;
};
MFEM_VERIFY(ncp >= 2,"At least 2 control points are required.");
if (cp_type == 0) // GL + End Point
{
control_points(0) = 0.0;
control_points(ncp-1) = 1.0;
if (ncp > 2)
{
const real_t *x = poly1d.GetPoints(ncp-3, 0);
MFEM_VERIFY(x, "Error in getting points.");
for (int i = 0; i < ncp-2; i++)
{
control_points(i+1) = x[i];
}
}
}
else if (cp_type == 1) // Chebyshev
{
auto GetChebyshevNodes = [](int n) -> Vector
{
Vector cheb(n);
for (int i = 0; i < n; ++i)
{
cheb(i) = -cos(M_PI * (static_cast<real_t>(i) / (n - 1)));
}
return cheb;
};
control_points = GetChebyshevNodes(ncp);
}
else
{
MFEM_ABORT("Unsupported interval points. Use [0,1].\n");
}
control_points = scalenodes(control_points, 0.0, 1.0); // rescale to [0,1]
Poly_1D::Basis &basis1d(poly1d.GetBasis(nb-1, b_type));
// Initialize bounds
lbound = 0.0;
ubound = 0.0;
Vector bmv(nb), bpv(nb), bv(nb); // basis values
Vector bdmv(nb), bdpv(nb), bdv(nb); // basis derivative values
Vector vals(3);
// See Section 3.1.1 of https://arxiv.org/pdf/2501.12349 for explanation of
// procedure below.
for (int j = 0; j < ncp; j++)
{
real_t x = control_points(j);
real_t xm = x;
if (j != 0)
{
xm = 0.5*(control_points(j-1)+control_points(j));
}
real_t xp = x;
if (j != ncp-1)
{
xp = 0.5*(control_points(j)+control_points(j+1));
}
basis1d.Eval(xm, bmv, bdmv);
basis1d.Eval(xp, bpv, bdpv);
basis1d.Eval(x, bv);
real_t dm = x-xm;
real_t dp = x-xp;
for (int i = 0; i < nb; i++)
{
if (j == 0)
{
lbound(i, j) = bv(i);
ubound(i, j) = bv(i);
}
else if (j == ncp-1)
{
lbound(i, j) = bv(i);
ubound(i, j) = bv(i);
}
else
{
vals(0) = bv(i);
vals(1) = bmv(i) + dm*bdmv(i);
vals(2) = bpv(i) + dp*bdpv(i);
lbound(i, j) = vals.Min()-tol; // tolerance for good measure
ubound(i, j) = vals.Max()+tol; // tolerance for good measure
}
}
}
IntegrationRule irule(nb);
if (b_type == 0)
{
QuadratureFunctions1D::GaussLegendre(nb, &irule);
for (int i = 0; i < nb; i++)
{
weights(i) = irule.IntPoint(i).weight;
nodes(i) = irule.IntPoint(i).x;
}
}
else if (b_type == 1)
{
QuadratureFunctions1D::GaussLobatto(nb, &irule);
for (int i = 0; i < nb; i++)
{
weights(i) = irule.IntPoint(i).weight;
nodes(i) = irule.IntPoint(i).x;
}
}
else if (b_type == 2)
{
QuadratureFunctions1D::ClosedUniform(nb, &irule);
for (int i = 0; i < nb; i++)
{
weights(i) = irule.IntPoint(i).weight;
nodes(i) = irule.IntPoint(i).x;
}
}
if (b_type == 2)
{
nodes_int.SetSize(nb);
weights_int.SetSize(nb);
IntegrationRule irule_int(nb);
{
QuadratureFunctions1D::GaussLobatto(nb, &irule_int);
for (int i = 0; i < nb; i++)
{
weights_int(i) = irule_int.IntPoint(i).weight;
nodes_int(i) = irule_int.IntPoint(i).x;
}
}
SetupBernsteinBasisMat(basisMatNodes, nodes);
// Setup memory for lu factors
basisMatLU = basisMatNodes;
lu_ip.SetSize(nb);
// Compute lu factors
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
bool factor = lu.Factor(nb);
MFEM_VERIFY(factor,"Failure in LU factorization in PLBound.");
// Setup the Bernstein basis matrix for the GLL integration points. This
// is used to compute linear fit.
SetupBernsteinBasisMat(basisMatInt, nodes_int);
}
else
{
nodes_int.SetDataAndSize(nodes.GetData(), nb);
weights_int.SetDataAndSize(weights.GetData(), nb);
}
}
PLBound::PLBound(FiniteElementSpace *fes, int ncp_i, int cp_type_i)
{
MFEM_VERIFY(!fes->IsVariableOrder(),
"Variable order meshes not yet supported.");
const char *name = fes->FEColl()->Name();
string cname = name;
cp_type = cp_type_i;
b_type = BasisType::Invalid;
nb = fes->GetMaxElementOrder()+1;
tol = 0.0;
int minncp = 2;
if (nb > 12)
{
minncp = 2*nb;
}
else if (!strncmp(name, "H1_", 3) && strncmp(name, "H1_Trace_", 9))
{
// H1 GLL
b_type = BasisType::GaussLobatto;
minncp = min_ncp_gll_x[cp_type][nb-2];
}
else if (!strncmp(name, "H1Pos_", 6) && strncmp(name, "H1Pos_Trace_", 12))
{
// H1 Positive
b_type = BasisType::Positive;
minncp = min_ncp_pos_x[cp_type][nb-2];
}
else if (!strncmp(name, "L2_", 3) && strncmp(name, "L2_T", 4))
{
// L2 Gauss-Legendre
b_type = BasisType::GaussLegendre;
minncp = min_ncp_gl_x[cp_type][nb-2];
}
else if (!strncmp(name, "L2_T1", 5))
{
// L2 GLL
b_type = BasisType::GaussLobatto;
minncp = min_ncp_gll_x[cp_type][nb-2];
}
else if (!strncmp(name, "L2_T2", 5))
{
// L2 Positive
b_type = BasisType::Positive;
minncp = min_ncp_pos_x[cp_type][nb-2];
}
else
{
MFEM_ABORT("Only H1 GLL/Positive & L2 GL/GLL/Positive bases supported.");
}
ncp = std::max(minncp, ncp_i);
Setup(nb, ncp, b_type, cp_type, tol);
}
void PLBound::Get1DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const
{
real_t x,w;
intmin.SetSize(ncp);
intmax.SetSize(ncp);
intmin = 0.0;
intmax = 0.0;
Vector coeffm(nb);
coeffm = 0.0;
real_t a0 = 0.0;
real_t a1 = 0.0;
Vector nodal_vals, nodal_integ_vals;
if (b_type == 2) // compute values at equispaced nodes and GLL nodes
{
nodal_vals.SetSize(nb);
nodal_integ_vals.SetSize(nb);
Vector shape(nb);
for (int i = 0; i < nb; i++)
{
basisMatNodes.GetRow(i, shape);
nodal_vals(i) = shape*coeff;
basisMatInt.GetRow(i, shape);
nodal_integ_vals(i) = shape*coeff;
}
}
else
{
nodal_vals.SetDataAndSize(coeff.GetData(), nb);
nodal_integ_vals.SetDataAndSize(coeff.GetData(), nb);
}
// compute L2 projection for linear bases: a0 + a1*x
if (proj)
{
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes_int(i)-1;
w = 2.0*weights_int(i);
a0 += 0.5*nodal_integ_vals(i)*w;
a1 += 1.5*nodal_integ_vals(i)*w*x;
}
// offset the linear fit from nodal values
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes(i)-1;
coeffm(i) = nodal_vals(i) - a0 - a1*x;
}
// compute coefficients for Bernstein
if (b_type == 2)
{
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
lu.Solve(nb, 1, coeffm.GetData());
}
// initialize the bounds to be the linear fit
for (int j = 0; j < ncp; j++)
{
x = 2.0*control_points(j)-1;
intmin(j) = a0 + a1*x;
intmax(j) = intmin(j);
}
}
else
{
coeffm.SetDataAndSize(coeff.GetData(), nb);
}
for (int i = 0; i < nb; i++)
{
real_t c = coeffm(i);
for (int j = 0; j < ncp; j++)
{
intmin(j) += min(lbound(i,j)*c, ubound(i,j)*c);
intmax(j) += max(lbound(i,j)*c, ubound(i,j)*c);
}
}
}
void PLBound::Get2DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const
{
intmin.SetSize(ncp*ncp);
intmax.SetSize(ncp*ncp);
intmin = 0.0;
intmax = 0.0;
Vector intminT(ncp*nb);
Vector intmaxT(ncp*nb);
// Get bounds for each row of the solution
for (int i = 0; i < nb; i++)
{
Vector solcoeff(coeff.GetData()+i*nb, nb);
Vector intminrow(intminT.GetData()+i*ncp, ncp);
Vector intmaxrow(intmaxT.GetData()+i*ncp, ncp);
Get1DBounds(solcoeff, intminrow, intmaxrow);
}
Vector intminT2 = intminT;
// Compute a0 and a1 for each column of nodes
Vector a0V(ncp), a1V(ncp);
a0V = 0.0;
a1V = 0.0;
real_t x,w,t;
if (proj)
{
if (b_type == 2)
{
// Note: DenseMatrix uses column-major ordering so we will need to
// transpose the matrix.
DenseMatrix intminTM(intminT.GetData(), ncp, nb),
intmaxTM(intmaxT.GetData(), ncp, nb),
intmeanTM(ncp, nb);
DenseMatrix minvalsM(nb, ncp), maxvalsM(nb, ncp), meanintvalsM(nb, ncp);
MultABt(basisMatNodes, intminTM, minvalsM);
MultABt(basisMatNodes, intmaxTM, maxvalsM);
intmeanTM = intminTM;
intmeanTM += intmaxTM;
intmeanTM *= 0.5;
MultABt(basisMatInt, intmeanTM, meanintvalsM);
// Compute the linear fit along each column and then offset it from
// the bounds on the coefficient.
// Note: Since Bernstein bases are positive, we can use the lower
// bounds to compute the lower bounding polynomial and subtract the
// linear fit before finding the Bernstein coefficients corresponding
// to the perturbation. Same for upper bounds. If the bases were not
// always positive, it is not yet clear if the perturbation
// coefficients will be this straightforward to compute.
for (int j = 0; j < ncp; j++) // row of interval points
{
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes_int(i)-1; // x-coordinate
w = 2.0*weights_int(i); // weight
t = meanintvalsM(i,j);
a0V(j) += 0.5*t*w;
a1V(j) += 1.5*t*w*x;
}
// Offset linear fit
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes(i)-1; // x-coordinate
minvalsM(i,j) -= a0V(j) + a1V(j)*x;
maxvalsM(i,j) -= a0V(j) + a1V(j)*x;
}
// Compute Bernstein coefficients
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
lu.Solve(nb, 1, minvalsM.GetColumn(j));
lu.Solve(nb, 1, maxvalsM.GetColumn(j));
for (int i = 0; i < nb; i++)
{
intminT(i*ncp+j) = minvalsM(i,j);
intmaxT(i*ncp+j) = maxvalsM(i,j);
}
}
}
else
{
for (int j = 0; j < nb; j++) // row of nodes
{
x = 2.0*nodes(j)-1; // x-coordinate
w = 2.0*weights(j); // weight
for (int i = 0; i < ncp; i++) // column of interval points
{
t = 0.5*(intminT(j*ncp+i)+intmaxT(j*ncp+i));
a0V(i) += 0.5*t*w;
a1V(i) += 1.5*t*w*x;
}
}
// offset the linear fit from nodal values
for (int j = 0; j < nb; j++) // row of nodes
{
x = 2.0*nodes(j)-1; // x-coordinate
for (int i = 0; i < ncp; i++) // column of interval points
{
t = a0V(i) + a1V(i)*x;
intminT(j*ncp+i) -= t;
intmaxT(j*ncp+i) -= t;
}
}
}
// Initialize bounds using a0 and a1 values
for (int j = 0; j < ncp; j++) // row j
{
x = 2.0*control_points(j)-1;
for (int i = 0; i < ncp; i++) // column i
{
intmin(j*ncp+i) = a0V(i) + a1V(i)*x;
intmax(j*ncp+i) = intmin(j*ncp+i);
}
}
}
// Compute bounds
int id1 = 0, id2 = 0;
Vector vals(4);
for (int j = 0; j < nb; j++)
{
for (int i = 0; i < ncp; i++) // ith column
{
real_t w0 = intminT(id1++);
real_t w1 = intmaxT(id2++);
for (int k = 0; k < ncp; k++) // kth row
{
vals(0) = w0*lbound(j,k);
vals(1) = w0*ubound(j,k);
vals(2) = w1*lbound(j,k);
vals(3) = w1*ubound(j,k);
intmin(k*ncp+i) += vals.Min();
intmax(k*ncp+i) += vals.Max();
}
}
}
}
void PLBound::Get3DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const
{
int nb2 = nb*nb,
ncp2 = ncp*ncp,
ncp3 = ncp*ncp*ncp;
intmin.SetSize(ncp3);
intmax.SetSize(ncp3);
intmin = 0.0;
intmax = 0.0;
Vector intminT(ncp2*nb);
Vector intmaxT(ncp2*nb);
// Get bounds for each slice of the solution
for (int i = 0; i < nb; i++)
{
Vector solcoeff(coeff.GetData()+i*nb2, nb2);
Vector intminrow(intminT.GetData()+i*ncp2, ncp2);
Vector intmaxrow(intmaxT.GetData()+i*ncp2, ncp2);
Get2DBounds(solcoeff, intminrow, intmaxrow);
}
DenseMatrix intminTM(intminT.GetData(), ncp2, nb),
intmaxTM(intmaxT.GetData(), ncp2, nb);
// Compute a0 and a1 for each tower of nodes
Vector a0V(ncp2), a1V(ncp2);
a0V = 0.0;
a1V = 0.0;
real_t x,w,t;
if (proj)
{
if (b_type == 2) // Bernstein bases
{
// Compute the mean coefficients along each tower.
for (int j = 0; j < ncp2; j++) // slice of interval points
{
Vector meanBounds(nb), minBounds(nb), maxBounds(nb);
intminTM.GetRow(j, minBounds);
intmaxTM.GetRow(j, maxBounds);
for (int i = 0; i < nb; i++) // column of nodes
{
meanBounds(i) = 0.5*(minBounds(i)+maxBounds(i));
}
Vector meanNodalIntVals(nb);
Vector minNodalVals(nb);
Vector maxNodalVals(nb);
Vector row(nb);
for (int i = 0; i < nb; i++)
{
basisMatNodes.GetRow(i, row);
minNodalVals(i) = row*minBounds;
maxNodalVals(i) = row*maxBounds;
basisMatInt.GetRow(i, row);
meanNodalIntVals(i) = row*meanBounds;
}
// linear fit along each tower
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes_int(i)-1; // x-coordinate
w = 2.0*weights_int(i); // weight
a0V(j) += 0.5*meanNodalIntVals(i)*w;
a1V(j) += 1.5*meanNodalIntVals(i)*w*x;
}
// offset the linear fit from bounding coefficients
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes(i)-1; // x-coordinate
minBounds(i) -= a0V(j) + a1V(j)*x;
maxBounds(i) -= a0V(j) + a1V(j)*x;
}
// Compute Bernstein coefficients
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
lu.Solve(nb, 1, minBounds.GetData());
lu.Solve(nb, 1, maxBounds.GetData());
for (int i = 0; i < nb; i++)
{
intminT(i*ncp2+j) = minBounds(i);
intmaxT(i*ncp2+j) = maxBounds(i);
}
}
}
else
{
// nodal bases
for (int j = 0; j < nb; j++) // tower of nodes
{
x = 2.0*nodes(j)-1; // x-coordinate
w = 2.0*weights(j); // weight
for (int i = 0; i < ncp2; i++) // slice of interval points
{
t = 0.5*(intminT(j*ncp2+i)+intmaxT(j*ncp2+i));
a0V(i) += 0.5*t*w;
a1V(i) += 1.5*t*w*x;
}
}
// offset the linear fit from nodal values
for (int j = 0; j < nb; j++) // row of nodes
{
x = 2.0*nodes(j)-1; // x-coordinate
for (int i = 0; i < ncp2; i++) // column of interval points
{
t = a0V(i) + a1V(i)*x;
intminT(j*ncp2+i) -= t;
intmaxT(j*ncp2+i) -= t;
}
}
}
// Initialize bounds using a0 and a1 values
for (int j = 0; j < ncp; j++) // slice j
{
x = 2.0*control_points(j)-1;
for (int i = 0; i < ncp2; i++) // tower i
{
intmin(j*ncp2+i) = a0V(i) + a1V(i)*x;
intmax(j*ncp2+i) = a0V(i) + a1V(i)*x;
}
}
}
// Compute bounds
int id1 = 0, id2 = 0;
Vector vals(4);
for (int j = 0; j < nb; j++)
{
for (int i = 0; i < ncp2; i++) // ith tower
{
real_t w0 = intminT(id1++);
real_t w1 = intmaxT(id2++);
for (int k = 0; k < ncp; k++) // kth slice
{
vals(0) = w0*lbound(j,k);
vals(1) = w0*ubound(j,k);
vals(2) = w1*lbound(j,k);
vals(3) = w1*ubound(j,k);
intmin(k*ncp2+i) += vals.Min();
intmax(k*ncp2+i) += vals.Max();
}
}
}
}
void PLBound::GetNDBounds(int rdim, Vector &coeff,
Vector &intmin, Vector &intmax) const
{
if (rdim == 1)
{
Get1DBounds(coeff, intmin, intmax);
}
else if (rdim == 2)
{
Get2DBounds(coeff, intmin, intmax);
}
else if (rdim == 3)
{
Get3DBounds(coeff, intmin, intmax);
}
else
{
MFEM_ABORT("Currently not supported.");
}
}
void PLBound::SetupBernsteinBasisMat(DenseMatrix &basisMat,
Vector &nodesBern) const
{
const int nbern = nodesBern.Size();
L2_SegmentElement el(nbern-1, 2); // we use L2 to leverage lexicographic order
Array<int> ordering = el.GetLexicographicOrdering();
basisMat.SetSize(nbern, nbern);
Vector shape(nbern);
IntegrationPoint ip;
for (int i = 0; i < nbern; i++)
{
ip.x = nodesBern(i);
el.CalcShape(ip, shape);
basisMat.SetRow(i, shape);
}
}
constexpr int PLBound::min_ncp_gl_x[2][11];
constexpr int PLBound::min_ncp_gll_x[2][11];
constexpr int PLBound::min_ncp_pos_x[2][11];
int PLBound::GetMinimumPointsForGivenBases(int nb_i, int b_type_i,
int cp_type_i) const
{
MFEM_VERIFY(b_type_i >= 0 && b_type_i <= 2, "Invalid node type. Specify 0 "
"for GL, 1 for GLL, and 2 for positive " "bases.");
MFEM_VERIFY(cp_type_i == 0 || cp_type_i == 1, "Invalid control point type. "
"Specify 0 for GL+end points, 1 for Chebyshev.");
if (nb_i > 12)
{
MFEM_ABORT("GetMinimumPointsForGivenBases can only be used for maximum "
"order = 11, i.e. nb=12. 2*nb points should be sufficient to "
"bound the bases up to nb = 30.");
}
else if (b_type_i == 0)
{
return min_ncp_gl_x[cp_type_i][nb_i-2];
}
else if (b_type_i == 1)
{
return min_ncp_gll_x[cp_type_i][nb_i-2];
}
else if (b_type_i == 2)
{
return min_ncp_pos_x[cp_type_i][nb_i-2];
}
return 0;
}
void PLBound::Print(std::ostream &outp) const
{
outp << "PLBound nb: " << nb << std::endl;
outp << "PLBound ncp: " << ncp << std::endl;
outp << "PLBound b_type: " << b_type << std::endl;
outp << "PLBound cp_type: " << cp_type << std::endl;
outp << "Print nodes: " << std::endl;
nodes.Print(outp);
outp << "Print weights: " << std::endl;
weights.Print(outp);
outp << "Print control_points: " << std::endl;
control_points.Print(outp);
outp << "Print lower bounds: " << std::endl;
lbound.Print(outp);
outp << "Print upper bounds: " << std::endl;
ubound.Print(outp);
}
}
+136
View File
@@ -0,0 +1,136 @@
// 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_BOUND
#define MFEM_BOUND
#include "../config/config.hpp"
#include "fespace.hpp"
namespace mfem
{
/** @name Piecewise linear bounds of bases
\brief Piecewise linear bounds of bases can be used to compute bounds on the grid function in each element. The bounds for the bases are constructed based on the following parameters:
(i) @b nb: number of bases/nodes in 1D (i.e. polynomial order+1),
(ii) @b b_type: bases type, 0 - Lagrange interpolants on Gauss-Legendre nodes, 1 - Lagrange interpolants on Gauss-Lobatto-Legendre nodes, and
2 - Positive/Bernstein bases on uniformly distributed nodes,
(iii) @b ncp: number of control points used to construct the piecewise linear bounds
(iv) @b cp_type: control point distribution. 0 - GL + end-points,
1 - Chebyshev.
Note: @b nb and @b b_type are inferred directly from the grid-function.
If the user does not specify @b ncp and @b cp_type, the minimum value of
@b ncp is used that would bound the bases for the @b cp_type. We default
to @b cp_type = 0 as it requires fewer number of points to bound the bases. Typically, @b ncp = 2 @b nb is sufficient to get fairly compact bounds, and increasing @b ncp results in tighter bounds.
Finally, only tensor-product elements are currently supported.
For more technical details see:
Mittal et al., "General Field Evaluation in High-Order Meshes on GPUs" &
Dzanic et al., "A method for bounding high-order finite element
functions: Applications to mesh validity and bounds-preserving limiters".
*/
class PLBound
{
private:
int nb; // #mesh nodes in 1D
int ncp; // #control points in 1D
int b_type; // bases type: 0 - GL, 1 - GLL, 2 - Bernstein
int cp_type; // control points type: 0 - GL+Ends, 1 - Chebyshev
bool proj = true; // Use linear projection to compute bounds.
real_t tol = 0.0; // offset bounds to avoid round-off errors
Vector nodes, weights, control_points;
DenseMatrix lbound, ubound; // nb x ncp matrices with bounds of all bases
// Some auxillary storage for computing the bounds with Bernstein
DenseMatrix basisMatNodes; // Bernstein bases at equispaced nodes
DenseMatrix basisMatInt; // Bernstein bases at GLL nodes
Vector nodes_int, weights_int; // Integration nodes and weights
DenseMatrix basisMatLU; // Used to compute LU factors for Bernstein
mutable Array<int> lu_ip;
// stores min_ncp for nb = 2..12 for Lagrange interpolants on GL nodes
// with GL+end points and Chebyshev points as control points
static constexpr int min_ncp_gl_x[2][11]= {{3,5,6,8,9,10,11,11,12,13,14},
{3,5,8,9,11,12,14,15,17,18,20}
};
// stores min_ncp for nb = 2..12 for Lagrange interpolants on GLL nodes
// with GL+end points and Chebyshev points as control points
static constexpr int min_ncp_gll_x[2][11]= {{3,5,7,8,9,10,12,13,14,15,16},
{3,5,8,10,12,13,15,17,19,21,22}
};
// stores min_ncp for nb = 2..12 for Bernstein bases with GL+end points
// and Chebyshev points as control points
static constexpr int min_ncp_pos_x[2][11]= {{3,5,7,8,8,9,10,10,11,12,13},
{3,5,8,9,11,12,13,13,14,15,16}
};
public:
// Constructor
PLBound(const int nb_i, const int ncp_i, const int b_type_i,
const int cp_type_i, const real_t tol_i)
{
Setup(nb_i, ncp_i, b_type_i, cp_type_i, tol_i);
}
// Constructor
PLBound(FiniteElementSpace *fes, int ncp_i = -1, int cp_type_i = 0);
// Get minimum number of control points needed to bound the given bases
int GetMinimumPointsForGivenBases(int nb_i, int b_type_i,
int cp_type_i) const;
// Print information about the bounds
void Print(std::ostream &outp = mfem::out) const;
// Enable (default) or disable linear projection before bounding.
// This projection increases the computational cost but results in tighter
// bounds.
void SetProjectionFlagForBounding(bool proj_) { proj = proj_; }
/// Compute piecewise linear bounds for the lexicographically-ordered
/// coefficients in @a coeff in 1D/2D/3D.
void GetNDBounds(int rdim, Vector &coeff,
Vector &intmin, Vector &intmax) const;
/// Get number of control points used to compute the bounds.
int GetNControlPoints() const { return ncp; }
private:
/// Compute piecewise linear bounds for the lexicographically-ordered
/// coefficients in @a coeff in 1D.
void Get1DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const;
/// Compute piecewise linear bounds for the lexicographically-ordered
/// coefficients in @a coeff in 2D.
void Get2DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const;
/// Compute piecewise linear bounds for the lexicographically-ordered
/// coefficients in @a coeff in 3D.
void Get3DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const;
/// Setup matrix used to compute values at given 1D locations in [0,1]
/// for Bernstein bases.
void SetupBernsteinBasisMat(DenseMatrix &basisMat, Vector &nodesBern) const;
void Setup(const int nb_i, const int ncp_i, const int b_type_i,
const int cp_type_i, const real_t tol_i);
};
} // namespace mfem
#endif // MFEM_BOUND
+10
View File
@@ -20,6 +20,16 @@ namespace mfem
using namespace std;
DofToQuad DofToQuad::Abs() const
{
DofToQuad d2q(*this);
d2q.B.Abs();
d2q.Bt.Abs();
d2q.G.Abs();
d2q.Gt.Abs();
return d2q;
}
FiniteElement::FiniteElement(int D, Geometry::Type G,
int Do, int O, int F)
: Nodes(Do)
+3
View File
@@ -219,6 +219,9 @@ public:
- #ndof x #nqpt, for H(div) vector elements, or
- #ndof x #nqpt x cdim, for H(curl) vector elements. */
Array<real_t> Gt;
/// Returns absolute value of the maps
DofToQuad Abs() const;
};
/// Describes the function space on each element
+1
View File
@@ -49,6 +49,7 @@
#include "lor/lor.hpp"
#include "dgmassinv.hpp"
#include "hyperbolic.hpp"
#include "bounds.hpp"
#include "dfem/doperator.hpp"
-3
View File
@@ -4278,9 +4278,6 @@ void FiniteElementSpace::Update(bool want_transform)
void FiniteElementSpace::PRefineAndUpdate(const Array<pRefinement> & refs,
bool want_transfer)
{
MFEM_VERIFY(PRefinementSupported(),
"p-refinement is not supported in this space");
if (want_transfer)
{
fesPrev.reset(new FiniteElementSpace(mesh, fec, vdim, ordering));
+131
View File
@@ -4563,4 +4563,135 @@ GridFunction *Extrude1DGridFunction(Mesh *mesh, Mesh *mesh2d,
return sol2d;
}
void GridFunction::GetElementBoundsAtControlPoints(const int elem,
const PLBound &plb,
Vector &lower, Vector &upper,
const int vdim)
{
const FiniteElement *fe = fes->GetFE(elem);
int fes_dim = fes->GetVDim();
int rdim = fe->GetDim();
const TensorBasisElement *tbe =
dynamic_cast<const TensorBasisElement *>(fe);
MFEM_VERIFY(tbe != NULL, "TensorBasis FiniteElement expected.");
const Array<int> &dof_map = tbe->GetDofMap();
Vector loc_data;
Array<int> dof_idx;
fes->GetElementDofs(elem, dof_idx);
int ndofs = dof_idx.Size();
int n_c_pts = std::pow(plb.GetNControlPoints(), rdim);
lower.SetSize(n_c_pts*(vdim > 0 ? 1 : fes_dim));
upper.SetSize(n_c_pts*(vdim > 0 ? 1 : fes_dim));
for (int d = 0; d < fes_dim; d++)
{
if (vdim > 0 && d != vdim-1) { continue; }
const int d_off = vdim > 0 ? 0 : d;
Array<int> dof_idx_c = dof_idx;
Vector lowerT(lower, d_off*n_c_pts, n_c_pts);
Vector upperT(upper, d_off*n_c_pts, n_c_pts);
fes->DofsToVDofs(vdim > 0 ? vdim-1 : d, dof_idx_c);
GetSubVector(dof_idx_c, loc_data);
Vector nodal_data;
if (dof_map.Size() == 0)
{
nodal_data.SetDataAndSize(loc_data.GetData(), ndofs);
}
else
{
nodal_data.SetSize(ndofs);
for (int j = 0; j < ndofs; j++)
{
nodal_data(j) = loc_data(dof_map[j]);
}
}
plb.GetNDBounds(rdim, nodal_data, lowerT, upperT);
}
}
void GridFunction::GetElementBounds(const int elem, const PLBound &plb,
Vector &lower, Vector &upper,
const int vdim)
{
Vector lowerC, upperC;
GetElementBoundsAtControlPoints(elem, plb, lowerC, upperC, vdim);
const FiniteElement *fe = fes->GetFE(elem);
int rdim = fe->GetDim();
int n_c_pts = std::pow(plb.GetNControlPoints(), rdim);
int fes_dim = fes->GetVDim();
lower.SetSize((vdim > 0 ? 1 :fes_dim));
upper.SetSize((vdim > 0 ? 1 :fes_dim));
for (int d = 0; d < fes_dim; d++)
{
if (vdim > 0 && d != vdim-1) { continue; }
const int d_off = vdim > 0 ? 0 : d;
Vector lowerT(lowerC, d_off*n_c_pts, n_c_pts);
Vector upperT(upperC, d_off*n_c_pts, n_c_pts);
lower(d_off) = lowerT.Min();
upper(d_off) = upperT.Max();
}
}
void GridFunction::GetElementBounds(const PLBound &plb,
Vector &lower, Vector &upper,
const int vdim)
{
int nel = fes->GetNE();
int fes_dim = fes->GetVDim();
lower.SetSize(nel*(vdim > 0 ? 1 :fes_dim));
upper.SetSize(nel*(vdim > 0 ? 1 :fes_dim));
for (int e = 0; e < nel; e++)
{
Vector lt, ut;
GetElementBounds(e, plb, lt, ut, vdim);
for (int d = 0; d < fes_dim ; d++)
{
if (vdim > 0 && d != vdim-1) { continue; }
const int d_off = vdim > 0 ? 0 : d;
lower(e + d_off*nel) = lt(d_off);
upper(e + d_off*nel) = ut(d_off);
}
}
}
PLBound GridFunction::GetElementBounds(Vector &lower,
Vector &upper,
const int ref_factor,
const int vdim)
{
int max_order = fes->GetMaxElementOrder();
PLBound plb(fes, ref_factor*(max_order+1));
GetElementBounds(plb, lower, upper, vdim);
return plb;
}
PLBound GridFunction::GetBounds(Vector &lower, Vector &upper,
const int ref_factor, const int vdim)
{
int max_order = fes->GetMaxElementOrder();
PLBound plb(fes, ref_factor*(max_order+1));
Vector lel, uel;
GetElementBounds(plb, lel, uel, vdim);
int nel = fes->GetNE();
int fes_dim = fes->GetVDim();
lower.SetSize(vdim > 0 ? 1 : fes_dim);
upper.SetSize(vdim > 0 ? 1 : fes_dim);
for (int d = 0; d < fes_dim; d++)
{
if (vdim > 0 && d != vdim-1) { continue; }
const int d_off = vdim > 0 ? 0 : d;
Vector lelt(lel, d_off*nel, nel);
Vector uelt(uel, d_off*nel, nel);
lower(d_off) = lelt.Min();
upper(d_off) = uelt.Max();
}
return plb;
}
}
+47 -1
View File
@@ -16,6 +16,7 @@
#include "fespace.hpp"
#include "coefficient.hpp"
#include "bilininteg.hpp"
#include "bounds.hpp"
#ifdef MFEM_USE_ADIOS2
#include "../general/adios2stream.hpp"
#endif
@@ -1561,11 +1562,56 @@ public:
must be 2 and that quad elements will be broken into two triangles.*/
void SaveSTL(std::ostream &out, int TimesToRefine = 1);
/** @name Methods to compute bounds on the grid function
\brief See bounds.hpp for \ref PLBound that constructs piecewise linear
bounds for a given set of bases. These piecewise bounds can be used to compute bounds on a grid function. Currently tensor-product elements are
supported with Lagrange interpolants on Gauss Legendre nodes and Gauss Lobatto Legendre nodes, and Bernstein bases.
*/
///@{
/// Computes the \ref PLBound for the gridfunction with number of control
/// points based on @a ref_factor, and returns the overall bounds for each
/// vdim (across all elements) in @b lower and @b upper. We also return the
/// PLBound object used to compute the bounds.
/// We compute the bounds for each vdim if @a vdim < 1.
/// Note: For most cases, this method/interface will be sufficient.
virtual PLBound GetBounds(Vector &lower, Vector &upper,
const int ref_factor=1, const int vdim=-1);
/// Computes the \ref PLBound for the gridfunction with number of control
/// points based on @a ref_factor, and returns the bounds for each element
/// ordered byVDim:
/// lower_{0,0}, lower_{1,0}, ..., lower_{ne-1,0},
/// lower_{0,1}, ..., lower_{ne-1,vdim-1}. We also return the
/// PLBound object used to compute the bounds.
/// We compute the bounds for each vdim if @a vdim < 1.
PLBound GetElementBounds(Vector &lower, Vector &upper,
const int ref_factor=1, const int vdim=-1);
/// Compute piecewise linear bounds on the given element at the grid of
/// [plb.ncp x plb.ncp x plb.ncp] control points for each of the vdim
/// components of the gridfunction.
void GetElementBoundsAtControlPoints(const int elem, const PLBound &plb,
Vector &lower, Vector &upper,
const int vdim = -1);
/// Compute bounds on the grid function for the given element.
/// The bounds are stored in @b lower and @b upper.
void GetElementBounds(const int elem, const PLBound &plb,
Vector &lower, Vector &upper,
const int vdim = -1);
/// Compute bounds on the grid function for all the elements. The bounds
/// are returned in @b lower and @b upper, ordered byVDim:
/// lower_{0,0}, lower_{1,0}, ..., lower_{ne-1,0},
/// lower_{0,1}, ..., lower_{ne-1,vdim-1}
void GetElementBounds(const PLBound &plb, Vector &lower, Vector &upper,
const int vdim=-1);
///@}
/// Destroys grid function.
virtual ~GridFunction() { Destroy(); }
};
/** Overload operator<< for std::ostream and GridFunction; valid also for the
derived class ParGridFunction */
std::ostream &operator<<(std::ostream &out, const GridFunction &sol);
+3 -1
View File
@@ -30,7 +30,9 @@ namespace mfem
{
/** \brief FindPointsGSLIB can robustly evaluate a GridFunction on an arbitrary
* collection of points.
* collection of points. See Mittal et al., "General Field Evaluation in
* High-Order Meshes on GPUs". (2025). Computers & Fluids. for technical
* details.
*
* There are three key functions in FindPointsGSLIB:
*
+64
View File
@@ -202,4 +202,68 @@ void CurlCurlIntegrator::AddMultPA(const Vector &x, Vector &y) const
}
}
void CurlCurlIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
{
Vector abs_pa_data(pa_data);
abs_pa_data.Abs();
auto absO = mapsO->Abs();
auto absC = mapsC->Abs();
if (dim == 3)
{
if (Device::Allows(Backend::DEVICE_MASK))
{
const int ID = (dofs1D << 4) | quad1D;
switch (ID)
{
case 0x23:
return internal::SmemPACurlCurlApply3D<2,3>(
dofs1D, quad1D,
symmetric, ne,
absO.B, absC.B, absO.Bt, absC.Bt,
absC.G, absC.Gt, abs_pa_data, x, y, true);
case 0x34:
return internal::SmemPACurlCurlApply3D<3,4>(
dofs1D, quad1D,
symmetric, ne,
absO.B, absC.B, absO.Bt, absC.Bt,
absC.G, absC.Gt, abs_pa_data, x, y, true);
case 0x45:
return internal::SmemPACurlCurlApply3D<4,5>(
dofs1D, quad1D,
symmetric, ne,
absO.B, absC.B, absO.Bt, absC.Bt,
absC.G, absC.Gt, abs_pa_data, x, y, true);
case 0x56:
return internal::SmemPACurlCurlApply3D<5,6>(
dofs1D, quad1D,
symmetric, ne,
absO.B, absC.B, absO.Bt, absC.Bt,
absC.G, absC.Gt, abs_pa_data, x, y, true);
default:
return internal::SmemPACurlCurlApply3D<0,0>(
dofs1D, quad1D, symmetric, ne,
absO.B, absC.B, absO.Bt, absC.Bt,
absC.G, absC.Gt, abs_pa_data, x, y, true);
}
}
else
{
internal::PACurlCurlApply3D<0,0>(
dofs1D, quad1D, symmetric, ne,
absO.B, absC.B, absO.Bt, absC.Bt, absC.G, absC.Gt,
abs_pa_data, x, y, true);
}
}
else if (dim == 2)
{
internal::PACurlCurlApply2D(dofs1D, quad1D, ne, absO.B, absO.Bt,
absC.G, absC.Gt, abs_pa_data, x, y, true);
}
else
{
MFEM_ABORT("Unsupported dimension!");
}
}
} // namespace mfem
@@ -483,19 +483,6 @@ inline void SmemPADiffusionDiagonal3D(const int NE,
});
}
void PADiffusionApply(const int dim,
const int D1D,
const int Q1D,
const int NE,
const bool symm,
const Array<real_t> &B,
const Array<real_t> &G,
const Array<real_t> &Bt,
const Array<real_t> &Gt,
const Vector &D,
const Vector &X,
Vector &Y);
#ifdef MFEM_USE_OCCA
// OCCA PA Diffusion Apply 2D kernel
void OccaPADiffusionApply2D(const int D1D,
+30
View File
@@ -164,6 +164,36 @@ void DiffusionIntegrator::AssemblePatchPA(const int patch,
SetupPatchPA(patch, mesh); // For full quadrature, unitWeights = false
}
void DiffusionIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
{
if (DeviceCanUseCeed())
{
MFEM_ABORT("Ceed AbsMult not implemented yet");
}
Vector abs_pa_data(pa_data);
abs_pa_data.Abs();
auto abs_maps = maps->Abs();
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric,
abs_maps.B, abs_maps.G, abs_maps.Bt, abs_maps.Gt,
abs_pa_data, x, y, dofs1D, quad1D);
}
void DiffusionIntegrator::AddAbsMultTransposePA(const Vector &x,
Vector &y) const
{
if (symmetric)
{
AddAbsMultPA(x, y);
}
else
{
MFEM_ABORT("DiffusionIntegrator::AddAbsMultTransposePA only implemented "
"in the symmetric case.")
}
}
// This version uses full 1D quadrature rules, taking into account the
// minimum interaction between basis functions and integration points.
void DiffusionIntegrator::AddMultPatchPA(const int patch, const Vector &x,
+6 -5
View File
@@ -212,7 +212,7 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
const int iIndex = isComponent ? 0 : i;
div += gradx(iIndex,i);
}
const real_t w = ipWeights[p] /det(invJ);
const real_t w = ipWeights[p]/det(invJ);
for (int m = 0; m < d; m++)
{
for (int q = qLower; q < qUpper; q++)
@@ -226,8 +226,8 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
{
for (int a = 0; a < d; a++)
{
contraction += 2*((a == q)*invJ(m,j_block) + (j_block==q)*invJ(m,a))*(gradx(0,
a));
contraction += 2*((a == q)*invJ(m,j_block)
+ (j_block==q)*invJ(m,a))*(gradx(0, a));
}
}
else
@@ -236,7 +236,7 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
{
for (int b = 0; b < d; b++)
{
contraction += ((a == q)*invJ(m,b) + (b==q)*invJ(m,a))
contraction += ((a == q)*invJ(m,b) + (b == q)*invJ(m,a))
*(gradx(a,b) + gradx(b, a));
}
}
@@ -244,7 +244,8 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
// lambda*div(u)*div(v) + 2*mu*sym(grad(u))*sym(grad(v))
// contraction = 4*sym(grad(u))sym(grad(v))
const int qIndex = isComponent ? 0 : q;
Q(p,m,qIndex,e) = w*(lamDev(p, e)*invJ(m,q)*div + 0.5*muDev(p, e)*contraction);
Q(p,m,qIndex,e) = w*(lamDev(p, e)*invJ(m,q)*div
+ 0.5*muDev(p, e)*contraction);
}
}
}
+6 -3
View File
@@ -662,7 +662,8 @@ void PACurlCurlApply2D(const int D1D,
const Array<real_t> &gct,
const Vector &pa_data,
const Vector &x,
Vector &y)
Vector &y,
const bool useAbs)
{
auto Bo = Reshape(bo.Read(), Q1D, D1D-1);
@@ -717,7 +718,8 @@ void PACurlCurlApply2D(const int D1D,
for (int qy = 0; qy < Q1D; ++qy)
{
const real_t wy = (c == 0) ? -Gc(qy,dy) : Bo(qy,dy);
const int sign = useAbs ? 1 : -1;
const real_t wy = (c == 0) ? (sign*Gc(qy,dy)) : Bo(qy,dy);
for (int qx = 0; qx < Q1D; ++qx)
{
curl[qy][qx] += gradX[qx] * wy;
@@ -760,7 +762,8 @@ void PACurlCurlApply2D(const int D1D,
}
for (int dy = 0; dy < D1Dy; ++dy)
{
const real_t wy = (c == 0) ? -Gct(dy,qy) : Bot(dy,qy);
const int sign = useAbs ? 1 : -1;
const real_t wy = (c == 0) ? (sign*Gct(dy,qy)) : Bot(dy,qy);
for (int dx = 0; dx < D1Dx; ++dx)
{
+132 -27
View File
@@ -828,7 +828,7 @@ inline void SmemPACurlCurlAssembleDiagonal3D(const int d1d,
}); // end of element loop
}
// PA H(curl) curl-curl Apply 2D kernel
// PA H(curl) curl-curl Apply/AbsApply 2D kernel
void PACurlCurlApply2D(const int D1D,
const int Q1D,
const int NE,
@@ -838,9 +838,10 @@ void PACurlCurlApply2D(const int D1D,
const Array<real_t> &gct,
const Vector &pa_data,
const Vector &x,
Vector &y);
Vector &y,
const bool useAbs = false);
// PA H(curl) curl-curl Apply 3D kernel
// PA H(curl) curl-curl Apply/AbsApply 3D kernel
template<int T_D1D = 0, int T_Q1D = 0>
inline void PACurlCurlApply3D(const int d1d,
const int q1d,
@@ -854,7 +855,8 @@ inline void PACurlCurlApply3D(const int d1d,
const Array<real_t> &gct,
const Vector &pa_data,
const Vector &x,
Vector &y)
Vector &y,
const bool useAbs = false)
{
MFEM_VERIFY(T_D1D || d1d <= DeviceDofQuadLimits::Get().HCURL_MAX_D1D,
"Error: d1d > HCURL_MAX_D1D");
@@ -970,7 +972,16 @@ inline void PACurlCurlApply3D(const int d1d,
{
// \hat{\nabla}\times\hat{u} is [0, (u_0)_{x_2}, -(u_0)_{x_1}]
curl[qz][qy][qx][1] += gradXY[qy][qx][1] * wDz; // (u_0)_{x_2}
curl[qz][qy][qx][2] -= gradXY[qy][qx][0] * wz; // -(u_0)_{x_1}
if (useAbs)
{
// +(u_0)_{x_1}
curl[qz][qy][qx][2] += gradXY[qy][qx][0] * wz;
}
else
{
// -(u_0)_{x_1}
curl[qz][qy][qx][2] -= gradXY[qy][qx][0] * wz;
}
}
}
}
@@ -1038,7 +1049,16 @@ inline void PACurlCurlApply3D(const int d1d,
for (int qx = 0; qx < Q1D; ++qx)
{
// \hat{\nabla}\times\hat{u} is [-(u_1)_{x_2}, 0, (u_1)_{x_0}]
curl[qz][qy][qx][0] -= gradXY[qy][qx][1] * wDz; // -(u_1)_{x_2}
if (useAbs)
{
// +(u_1)_{x_2}
curl[qz][qy][qx][0] += gradXY[qy][qx][1] * wDz;
}
else
{
// -(u_1)_{x_2}
curl[qz][qy][qx][0] -= gradXY[qy][qx][1] * wDz;
}
curl[qz][qy][qx][2] += gradXY[qy][qx][0] * wz; // (u_1)_{x_0}
}
}
@@ -1109,7 +1129,16 @@ inline void PACurlCurlApply3D(const int d1d,
{
// \hat{\nabla}\times\hat{u} is [(u_2)_{x_1}, -(u_2)_{x_0}, 0]
curl[qz][qy][qx][0] += gradYZ[qz][qy][1] * wx; // (u_2)_{x_1}
curl[qz][qy][qx][1] -= gradYZ[qz][qy][0] * wDx; // -(u_2)_{x_0}
if (useAbs)
{
// +(u_2)_{x_0}
curl[qz][qy][qx][1] += gradYZ[qz][qy][0] * wDx;
}
else
{
// -(u_2)_{x_0}
curl[qz][qy][qx][1] -= gradYZ[qz][qy][0] * wDx;
}
}
}
}
@@ -1209,9 +1238,21 @@ inline void PACurlCurlApply3D(const int d1d,
for (int dx = 0; dx < D1Dx; ++dx)
{
// \hat{\nabla}\times\hat{u} is [0, (u_0)_{x_2}, -(u_0)_{x_1}]
// (u_0)_{x_2} * (op * curl)_1 - (u_0)_{x_1} * (op * curl)_2
Y(dx + ((dy + (dz * D1Dy)) * D1Dx) + osc,
e) += (gradXY21[dy][dx] * wDz) - (gradXY12[dy][dx] * wz);
const int idx = dx + ((dy + (dz * D1Dy)) * D1Dx) + osc;
if (useAbs)
{
// (u_0)_{x_2} * (op * curl)_1 +
// (u_0)_{x_1} * (op * curl)_2
Y(idx, e) += (gradXY21[dy][dx] * wDz) +
(gradXY12[dy][dx] * wz);
}
else
{
// (u_0)_{x_2} * (op * curl)_1 -
// (u_0)_{x_1} * (op * curl)_2
Y(idx, e) += (gradXY21[dy][dx] * wDz) -
(gradXY12[dy][dx] * wz);
}
}
}
}
@@ -1278,10 +1319,22 @@ inline void PACurlCurlApply3D(const int d1d,
{
for (int dx = 0; dx < D1Dx; ++dx)
{
const int idx = dx + ((dy + (dz * D1Dy)) * D1Dx) + osc;
// \hat{\nabla}\times\hat{u} is [-(u_1)_{x_2}, 0, (u_1)_{x_0}]
// -(u_1)_{x_2} * (op * curl)_0 + (u_1)_{x_0} * (op * curl)_2
Y(dx + ((dy + (dz * D1Dy)) * D1Dx) + osc,
e) += (-gradXY20[dy][dx] * wDz) + (gradXY02[dy][dx] * wz);
if (useAbs)
{
// +(u_1)_{x_2} * (op * curl)_0 +
// (u_1)_{x_0} * (op * curl)_2
Y(idx, e) += (gradXY20[dy][dx] * wDz) +
(gradXY02[dy][dx] * wz);
}
else
{
// -(u_1)_{x_2} * (op * curl)_0 +
// (u_1)_{x_0} * (op * curl)_2
Y(idx, e) += (-gradXY20[dy][dx] * wDz) +
(gradXY02[dy][dx] * wz);
}
}
}
}
@@ -1351,10 +1404,22 @@ inline void PACurlCurlApply3D(const int d1d,
{
for (int dz = 0; dz < D1Dz; ++dz)
{
const int idx = dx + ((dy + (dz * D1Dy)) * D1Dx) + osc;
// \hat{\nabla}\times\hat{u} is [(u_2)_{x_1}, -(u_2)_{x_0}, 0]
// (u_2)_{x_1} * (op * curl)_0 - (u_2)_{x_0} * (op * curl)_1
Y(dx + ((dy + (dz * D1Dy)) * D1Dx) + osc,
e) += (gradYZ10[dz][dy] * wx) - (gradYZ01[dz][dy] * wDx);
if (useAbs)
{
// (u_2)_{x_1} * (op * curl)_0 +
// (u_2)_{x_0} * (op * curl)_1
Y(idx, e) += (gradYZ10[dz][dy] * wx) +
(gradYZ01[dz][dy] * wDx);
}
else
{
// (u_2)_{x_1} * (op * curl)_0 -
// (u_2)_{x_0} * (op * curl)_1
Y(idx, e) += (gradYZ10[dz][dy] * wx) -
(gradYZ01[dz][dy] * wDx);
}
}
}
}
@@ -1363,7 +1428,7 @@ inline void PACurlCurlApply3D(const int d1d,
}); // end of element loop
}
// Shared memory PA H(curl) curl-curl Apply 3D kernel
// Shared memory PA H(curl) curl-curl Apply/AbsApply 3D kernel
template<int T_D1D = 0, int T_Q1D = 0>
inline void SmemPACurlCurlApply3D(const int d1d,
const int q1d,
@@ -1377,7 +1442,8 @@ inline void SmemPACurlCurlApply3D(const int d1d,
const Array<real_t> &gct,
const Vector &pa_data,
const Vector &x,
Vector &y)
Vector &y,
const bool useAbs = false)
{
MFEM_VERIFY(T_D1D || d1d <= DeviceDofQuadLimits::Get().HCURL_MAX_D1D,
"Error: d1d > HCURL_MAX_D1D");
@@ -1531,7 +1597,8 @@ inline void SmemPACurlCurlApply3D(const int d1d,
}
curl[qy][qx][1] += v; // (u_0)_{x_2}
curl[qy][qx][2] -= u; // -(u_0)_{x_1}
if (useAbs) { curl[qy][qx][2] += u; } // +(u_0)_{x_1}
else { curl[qy][qx][2] -= u; } // -(u_0)_{x_1}
}
else if (c == 1) // y component
{
@@ -1558,7 +1625,8 @@ inline void SmemPACurlCurlApply3D(const int d1d,
}
}
curl[qy][qx][0] -= v; // -(u_1)_{x_2}
if (useAbs) { curl[qy][qx][0] += v; } // +(u_1)_{x_2}
else { curl[qy][qx][0] -= v; } // -(u_1)_{x_2}
curl[qy][qx][2] += u; // (u_1)_{x_0}
}
else // z component
@@ -1587,7 +1655,8 @@ inline void SmemPACurlCurlApply3D(const int d1d,
}
curl[qy][qx][0] += v; // (u_2)_{x_1}
curl[qy][qx][1] -= u; // -(u_2)_{x_0}
if (useAbs) { curl[qy][qx][1] += u; }// +(u_2)_{x_0}
else { curl[qy][qx][1] -= u; } // -(u_2)_{x_0}
}
} // qx
} // qy
@@ -1642,18 +1711,54 @@ inline void SmemPACurlCurlApply3D(const int d1d,
if (dx < D1D-1)
{
// \hat{\nabla}\times\hat{u} is [0, (u_0)_{x_2}, -(u_0)_{x_1}]
// (u_0)_{x_2} * (op * curl)_1 - (u_0)_{x_1} * (op * curl)_2
const real_t wx = sBo[dx][qx];
dxyz1 += (wx * c2 * wcy * wcDz) - (wx * c3 * wcDy * wcz);
if (useAbs)
{
// (u_0)_{x_2} * (op * curl)_1 +
// (u_0)_{x_1} * (op * curl)_2
dxyz1 += (wx * c2 * wcy * wcDz) +
(wx * c3 * wcDy * wcz);
}
else
{
// (u_0)_{x_2} * (op * curl)_1 -
// (u_0)_{x_1} * (op * curl)_2
dxyz1 += (wx * c2 * wcy * wcDz) -
(wx * c3 * wcDy * wcz);
}
}
// \hat{\nabla}\times\hat{u} is [-(u_1)_{x_2}, 0, (u_1)_{x_0}]
// -(u_1)_{x_2} * (op * curl)_0 + (u_1)_{x_0} * (op * curl)_2
dxyz2 += (-wy * c1 * wcx * wcDz) + (wy * c3 * wDx * wcz);
if (useAbs)
{
// +(u_1)_{x_2} * (op * curl)_0 +
// (u_1)_{x_0} * (op * curl)_2
dxyz2 += (wy * c1 * wcx * wcDz) +
(wy * c3 * wDx * wcz);
}
else
{
// -(u_1)_{x_2} * (op * curl)_0 +
// (u_1)_{x_0} * (op * curl)_2
dxyz2 += (-wy * c1 * wcx * wcDz) +
(wy * c3 * wDx * wcz);
}
// \hat{\nabla}\times\hat{u} is [(u_2)_{x_1}, -(u_2)_{x_0}, 0]
// (u_2)_{x_1} * (op * curl)_0 - (u_2)_{x_0} * (op * curl)_1
dxyz3 += (wcDy * wz * c1 * wcx) - (wcy * wz * c2 * wDx);
if (useAbs)
{
// (u_2)_{x_1} * (op * curl)_0 +
// (u_2)_{x_0} * (op * curl)_1
dxyz3 += (wcDy * wz * c1 * wcx) +
(wcy * wz * c2 * wDx);
}
else
{
// (u_2)_{x_1} * (op * curl)_0 -
// (u_2)_{x_0} * (op * curl)_1
dxyz3 += (wcDy * wz * c1 * wcx) -
(wcy * wz * c2 * wDx);
}
} // qx
} // qy
} // dx
+27
View File
@@ -199,10 +199,37 @@ void MassIntegrator::AddMultPA(const Vector &x, Vector &y) const
}
}
void MassIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
{
if (DeviceCanUseCeed())
{
MFEM_ABORT("AddAbsMultPA not implemented with CEED!");
ceedOp->AddMult(x, y);
}
else
{
Vector abs_pa_data(pa_data);
abs_pa_data.Abs();
Array<real_t> absB(maps->B);
Array<real_t> absBt(maps->Bt);
absB.Abs();
absBt.Abs();
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, absB, absBt, abs_pa_data,
x, y, dofs1D, quad1D);
}
}
void MassIntegrator::AddMultTransposePA(const Vector &x, Vector &y) const
{
// Mass integrator is symmetric
AddMultPA(x, y);
}
void MassIntegrator::AddAbsMultTransposePA(const Vector &x, Vector &y) const
{
// Mass integrator is symmetric
AddAbsMultPA(x, y);
}
} // namespace mfem
+123
View File
@@ -313,6 +313,129 @@ void VectorFEMassIntegrator::AddMultPA(const Vector &x, Vector &y) const
}
}
void VectorFEMassIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
{
const bool trial_curl = (trial_fetype == mfem::FiniteElement::CURL);
const bool trial_div = (trial_fetype == mfem::FiniteElement::DIV);
const bool test_curl = (test_fetype == mfem::FiniteElement::CURL);
const bool test_div = (test_fetype == mfem::FiniteElement::DIV);
Vector abs_pa_data(pa_data);
abs_pa_data.Abs();
Array<real_t> absBo(mapsO->B);
Array<real_t> absBc(mapsC->B);
Array<real_t> absBto(mapsO->Bt);
Array<real_t> absBtc(mapsC->Bt);
Array<real_t> absBto_t(mapsOtest->Bt);
Array<real_t> absBtc_t(mapsCtest->Bt);
absBo.Abs();
absBc.Abs();
absBto.Abs();
absBtc.Abs();
absBto_t.Abs();
absBtc_t.Abs();
if (dim == 3)
{
if (trial_curl && test_curl)
{
if (Device::Allows(Backend::DEVICE_MASK))
{
const int ID = (dofs1D << 4) | quad1D;
switch (ID)
{
case 0x23:
return internal::SmemPAHcurlMassApply3D<2,3>(
dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
case 0x34:
return internal::SmemPAHcurlMassApply3D<3,4>(
dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
case 0x45:
return internal::SmemPAHcurlMassApply3D<4,5>(
dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
case 0x56:
return internal::SmemPAHcurlMassApply3D<5,6>(
dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
default:
return internal::SmemPAHcurlMassApply3D(
dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
}
}
else
{
internal::PAHcurlMassApply3D(dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
}
}
else if (trial_div && test_div)
{
internal::PAHdivMassApply(3, dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
}
else if (trial_curl && test_div)
{
const bool scalarCoeff = !(DQ || MQ);
internal::PAHcurlHdivMassApply3D(dofs1D, dofs1Dtest, quad1D, ne,
scalarCoeff, true, false,
absBo, absBc, absBto_t, absBtc_t,
abs_pa_data, x, y);
}
else if (trial_div && test_curl)
{
const bool scalarCoeff = !(DQ || MQ);
internal::PAHcurlHdivMassApply3D(dofs1D, dofs1Dtest, quad1D, ne,
scalarCoeff, false, false,
absBo, absBc, absBto_t, absBtc_t,
abs_pa_data, x, y);
}
else
{
MFEM_ABORT("Unknown kernel.");
}
}
else // 2D
{
if (trial_curl && test_curl)
{
internal::PAHcurlMassApply2D(dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
}
else if (trial_div && test_div)
{
internal::PAHdivMassApply(2, dofs1D, quad1D, ne, symmetric,
absBo, absBc, absBto, absBtc,
abs_pa_data, x, y);
}
else if ((trial_curl && test_div) || (trial_div && test_curl))
{
const bool scalarCoeff = !(DQ || MQ);
internal::PAHcurlHdivMassApply2D(dofs1D, dofs1Dtest, quad1D, ne,
scalarCoeff, trial_curl, false,
absBo, absBc, absBto_t, absBtc_t,
abs_pa_data, x, y);
}
else
{
MFEM_ABORT("Unknown kernel.");
}
}
}
void VectorFEMassIntegrator::AddMultTransposePA(const Vector &x,
Vector &y) const
{
+12
View File
@@ -577,7 +577,13 @@ public:
void Mult(const Vector &x, Vector &y) const override;
void AbsMult(const Vector &x, Vector &y) const override
{ Mult(x,y); }
void MultTranspose(const Vector &x, Vector &y) const override;
void AbsMultTranspose(const Vector &x, Vector &y) const override
{ MultTranspose(x,y); }
};
/// Auxiliary device class used by ParFiniteElementSpace.
@@ -628,7 +634,13 @@ public:
void Mult(const Vector &x, Vector &y) const override;
void AbsMult(const Vector &x, Vector &y) const override
{ Mult(x,y); }
void MultTranspose(const Vector &x, Vector &y) const override;
void AbsMultTranspose(const Vector &x, Vector &y) const override
{ MultTranspose(x,y); }
};
}
+12
View File
@@ -1406,6 +1406,18 @@ real_t L2ZZErrorEstimator(BilinearFormIntegrator &flux_integrator,
return pow(glob_error, 1.0/norm_p);
}
PLBound ParGridFunction::GetBounds(Vector &lower, Vector &upper,
const int ref_factor, const int vdim)
{
PLBound plb = GridFunction::GetBounds(lower, upper, ref_factor, vdim);
int siz = vdim > 0 ? 1 : fes->GetVDim();
MPI_Allreduce(MPI_IN_PLACE, lower.HostReadWrite(), siz,
MFEM_MPI_REAL_T, MPI_MIN, pfes->GetComm());
MPI_Allreduce(MPI_IN_PLACE, upper.HostReadWrite(), siz,
MFEM_MPI_REAL_T, MPI_MAX, pfes->GetComm());
return plb;
}
} // namespace mfem
#endif // MFEM_USE_MPI
+8
View File
@@ -581,6 +581,14 @@ public:
GridFunction &flux,
bool wcoef = true, int subdomain = -1) override;
/// Computes the PLBound for the gridfunction with number of control
/// points based on @a ref_factor, and returns the bounds for each
/// vdim across all elements in @b lower and @b upper. We also return the
/// PLBound object used to compute the bounds. Note: if vdim < 1, we compute
/// the bounds for each vector dimension.
PLBound GetBounds(Vector &lower, Vector &upper,
const int ref_factor=1, const int vdim=-1) override;
/** Save the local portion of the ParGridFunction. This differs from the
serial GridFunction::Save in that it takes into account the signs of
the local dofs. */
+2 -2
View File
@@ -566,8 +566,8 @@ void QuadratureInterpolator::Mult(const Vector &e_vec,
}
else // use_tensor_eval == false
{
EvalKernels::Run(dim, vdim, maps.ndof, maps.nqpt, ne,vdim,q_layout,
geom, maps,e_vec, q_val,q_der,q_det,eval_flags);
EvalKernels::Run(dim, vdim, maps.ndof, maps.nqpt, ne,vdim, q_layout,
geom, maps, e_vec, q_val, q_der, q_det, eval_flags);
}
}
+6 -5
View File
@@ -128,7 +128,7 @@ void ElementRestriction::Mult(const Vector& x, Vector& y) const
});
}
void ElementRestriction::MultUnsigned(const Vector& x, Vector& y) const
void ElementRestriction::AbsMult(const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nd = dof;
@@ -193,7 +193,7 @@ void ElementRestriction::AddMultTranspose(const Vector& x, Vector& y,
TAddMultTranspose<ADD>(x, y);
}
void ElementRestriction::MultTransposeUnsigned(const Vector& x, Vector& y) const
void ElementRestriction::AbsMultTranspose(const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nd = dof;
@@ -653,7 +653,8 @@ ConformingFaceRestriction::ConformingFaceRestriction(
: ConformingFaceRestriction(fes, f_ordering, type, true)
{ }
void ConformingFaceRestriction::Mult(const Vector& x, Vector& y) const
void ConformingFaceRestriction::MultInternal(const Vector& x, Vector& y,
const bool useAbs) const
{
if (nf==0) { return; }
// Assumes all elements have the same number of dofs
@@ -666,7 +667,7 @@ void ConformingFaceRestriction::Mult(const Vector& x, Vector& y) const
mfem::forall(nfdofs, [=] MFEM_HOST_DEVICE (int i)
{
const int s_idx = d_indices[i];
const int sgn = (s_idx >= 0) ? 1 : -1;
const int sgn = (useAbs || s_idx >= 0) ? 1 : -1;
const int idx = (s_idx >= 0) ? s_idx : -1 - s_idx;
const int dof = i % nface_dofs;
const int face = i / nface_dofs;
@@ -724,7 +725,7 @@ void ConformingFaceRestriction::AddMultTranspose(
true, a);
}
void ConformingFaceRestriction::AddMultTransposeUnsigned(
void ConformingFaceRestriction::AddAbsMultTranspose(
const Vector& x, Vector& y, const real_t a) const
{
ConformingFaceRestriction_AddMultTranspose(
+55 -7
View File
@@ -59,9 +59,18 @@ public:
const real_t a = 1.0) const override;
/// Compute Mult without applying signs based on DOF orientations.
void MultUnsigned(const Vector &x, Vector &y) const;
void AbsMult(const Vector &x, Vector &y) const override;
/// Compute MultTranspose without applying signs based on DOF orientations.
void MultTransposeUnsigned(const Vector &x, Vector &y) const;
void AbsMultTranspose(const Vector &x, Vector &y) const override;
/// @deprecated Use AbsMult() instead.
MFEM_DEPRECATED void MultUnsigned(const Vector &x, Vector &y) const
{ AbsMult(x, y); }
/// @deprecated Use AbsMultTranspose() instead.
MFEM_DEPRECATED void MultTransposeUnsigned(const Vector &x, Vector &y) const
{ AbsMultTranspose(x, y); }
/// Compute MultTranspose by setting (rather than adding) element
/// contributions; this is a left inverse of the Mult() operation
@@ -184,12 +193,19 @@ public:
/** @brief Add the face degrees of freedom @a x to the element degrees of
freedom @a y ignoring the signs from DOF orientation. */
virtual void AddMultTransposeUnsigned(const Vector &x, Vector &y,
const real_t a = 1.0) const
virtual void AddAbsMultTranspose(const Vector &x, Vector &y,
const real_t a = 1.0) const
{
AddMultTranspose(x, y, a);
}
/// @deprecated Use AddAbsMultTranspose() instead.
MFEM_DEPRECATED void AddMultTransposeUnsigned(const Vector &x, Vector &y,
const real_t a = 1.0) const
{
AddAbsMultTranspose(x, y, a);
}
/** @brief Add the face degrees of freedom @a x to the element degrees of
freedom @a y. Perform the same computation as AddMultTranspose, but
@a x is invalid after calling this method.
@@ -219,6 +235,12 @@ public:
AddMultTranspose(x, y);
}
void AbsMultTranspose(const Vector &x, Vector &y) const override
{
y = 0.0;
AddAbsMultTranspose(x, y);
}
/** @brief For each face, sets @a y to the partial derivative of @a x with
respect to the reference coordinate whose direction is
perpendicular to the face on the reference element.
@@ -319,7 +341,16 @@ public:
requested by @a type in the constructor.
The face_dofs are ordered according to the given
ElementDofOrdering. */
void Mult(const Vector &x, Vector &y) const override;
void Mult(const Vector &x, Vector &y) const override
{ MultInternal(x, y); }
/// Compute Mult without applying signs based on DOF orientations.
void AbsMult(const Vector &x, Vector &y) const override
{ MultInternal(x, y, true); }
/// @deprecated Use AbsMult() instead.
MFEM_DEPRECATED void MultUnsigned(const Vector &x, Vector &y) const
{ AbsMult(x, y); }
using FaceRestriction::AddMultTransposeInPlace;
@@ -341,8 +372,20 @@ public:
L-Vector @b not taking into account signs from DOF orientations.
@sa AddMultTranspose(). */
void AddMultTransposeUnsigned(const Vector &x, Vector &y,
const real_t a = 1.0) const override;
void AddAbsMultTranspose(const Vector &x, Vector &y,
const real_t a = 1.0) const override;
/// @deprecated Use AddAbsMultTranspose() instead.
MFEM_DEPRECATED void AddMultTransposeUnsigned(const Vector &x, Vector &y) const
{
AddAbsMultTranspose(x, y);
}
void AbsMultTranspose(const Vector &x, Vector &y) const override
{
y = 0.0;
AddAbsMultTranspose(x, y);
}
private:
/** @brief Compute the scatter indices: L-vector to E-vector, and the offsets
@@ -395,6 +438,11 @@ protected:
void SetFaceDofsGatherIndices(const Mesh::FaceInformation &face,
const int face_index,
const ElementDofOrdering f_ordering);
public:
// This method needs to be public due to 'nvcc' restriction.
void MultInternal(const Vector &x, Vector &y,
const bool useAbs = false) const;
};
/// @brief Alias for ConformingFaceRestriction, for backwards compatibility and
+14
View File
@@ -15,6 +15,7 @@
#include "array.hpp"
#include "../general/forall.hpp"
#include <fstream>
#include <type_traits>
namespace mfem
{
@@ -110,6 +111,19 @@ void Array<T>::PartialSum()
}
}
template <class T>
void Array<T>::Abs()
{
static_assert(std::is_arithmetic<T>::value, "Use with arithmetic types!");
const bool useDevice = UseDevice();
const int N = size;
auto y = ReadWrite(useDevice);
mfem::forall_switch(useDevice, N, [=] MFEM_HOST_DEVICE (int i)
{
y[i] = std::abs(y[i]);
});
}
// Sum
template <class T>
T Array<T>::Sum() const
+3
View File
@@ -305,6 +305,9 @@ public:
/// Fill the entries of the array with the cumulative sum of the entries.
void PartialSum();
/// Replace each entry of the array with its absolute value.
void Abs();
/// Return the sum of all the array entries using the '+'' operator for class 'T'.
T Sum() const;
-156
View File
@@ -23,9 +23,6 @@
#include <_hypre_utilities.h>
#endif
#include "array.hpp"
#include "reducers.hpp"
namespace mfem
{
@@ -853,159 +850,6 @@ inline MemoryClass GetHypreForallMemoryClass()
#endif // MFEM_USE_MPI
namespace internal
{
/**
@brief Device portion of a reduction over a 1D sequence [0, N)
@tparam B Reduction body. Must be callable with the signature void(int i, value_type&
v), where i is the index to evaluate and v is the value to update.
@tparam R Reducer capable of combining values of type value_type. See reducers.hpp for
pre-defined reducers.
*/
template<class B, class R> struct reduction_kernel
{
/// value type body and reducer operate on.
using value_type = typename R::value_type;
/// workspace for the intermediate reduction results
mutable value_type *work;
B body;
R reducer;
/// Length of sequence to reduce over.
int N;
/// How many items is each thread responsible for during the serial phase
int items_per_thread;
constexpr static MFEM_HOST_DEVICE int max_blocksize() { return 256; }
/// helper for computing the reduction block size
static int block_log2(unsigned N)
{
#if defined(__GNUC__) || defined(__clang__)
return N ? (sizeof(unsigned) * 8 - __builtin_clz(N)) : 0;
#elif defined(_MSC_VER)
return sizeof(unsigned) * 8 - __lzclz(N);
#else
int res = 0;
while (N)
{
N >>= 1;
++res;
}
return res;
#endif
}
MFEM_HOST_DEVICE void operator()(int work_idx) const
{
MFEM_SHARED value_type buffer[max_blocksize()];
reducer.SetInitialValue(buffer[MFEM_THREAD_ID(x)]);
// serial part
for (int idx = 0; idx < items_per_thread; ++idx)
{
int i = MFEM_THREAD_ID(x) +
(idx + work_idx * items_per_thread) * MFEM_THREAD_SIZE(x);
if (i < N)
{
body(i, buffer[MFEM_THREAD_ID(x)]);
}
else
{
break;
}
}
// binary tree reduction
for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1)
{
MFEM_SYNC_THREAD;
if (MFEM_THREAD_ID(x) < i)
{
reducer.Join(buffer[MFEM_THREAD_ID(x)], buffer[MFEM_THREAD_ID(x) + i]);
}
}
if (MFEM_THREAD_ID(x) == 0)
{
work[work_idx] = buffer[0];
}
}
};
}
/**
@brief Performs a 1D reduction on the range [0,N).
@a res initial value and where the result will be written.
@a body reduction function body.
@a reducer helper for joining two reduced values.
@a use_dev true to perform the reduction on the device, if possible.
@a workspace temporary workspace used for device reductions. May be resized to
a larger capacity as needed. Preferably should have MemoryType::MANAGED or
MemoryType::HOST_PINNED. TODO: replace with internal temporary workspace
vectors once that's added to the memory manager.
@tparam T value_type to operate on
*/
template <class T, class B, class R>
void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
Array<T> &workspace)
{
if (N == 0)
{
return;
}
#if defined(MFEM_USE_HIP) || defined(MFEM_USE_CUDA)
if (use_dev &&
mfem::Device::Allows(Backend::CUDA | Backend::HIP | Backend::RAJA_CUDA |
Backend::RAJA_HIP))
{
using red_type = internal::reduction_kernel<typename std::decay<B>::type,
typename std::decay<R>::type>;
// max block size is 256, but can be smaller
int block_size = std::min<int>(red_type::max_blocksize(),
1ll << red_type::block_log2(N));
int num_mp = Device::NumMultiprocessors(Device::GetId());
#if defined(MFEM_USE_CUDA)
// good value of mp_sat found experimentally on Lassen
constexpr int mp_sat = 8;
#elif defined(MFEM_USE_HIP)
// good value of mp_sat found experimentally on Tuolumne
constexpr int mp_sat = 4;
#else
num_mp = 1;
constexpr int mp_sat = 1;
#endif
// determine how many items each thread should sum during the serial
// portion
int nblocks = std::min(mp_sat * num_mp, (N + block_size - 1) / block_size);
int items_per_thread =
(N + block_size * nblocks - 1) / (block_size * nblocks);
red_type red{nullptr, std::forward<B>(body), reducer, N, items_per_thread};
// allocate res to fit block_size entries
auto mt = workspace.GetMemory().GetMemoryType();
if (mt != MemoryType::HOST_PINNED && mt != MemoryType::MANAGED)
{
mt = MemoryType::HOST_PINNED;
}
workspace.SetSize(nblocks, mt);
auto work = workspace.HostWrite();
red.work = work;
forall_2D(nblocks, block_size, 1, std::move(red));
// wait for results
MFEM_DEVICE_SYNC;
for (int i = 0; i < nblocks; ++i)
{
reducer.Join(res, work[i]);
}
return;
}
#endif
for (int i = 0; i < N; ++i)
{
body(i, res);
}
}
} // namespace mfem
#endif // MFEM_FORALL_HPP
+1 -1
View File
@@ -641,7 +641,7 @@ public:
UmpireMemorySpace(name, "DEVICE") {}
void Alloc(Memory &base) override
{ base.d_ptr = allocator.allocate(base.bytes); }
void Dealloc(Memory &base) override { rm.deallocate(base.d_ptr); }
void Dealloc(Memory &base) override { allocator.deallocate(base.d_ptr); }
void *HtoD(void *dst, const void *src, size_t bytes) override
{
#ifdef MFEM_USE_CUDA
+156 -3
View File
@@ -12,11 +12,10 @@
#ifndef MFEM_REDUCERS_HPP
#define MFEM_REDUCERS_HPP
#include "array.hpp"
#include "forall.hpp"
#include <climits>
#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>
@@ -439,6 +438,160 @@ template <class I> struct ArgMinMaxReducer<double, I>
}
};
namespace internal
{
/**
@brief Device portion of a reduction over a 1D sequence [0, N)
@tparam B Reduction body. Must be callable with the signature void(int i, value_type&
v), where i is the index to evaluate and v is the value to update.
@tparam R Reducer capable of combining values of type value_type. See reducers.hpp for
pre-defined reducers.
*/
template<class B, class R> struct reduction_kernel
{
/// value type body and reducer operate on.
using value_type = typename R::value_type;
/// workspace for the intermediate reduction results
mutable value_type *work;
B body;
R reducer;
/// Length of sequence to reduce over.
int N;
/// How many items is each thread responsible for during the serial phase
int items_per_thread;
constexpr static MFEM_HOST_DEVICE int max_blocksize() { return 256; }
/// helper for computing the reduction block size
static int block_log2(unsigned N)
{
#if defined(__GNUC__) or defined(__clang__)
return N ? (sizeof(unsigned) * 8 - __builtin_clz(N)) : 0;
#elif defined(_MSC_VER)
return sizeof(unsigned) * 8 - __lzclz(N);
#else
int res = 0;
while (N)
{
N >>= 1;
++res;
}
return res;
#endif
}
MFEM_HOST_DEVICE void operator()(int work_idx) const
{
MFEM_SHARED value_type buffer[max_blocksize()];
reducer.SetInitialValue(buffer[MFEM_THREAD_ID(x)]);
// serial part
for (int idx = 0; idx < items_per_thread; ++idx)
{
int i = MFEM_THREAD_ID(x) +
(idx + work_idx * items_per_thread) * MFEM_THREAD_SIZE(x);
if (i < N)
{
body(i, buffer[MFEM_THREAD_ID(x)]);
}
else
{
break;
}
}
// binary tree reduction
for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1)
{
MFEM_SYNC_THREAD;
if (MFEM_THREAD_ID(x) < i)
{
reducer.Join(buffer[MFEM_THREAD_ID(x)], buffer[MFEM_THREAD_ID(x) + i]);
}
}
if (MFEM_THREAD_ID(x) == 0)
{
work[work_idx] = buffer[0];
}
}
};
}
/**
@brief Performs a 1D reduction on the range [0,N).
@a res initial value and where the result will be written.
@a body reduction function body.
@a reducer helper for joining two reduced values.
@a use_dev true to perform the reduction on the device, if possible.
@a workspace temporary workspace used for device reductions. May be resized to
a larger capacity as needed. Preferably should have MemoryType::MANAGED or
MemoryType::HOST_PINNED. TODO: replace with internal temporary workspace
vectors once that's added to the memory manager.
@tparam T value_type to operate on
*/
template <class T, class B, class R>
void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
Array<T> &workspace)
{
if (N == 0)
{
return;
}
#if defined(MFEM_USE_HIP) || defined(MFEM_USE_CUDA)
if (use_dev &&
mfem::Device::Allows(Backend::CUDA | Backend::HIP | Backend::RAJA_CUDA |
Backend::RAJA_HIP))
{
using red_type = internal::reduction_kernel<typename std::decay<B>::type,
typename std::decay<R>::type>;
// max block size is 256, but can be smaller
int block_size = std::min<int>(red_type::max_blocksize(),
1ll << red_type::block_log2(N));
int num_mp = Device::NumMultiprocessors(Device::GetId());
#if defined(MFEM_USE_CUDA)
// good value of mp_sat found experimentally on Lassen
constexpr int mp_sat = 8;
#elif defined(MFEM_USE_HIP)
// good value of mp_sat found experimentally on Tuolumne
constexpr int mp_sat = 4;
#else
num_mp = 1;
constexpr int mp_sat = 1;
#endif
// determine how many items each thread should sum during the serial
// portion
int nblocks = std::min(mp_sat * num_mp, (N + block_size - 1) / block_size);
int items_per_thread =
(N + block_size * nblocks - 1) / (block_size * nblocks);
red_type red{nullptr, std::forward<B>(body), reducer, N, items_per_thread};
// allocate res to fit block_size entries
auto mt = workspace.GetMemory().GetMemoryType();
if (mt != MemoryType::HOST_PINNED && mt != MemoryType::MANAGED)
{
mt = MemoryType::HOST_PINNED;
}
workspace.SetSize(nblocks, mt);
auto work = workspace.HostWrite();
red.work = work;
forall_2D(nblocks, block_size, 1, std::move(red));
// wait for results
MFEM_DEVICE_SYNC;
for (int i = 0; i < nblocks; ++i)
{
reducer.Join(res, work[i]);
}
return;
}
#endif
for (int i = 0; i < N; ++i)
{
body(i, res);
}
}
} // namespace mfem
#endif
#endif // MFEM_REDUCERS_HPP
+25 -28
View File
@@ -124,24 +124,21 @@ const real_t &DenseMatrix::Elem(int i, int j) const
void DenseMatrix::Mult(const real_t *x, real_t *y) const
{
HostRead();
kernels::Mult(height, width, Data(), x, y);
kernels::Mult(height, width, HostRead(), x, y);
}
void DenseMatrix::Mult(const real_t *x, Vector &y) const
{
MFEM_ASSERT(height == y.Size(), "incompatible dimensions");
y.HostReadWrite();
Mult(x, y.GetData());
Mult(x, y.HostWrite());
}
void DenseMatrix::Mult(const Vector &x, real_t *y) const
{
MFEM_ASSERT(width == x.Size(), "incompatible dimensions");
x.HostRead();
Mult(x.GetData(), y);
Mult(x.HostRead(), y);
}
void DenseMatrix::Mult(const Vector &x, Vector &y) const
@@ -149,9 +146,15 @@ void DenseMatrix::Mult(const Vector &x, Vector &y) const
MFEM_ASSERT(height == y.Size() && width == x.Size(),
"incompatible dimensions");
x.HostRead();
y.HostReadWrite();
Mult(x.GetData(), y.GetData());
Mult(x.HostRead(), y.HostWrite());
}
void DenseMatrix::AbsMult(const Vector &x, Vector &y) const
{
MFEM_ASSERT(height == y.Size() && width == x.Size(),
"incompatible dimensions");
kernels::AbsMult(height, width, HostRead(), x.HostRead(), y.HostWrite());
}
real_t DenseMatrix::operator *(const DenseMatrix &m) const
@@ -171,34 +174,21 @@ real_t DenseMatrix::operator *(const DenseMatrix &m) const
void DenseMatrix::MultTranspose(const real_t *x, real_t *y) const
{
HostRead();
real_t *d_col = Data();
for (int col = 0; col < width; col++)
{
real_t y_col = 0.0;
for (int row = 0; row < height; row++)
{
y_col += x[row]*d_col[row];
}
y[col] = y_col;
d_col += height;
}
kernels::MultTranspose(height, width, HostRead(), x, y);
}
void DenseMatrix::MultTranspose(const real_t *x, Vector &y) const
{
MFEM_ASSERT(width == y.Size(), "incompatible dimensions");
y.HostReadWrite();
MultTranspose(x, y.GetData());
MultTranspose(x, y.HostWrite());
}
void DenseMatrix::MultTranspose(const Vector &x, real_t *y) const
{
MFEM_ASSERT(height == x.Size(), "incompatible dimensions");
x.HostRead();
MultTranspose(x.GetData(), y);
MultTranspose(x.HostRead(), y);
}
void DenseMatrix::MultTranspose(const Vector &x, Vector &y) const
@@ -206,9 +196,16 @@ void DenseMatrix::MultTranspose(const Vector &x, Vector &y) const
MFEM_ASSERT(height == x.Size() && width == y.Size(),
"incompatible dimensions");
x.HostRead();
y.HostReadWrite();
MultTranspose(x.GetData(), y.GetData());
MultTranspose(x.HostRead(), y.HostWrite());
}
void DenseMatrix::AbsMultTranspose(const Vector &x, Vector &y) const
{
MFEM_ASSERT(height == x.Size() && width == y.Size(),
"incompatible dimensions");
kernels::AbsMultTranspose(height, width, HostRead(),
x.HostRead(), y.HostWrite());
}
void DenseMatrix::AddMult(const Vector &x, Vector &y, const real_t a) const
+6
View File
@@ -153,6 +153,9 @@ public:
/// Matrix vector multiplication.
void Mult(const Vector &x, Vector &y) const override;
/// Absolute-value matrix vector multiplication.
void AbsMult(const Vector &x, Vector &y) const override;
/// Multiply a vector with the transpose matrix.
void MultTranspose(const real_t *x, real_t *y) const;
@@ -165,6 +168,9 @@ public:
/// Multiply a vector with the transpose matrix.
void MultTranspose(const Vector &x, Vector &y) const override;
/// Multiply a vector with the absolute-value transpose matrix.
void AbsMultTranspose(const Vector &x, Vector &y) const override;
using Operator::Mult;
using Operator::MultTranspose;
+9
View File
@@ -778,10 +778,19 @@ public:
of the matrix A. */
void AbsMult(real_t a, const Vector &x, real_t b, Vector &y) const;
/// @brief Computes y = |A| * x, using entry-wise absolute values of the matrix A.
void AbsMult(const Vector &x, Vector &y) const override
{ AbsMult(1.0, x, 0.0, y); }
/** @brief Computes y = a * |At| * x + b * y, using entry-wise absolute
values of the transpose of the matrix A. */
void AbsMultTranspose(real_t a, const Vector &x, real_t b, Vector &y) const;
/** @brief Computes y = |At| * x, using entry-wise absolute values of the
matrix A. */
void AbsMultTranspose(const Vector &x, Vector &y) const override
{ AbsMultTranspose(1.0, x, 0.0, y); }
/** @brief The "Boolean" analog of y = alpha * A * x + beta * y, where
elements in the sparsity pattern of the matrix are treated as "true". */
void BooleanMult(int alpha, const int *x, int beta, int *y)
+63
View File
@@ -188,6 +188,40 @@ void Mult(const int height, const int width, const TA *data, const TX *x, TY *y)
}
}
/** @brief Absolute-value matrix vector multiplication: y = |A| x, where the
matrix A is of size @a height x @a width with given @a data, while @a x and
@a y specify the data of the input and output vectors. */
template<typename TA, typename TX, typename TY>
MFEM_HOST_DEVICE inline
void AbsMult(const int height, const int width, const TA *data,
const TX *x, TY *y)
{
if (width == 0)
{
for (int row = 0; row < height; row++)
{
y[row] = 0.0;
}
return;
}
const TA *d_col = data;
TX x_col = x[0];
for (int row = 0; row < height; row++)
{
y[row] = x_col*std::fabs(d_col[row]);
}
d_col += height;
for (int col = 1; col < width; col++)
{
x_col = x[col];
for (int row = 0; row < height; row++)
{
y[row] += x_col*std::fabs(d_col[row]);
}
d_col += height;
}
}
/** @brief Matrix transpose vector multiplication: y = At x, where the matrix A
is of size @a height x @a width with given @a data, while @a x and @a y
specify the data of the input and output vectors. */
@@ -217,6 +251,35 @@ void MultTranspose(const int height, const int width, const TA *data,
}
}
/** @brief Absolute-value matrix transpose vector multiplication: y = |At| x,
where the matrix A is of size @a height x @a width with given @a data, while
@a x and @a y specify the data of the input and output vectors. */
template<typename TA, typename TX, typename TY>
MFEM_HOST_DEVICE inline
void AbsMultTranspose(const int height, const int width, const TA *data,
const TX *x, TY *y)
{
if (height == 0)
{
for (int row = 0; row < width; row++)
{
y[row] = 0.0;
}
return;
}
TY *y_off = y;
for (int i = 0; i < width; ++i)
{
TY val = 0.0;
for (int j = 0; j < height; ++j)
{
val += x[j] * std::fabs(data[i * height + j]);
}
*y_off = val;
y_off++;
}
}
/// Symmetrize a square matrix with given @a size and @a data: A -> (A+A^T)/2.
template<typename T>
MFEM_HOST_DEVICE inline
+74
View File
@@ -645,18 +645,92 @@ void ConstrainedOperator::ConstrainedMult(const Vector &x, Vector &y,
}
}
void ConstrainedOperator::ConstrainedAbsMult(const Vector &x, Vector &y,
const bool transpose) const
{
const int csz = constraint_list.Size();
if (csz == 0)
{
if (transpose)
{
A->AbsMultTranspose(x, y);
}
else
{
A->AbsMult(x, y);
}
return;
}
z = x;
auto idx = constraint_list.Read();
// Use read+write access - we are modifying sub-vector of z
auto d_z = z.ReadWrite();
mfem::forall(csz, [=] MFEM_HOST_DEVICE (int i) { d_z[idx[i]] = 0.0; });
if (transpose)
{
A->AbsMultTranspose(z, y);
}
else
{
A->AbsMult(z, y);
}
auto d_x = x.Read();
// Use read+write access - we are modifying sub-vector of y
auto d_y = y.ReadWrite();
switch (diag_policy)
{
case DIAG_ONE:
mfem::forall(csz, [=] MFEM_HOST_DEVICE (int i)
{
const int id = idx[i];
d_y[id] = d_x[id];
});
break;
case DIAG_ZERO:
mfem::forall(csz, [=] MFEM_HOST_DEVICE (int i)
{
const int id = idx[i];
d_y[id] = 0.0;
});
break;
case DIAG_KEEP:
// Needs action of the operator diagonal on vector
mfem_error("ConstrainedOperator::AbsMult #1");
break;
default:
mfem_error("ConstrainedOperator::AbsMult #2");
break;
}
}
void ConstrainedOperator::Mult(const Vector &x, Vector &y) const
{
constexpr bool transpose = false;
ConstrainedMult(x, y, transpose);
}
void ConstrainedOperator::AbsMult(const Vector &x, Vector &y) const
{
constexpr bool transpose = false;
ConstrainedAbsMult(x, y, transpose);
}
void ConstrainedOperator::MultTranspose(const Vector &x, Vector &y) const
{
constexpr bool transpose = true;
ConstrainedMult(x, y, transpose);
}
void ConstrainedOperator::AbsMultTranspose(const Vector &x, Vector &y) const
{
constexpr bool transpose = true;
ConstrainedAbsMult(x, y, transpose);
}
void ConstrainedOperator::AddMult(const Vector &x, Vector &y,
const real_t a) const
{
+38 -6
View File
@@ -88,10 +88,22 @@ public:
/// Operator application: `y=A(x)`.
virtual void Mult(const Vector &x, Vector &y) const = 0;
/** @brief Action of the absolute-value operator: `y=|A|(x)`. The default
behavior in class Operator is to generate an error. If the Operator is a
composition of several operators, the composition unfold into a product
of absolute-value operators too. */
virtual void AbsMult(const Vector &x, Vector &y) const
{ MFEM_ABORT("Operator::AbsMult() is not overridden!"); }
/** @brief Action of the transpose operator: `y=A^t(x)`. The default behavior
in class Operator is to generate an error. */
virtual void MultTranspose(const Vector &x, Vector &y) const
{ mfem_error("Operator::MultTranspose() is not overridden!"); }
{ MFEM_ABORT("Operator::MultTranspose() is not overridden!"); }
/** @brief Action of the transpose absolute-value operator: `y=|A|^t(x)`.
The default behavior in class Operator is to generate an error. */
virtual void AbsMultTranspose(const Vector &x, Vector &y) const
{ MFEM_ABORT("Operator::AbsMultTranspose() is not overridden!"); }
/// Operator application: `y+=A(x)` (default) or `y+=a*A(x)`.
virtual void AddMult(const Vector &x, Vector &y, const real_t a = 1.0) const;
@@ -121,7 +133,7 @@ public:
behavior in class Operator is to generate an error. */
virtual Operator &GetGradient(const Vector &x) const
{
mfem_error("Operator::GetGradient() is not overridden!");
MFEM_ABORT("Operator::GetGradient() is not overridden!");
return const_cast<Operator &>(*this);
}
@@ -691,7 +703,7 @@ public:
const Vector &xB, const Vector &fxB,
int jokB, int *jcurB, real_t gammaB)
{
mfem_error("TimeDependentAdjointOperator::SUNImplicitSetupB() is not "
MFEM_ABORT("TimeDependentAdjointOperator::SUNImplicitSetupB() is not "
"overridden!");
return (-1);
}
@@ -709,7 +721,7 @@ public:
see the SUNDIALS User Guides. */
virtual int SUNImplicitSolveB(Vector &x, const Vector &b, real_t tol)
{
mfem_error("TimeDependentAdjointOperator::SUNImplicitSolveB() is not "
MFEM_ABORT("TimeDependentAdjointOperator::SUNImplicitSolveB() is not "
"overridden!");
return (-1);
}
@@ -930,6 +942,10 @@ public:
void Mult(const Vector & x, Vector & y) const override
{ P.Mult(x, Px); A.Mult(Px, APx); Rt.MultTranspose(APx, y); }
/// Operator-wise absolute-value application.
void AbsMult(const Vector & x, Vector & y) const override
{ P.AbsMult(x, Px); A.AbsMult(Px, APx); Rt.AbsMultTranspose(APx, y); }
/// Approximate diagonal of the RAP Operator.
/** Returns the diagonal of A, as returned by its AssembleDiagonal method,
multiplied be P^T.
@@ -950,6 +966,14 @@ public:
/// Application of the transpose.
void MultTranspose(const Vector & x, Vector & y) const override
{ Rt.Mult(x, APx); A.MultTranspose(APx, Px); P.MultTranspose(Px, y); }
/// Operator-wise absolute-value application of the transpose
void AbsMultTranspose(const Vector & x, Vector & y) const override
{
Rt.AbsMult(x, APx);
A.AbsMultTranspose(APx, Px);
P.AbsMultTranspose(Px, y);
}
};
@@ -1045,13 +1069,21 @@ public:
void AddMult(const Vector &x, Vector &y, const real_t a = 1.0) const override;
void AbsMult(const Vector &x, Vector &y) const override;
void MultTranspose(const Vector &x, Vector &y) const override;
void AbsMultTranspose(const Vector &x, Vector &y) const override;
/** @brief Implementation of Mult or MultTranspose.
* TODO - Generalize to allow constraining rows and columns differently.
*/
TODO - Generalize to allow constraining rows and columns differently. */
void ConstrainedMult(const Vector &x, Vector &y, const bool transpose) const;
/** @brief Implementation of AbsMult or AbsMultTranspose.
TODO - Generalize to allow constraining rows and columns differently. */
void ConstrainedAbsMult(const Vector &x, Vector &y,
const bool transpose) const;
/// Destructor: destroys the unconstrained Operator, if owned.
~ConstrainedOperator() override { if (own_A) { delete A; } }
};
+8 -7
View File
@@ -624,7 +624,7 @@ void SLISolver::Mult(const Vector &b, Vector &x) const
}
r0 = std::max(nom*rel_tol, abs_tol);
if (nom <= r0)
if (Monitor(0, nom, r, x) || nom <= r0)
{
converged = true;
final_iter = 0;
@@ -665,18 +665,13 @@ void SLISolver::Mult(const Vector &b, Vector &x) const
nomold = nom;
bool done = false;
if (nom < r0)
if (Monitor(i, nom, r, x) || nom < r0)
{
converged = true;
final_iter = i;
done = true;
}
if (++i > max_iter)
{
done = true;
}
if (print_options.iterations || (done && print_options.first_and_last))
{
mfem::out << " Iteration : " << setw(3) << right << (i-1)
@@ -684,6 +679,11 @@ void SLISolver::Mult(const Vector &b, Vector &x) const
<< "\tConv. rate: " << cf << '\n';
}
if (++i > max_iter)
{
done = true;
}
if (done) { break; }
}
@@ -700,6 +700,7 @@ void SLISolver::Mult(const Vector &b, Vector &x) const
}
final_norm = nom;
Monitor(final_iter, final_norm, r, x, true);
}
void SLI(const Operator &A, const Vector &b, Vector &x,
+2 -2
View File
@@ -422,13 +422,13 @@ public:
void BooleanMultTranspose(const Array<int> &x, Array<int> &y) const;
/// y = |A| * x, using entry-wise absolute values of matrix A
void AbsMult(const Vector &x, Vector &y) const;
void AbsMult(const Vector &x, Vector &y) const override;
/// y = |At| * x, using entry-wise absolute values of the transpose of matrix A
/** If the matrix is modified, call ResetTranspose() and optionally
EnsureMultTranspose() to make sure this method uses the correct updated
transpose. */
void AbsMultTranspose(const Vector &x, Vector &y) const;
void AbsMultTranspose(const Vector &x, Vector &y) const override;
/// Compute y^t A x
real_t InnerProduct(const Vector &x, const Vector &y) const;
+219 -194
View File
@@ -11,19 +11,18 @@
// Implementation of data type vector
#include "kernels.hpp"
#include "vector.hpp"
#include "../general/forall.hpp"
#include "../general/reducers.hpp"
#include "../general/hash.hpp"
#include "vector.hpp"
#ifdef MFEM_USE_OPENMP
#include <omp.h>
#endif
#include <iostream>
#include <iomanip>
#include <cmath>
#include <ctime>
#include <limits>
namespace mfem
{
@@ -207,7 +206,7 @@ Vector &Vector::operator=(const Vector &v)
UseDevice(v.UseDevice());
#else
SetSize(v.Size());
bool vuse = v.UseDevice();
const bool vuse = v.UseDevice();
const bool use_dev = UseDevice() || vuse;
v.UseDevice(use_dev);
// keep 'data' where it is, unless 'use_dev' is true
@@ -249,8 +248,8 @@ Vector &Vector::operator*=(const Vector &v)
const bool use_dev = UseDevice() || v.UseDevice();
const int N = size;
const auto x = v.Read(use_dev);
auto y = ReadWrite(use_dev);
auto x = v.Read(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] *= x[i]; });
return *this;
}
@@ -271,8 +270,8 @@ Vector &Vector::operator/=(const Vector &v)
const bool use_dev = UseDevice() || v.UseDevice();
const int N = size;
const auto x = v.Read(use_dev);
auto y = ReadWrite(use_dev);
auto x = v.Read(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] /= x[i]; });
return *this;
}
@@ -292,8 +291,8 @@ Vector &Vector::operator-=(const Vector &v)
const bool use_dev = UseDevice() || v.UseDevice();
const int N = size;
const auto x = v.Read(use_dev);
auto y = ReadWrite(use_dev);
auto x = v.Read(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] -= x[i]; });
return *this;
}
@@ -313,8 +312,8 @@ Vector &Vector::operator+=(const Vector &v)
const bool use_dev = UseDevice() || v.UseDevice();
const int N = size;
const auto x = v.Read(use_dev);
auto y = ReadWrite(use_dev);
auto x = v.Read(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] += x[i]; });
return *this;
}
@@ -327,8 +326,8 @@ Vector &Vector::Add(const real_t a, const Vector &Va)
{
const int N = size;
const bool use_dev = UseDevice() || Va.UseDevice();
const auto x = Va.Read(use_dev);
auto y = ReadWrite(use_dev);
auto x = Va.Read(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] += a * x[i]; });
}
return *this;
@@ -340,7 +339,7 @@ Vector &Vector::Set(const real_t a, const Vector &Va)
const bool use_dev = UseDevice() || Va.UseDevice();
const int N = size;
auto x = Va.Read(use_dev);
const auto x = Va.Read(use_dev);
auto y = Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] = a * x[i]; });
return *this;
@@ -352,9 +351,9 @@ void Vector::SetVector(const Vector &v, int offset)
const bool use_dev = UseDevice() || v.UseDevice();
const int vs = v.Size();
const real_t *vp = v.Read(use_dev);
const auto vp = v.Read(use_dev);
// Use read+write access for *this - we only modify some of its entries
real_t *p = ReadWrite(use_dev) + offset;
auto p = ReadWrite(use_dev) + offset;
mfem::forall_switch(use_dev, vs, [=] MFEM_HOST_DEVICE (int i) { p[i] = vp[i]; });
}
@@ -364,8 +363,8 @@ void Vector::AddSubVector(const Vector &v, int offset)
const bool use_dev = UseDevice() || v.UseDevice();
const int vs = v.Size();
const real_t *vp = v.Read(use_dev);
real_t *p = ReadWrite(use_dev) + offset;
const auto vp = v.Read(use_dev);
auto p = ReadWrite(use_dev) + offset;
mfem::forall_switch(use_dev, vs, [=] MFEM_HOST_DEVICE (int i) { p[i] += vp[i]; });
}
@@ -385,6 +384,28 @@ void Vector::Reciprocal()
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] = 1.0/y[i]; });
}
void Vector::Abs()
{
const bool use_dev = UseDevice();
const int N = size;
auto y = ReadWrite(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
{
y[i] = std::abs(y[i]);
});
}
void Vector::Pow(const real_t p)
{
const bool use_dev = UseDevice();
const int N = size;
auto y = ReadWrite(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
{
y[i] = std::pow(y[i], p);
});
}
void add(const Vector &v1, const Vector &v2, Vector &v)
{
MFEM_ASSERT(v.size == v1.size && v.size == v2.size,
@@ -394,8 +415,8 @@ void add(const Vector &v1, const Vector &v2, Vector &v)
const bool use_dev = v1.UseDevice() || v2.UseDevice() || v.UseDevice();
const int N = v.size;
// Note: get read access first, in case v is the same as v1/v2.
auto x1 = v1.Read(use_dev);
auto x2 = v2.Read(use_dev);
const auto x1 = v1.Read(use_dev);
const auto x2 = v2.Read(use_dev);
auto y = v.Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] = x1[i] + x2[i]; });
#else
@@ -426,8 +447,8 @@ void add(const Vector &v1, real_t alpha, const Vector &v2, Vector &v)
const bool use_dev = v1.UseDevice() || v2.UseDevice() || v.UseDevice();
const int N = v.size;
// Note: get read access first, in case v is the same as v1/v2.
auto d_x = v1.Read(use_dev);
auto d_y = v2.Read(use_dev);
const auto d_x = v1.Read(use_dev);
const auto d_y = v2.Read(use_dev);
auto d_z = v.Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
{
@@ -465,8 +486,8 @@ void add(const real_t a, const Vector &x, const Vector &y, Vector &z)
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
const int N = x.size;
// Note: get read access first, in case z is the same as x/y.
auto xd = x.Read(use_dev);
auto yd = y.Read(use_dev);
const auto xd = x.Read(use_dev);
const auto yd = y.Read(use_dev);
auto zd = z.Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
{
@@ -520,8 +541,8 @@ void add(const real_t a, const Vector &x,
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
const int N = x.size;
// Note: get read access first, in case z is the same as x/y.
auto xd = x.Read(use_dev);
auto yd = y.Read(use_dev);
const auto xd = x.Read(use_dev);
const auto yd = y.Read(use_dev);
auto zd = z.Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
{
@@ -550,8 +571,8 @@ void subtract(const Vector &x, const Vector &y, Vector &z)
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
const int N = x.size;
// Note: get read access first, in case z is the same as x/y.
auto xd = x.Read(use_dev);
auto yd = y.Read(use_dev);
const auto xd = x.Read(use_dev);
const auto yd = y.Read(use_dev);
auto zd = z.Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
{
@@ -589,8 +610,8 @@ void subtract(const real_t a, const Vector &x, const Vector &y, Vector &z)
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
const int N = x.size;
// Note: get read access first, in case z is the same as x/y.
auto xd = x.Read(use_dev);
auto yd = y.Read(use_dev);
const auto xd = x.Read(use_dev);
const auto yd = y.Read(use_dev);
auto zd = z.Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
{
@@ -631,8 +652,8 @@ void Vector::median(const Vector &lo, const Vector &hi)
const bool use_dev = UseDevice() || lo.UseDevice() || hi.UseDevice();
const int N = size;
// Note: get read access first, in case *this is the same as lo/hi.
auto l = lo.Read(use_dev);
auto h = hi.Read(use_dev);
const auto l = lo.Read(use_dev);
const auto h = hi.Read(use_dev);
auto m = Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
{
@@ -652,9 +673,9 @@ void Vector::GetSubVector(const Array<int> &dofs, Vector &elemvect) const
const int n = dofs.Size();
elemvect.SetSize(n);
const bool use_dev = dofs.UseDevice() || elemvect.UseDevice();
const auto d_X = Read(use_dev);
const auto d_dofs = dofs.Read(use_dev);
auto d_y = elemvect.Write(use_dev);
auto d_X = Read(use_dev);
auto d_dofs = dofs.Read(use_dev);
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
{
const int dof_i = d_dofs[i];
@@ -664,7 +685,7 @@ void Vector::GetSubVector(const Array<int> &dofs, Vector &elemvect) const
void Vector::GetSubVector(const Array<int> &dofs, real_t *elem_data) const
{
data.Read(MemoryClass::HOST, size);
HostRead();
const int n = dofs.Size();
for (int i = 0; i < n; i++)
{
@@ -679,7 +700,7 @@ void Vector::SetSubVector(const Array<int> &dofs, const real_t value)
const int n = dofs.Size();
// Use read+write access for *this - we only modify some of its entries
auto d_X = ReadWrite(use_dev);
auto d_dofs = dofs.Read(use_dev);
const auto d_dofs = dofs.Read(use_dev);
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
{
const int j = d_dofs[i];
@@ -721,8 +742,8 @@ void Vector::SetSubVector(const Array<int> &dofs, const Vector &elemvect)
const int n = dofs.Size();
// Use read+write access for X - we only modify some of its entries
auto d_X = ReadWrite(use_dev);
auto d_y = elemvect.Read(use_dev);
auto d_dofs = dofs.Read(use_dev);
const auto d_y = elemvect.Read(use_dev);
const auto d_dofs = dofs.Read(use_dev);
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
{
const int dof_i = d_dofs[i];
@@ -740,7 +761,7 @@ void Vector::SetSubVector(const Array<int> &dofs, const Vector &elemvect)
void Vector::SetSubVector(const Array<int> &dofs, real_t *elem_data)
{
// Use read+write access because we overwrite only part of the data.
data.ReadWrite(MemoryClass::HOST, size);
HostReadWrite();
const int n = dofs.Size();
for (int i = 0; i < n; i++)
{
@@ -764,9 +785,9 @@ void Vector::AddElementVector(const Array<int> &dofs, const Vector &elemvect)
const bool use_dev = dofs.UseDevice() || elemvect.UseDevice();
const int n = dofs.Size();
auto d_y = elemvect.Read(use_dev);
const auto d_y = elemvect.Read(use_dev);
const auto d_dofs = dofs.Read(use_dev);
auto d_X = ReadWrite(use_dev);
auto d_dofs = dofs.Read(use_dev);
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
{
const int j = d_dofs[i];
@@ -783,7 +804,7 @@ void Vector::AddElementVector(const Array<int> &dofs, const Vector &elemvect)
void Vector::AddElementVector(const Array<int> &dofs, real_t *elem_data)
{
data.ReadWrite(MemoryClass::HOST, size);
HostReadWrite();
const int n = dofs.Size();
for (int i = 0; i < n; i++)
{
@@ -808,9 +829,9 @@ void Vector::AddElementVector(const Array<int> &dofs, const real_t a,
const bool use_dev = dofs.UseDevice() || elemvect.UseDevice();
const int n = dofs.Size();
const auto d_x = elemvect.Read(use_dev);
const auto d_dofs = dofs.Read(use_dev);
auto d_y = ReadWrite(use_dev);
auto d_x = elemvect.Read(use_dev);
auto d_dofs = dofs.Read(use_dev);
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
{
const int j = d_dofs[i];
@@ -835,7 +856,7 @@ void Vector::SetSubVectorComplement(const Array<int> &dofs, const real_t val)
Device::GetHostMemoryType());
auto d_data = ReadWrite(use_dev);
auto d_dofs_vals = dofs_vals.Write(use_dev);
auto d_dofs = dofs.Read(use_dev);
const auto d_dofs = dofs.Read(use_dev);
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i) { d_dofs_vals[i] = d_data[d_dofs[i]]; });
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { d_data[i] = val; });
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i) { d_data[d_dofs[i]] = d_dofs_vals[i]; });
@@ -844,7 +865,7 @@ void Vector::SetSubVectorComplement(const Array<int> &dofs, const real_t val)
void Vector::Print(std::ostream &os, int width) const
{
if (!size) { return; }
data.Read(MemoryClass::HOST, size);
HostRead();
for (int i = 0; 1; )
{
os << ZeroSubnormal(data[i]);
@@ -870,7 +891,7 @@ void Vector::Print(adios2stream &os,
const std::string& variable_name) const
{
if (!size) { return; }
data.Read(MemoryClass::HOST, size);
HostRead();
os.engine.Put(variable_name, &data[0] );
}
#endif
@@ -928,10 +949,7 @@ void Vector::PrintHash(std::ostream &os) const
void Vector::Randomize(int seed)
{
if (seed == 0)
{
seed = (int)time(0);
}
if (seed == 0) { seed = (int)time(0); }
srand((unsigned)seed);
@@ -947,20 +965,15 @@ real_t Vector::Norml2() const
// Scale entries of Vector on the fly, using algorithms from
// std::hypot() and LAPACK's drm2. This scaling ensures that the
// argument of each call to std::pow is <= 1 to avoid overflow.
if (size == 0)
{
return 0.0;
}
if (size == 0) { return 0.0; }
auto m_data = Read(UseDevice());
const auto m_data = Read(UseDevice());
using value_type = DevicePair<real_t, real_t>;
value_type res;
res.first = 0;
res.second = 0;
// first compute sum (|m_data|/scale)^2
reduce(
size, res,
[=] MFEM_HOST_DEVICE(int i, value_type &r)
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, value_type &r)
{
real_t n = fabs(m_data[i]);
if (n > 0)
@@ -987,11 +1000,12 @@ real_t Vector::Normlinf() const
{
if (size == 0) { return 0; }
auto m_data = Read(UseDevice());
real_t res = 0;
reduce(
size, res,
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r = fmax(r, fabs(m_data[i])); },
const auto m_data = Read(UseDevice());
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
{
r = fmax(r, fabs(m_data[i]));
},
MaxReducer<real_t> {}, UseDevice(), vector_workspace());
return res;
}
@@ -1000,11 +1014,12 @@ real_t Vector::Norml1() const
{
if (size == 0) { return 0.0; }
auto m_data = Read(UseDevice());
real_t res = 0;
reduce(
size, res,
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r += fabs(m_data[i]); },
const auto m_data = Read(UseDevice());
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
{
r += fabs(m_data[i]);
},
SumReducer<real_t> {}, UseDevice(), vector_workspace());
return res;
}
@@ -1013,33 +1028,24 @@ real_t Vector::Normlp(real_t p) const
{
MFEM_ASSERT(p > 0.0, "Vector::Normlp");
if (p == 1.0)
{
return Norml1();
}
if (p == 2.0)
{
return Norml2();
}
if (p == 1.0) { return Norml1(); }
if (p == 2.0) { return Norml2(); }
if (p < infinity())
{
// Scale entries of Vector on the fly, using algorithms from
// std::hypot() and LAPACK's drm2. This scaling ensures that the
// argument of each call to std::pow is <= 1 to avoid overflow.
if (size == 0)
{
return 0.0;
}
if (size == 0) { return 0.0; }
auto m_data = Read(UseDevice());
using value_type = DevicePair<real_t, real_t>;
value_type res;
res.first = 0;
res.second = 0;
const auto m_data = Read(UseDevice());
// first compute sum (|m_data|/scale)^p
reduce(
size, res,
[=] MFEM_HOST_DEVICE(int i, value_type &r)
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, value_type &r)
{
real_t n = fabs(m_data[i]);
if (n > 0)
@@ -1068,163 +1074,182 @@ real_t Vector::Normlp(real_t p) const
real_t Vector::operator*(const Vector &v) const
{
MFEM_ASSERT(size == v.size, "incompatible Vectors!");
if (size == 0) { return 0.0; }
const bool use_dev = UseDevice() || v.UseDevice();
const auto m_data = Read(use_dev), v_data = v.Read(use_dev);
auto m_data = Read(use_dev);
auto v_data = v.Read(use_dev);
if (use_dev)
{
// special path for OCCA and OpenMP
// If OCCA is enabled, it handles all selected backends
#ifdef MFEM_USE_OCCA
if (DeviceCanUseOcca())
{
return occa::linalg::dot<real_t, real_t, real_t>(
OccaMemoryRead(data, size), OccaMemoryRead(v.data, size));
}
if (use_dev && DeviceCanUseOcca())
{
return occa::linalg::dot<real_t, real_t, real_t>(
OccaMemoryRead(data, size), OccaMemoryRead(v.data, size));
}
#endif
#ifdef MFEM_USE_OPENMP
if (Device::Allows(Backend::OMP_MASK))
const auto compute_dot = [&]()
{
real_t res = 0;
reduce(size, res, [=] MFEM_HOST_DEVICE (int i, real_t &r)
{
r += m_data[i] * v_data[i];
},
SumReducer<real_t> {}, use_dev, vector_workspace());
return res;
};
// Device backends have top priority
if (Device::Allows(Backend::DEVICE_MASK)) { return compute_dot(); }
// Special path for OpenMP
#ifdef MFEM_USE_OPENMP
if (use_dev && Device::Allows(Backend::OMP_MASK))
{
// By default, use a deterministic way of computing the dot product
#define MFEM_USE_OPENMP_DETERMINISTIC_DOT
#ifdef MFEM_USE_OPENMP_DETERMINISTIC_DOT
// By default, use a deterministic way of computing the dot product
static Vector th_dot;
#pragma omp parallel
static Vector th_dot;
#pragma omp parallel
{
const int nt = omp_get_num_threads();
#pragma omp master
th_dot.SetSize(nt);
const int tid = omp_get_thread_num();
const int stride = (size + nt - 1) / nt;
const int start = tid * stride;
const int stop = std::min(start + stride, size);
real_t my_dot = 0.0;
for (int i = start; i < stop; i++)
{
const int nt = omp_get_num_threads();
#pragma omp master
th_dot.SetSize(nt);
const int tid = omp_get_thread_num();
const int stride = (size + nt - 1) / nt;
const int start = tid * stride;
const int stop = std::min(start + stride, size);
real_t my_dot = 0.0;
for (int i = start; i < stop; i++)
{
my_dot += m_data[i] * v_data[i];
}
#pragma omp barrier
th_dot(tid) = my_dot;
my_dot += m_data[i] * v_data[i];
}
return th_dot.Sum();
#else
// The standard way of computing the dot product is non-deterministic
real_t prod = 0.0;
#pragma omp parallel for reduction(+ : prod)
for (int i = 0; i < size; i++)
{
prod += m_data[i] * v_data[i];
}
return prod;
#endif // MFEM_USE_OPENMP_DETERMINISTIC_DOT
#pragma omp barrier
th_dot(tid) = my_dot;
}
#endif // MFEM_USE_OPENMP
return th_dot.Sum();
#else
// The standard way of computing the dot product is non-deterministic
real_t prod = 0.0;
#pragma omp parallel for reduction(+ : prod)
for (int i = 0; i < size; i++)
{
prod += m_data[i] * v_data[i];
}
return prod;
#endif // MFEM_USE_OPENMP_DETERMINISTIC_DOT
}
#endif // MFEM_USE_OPENMP
// normal path for everything else (cuda, hip, debug, cpu)
real_t res = 0;
reduce(
size, res,
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r += m_data[i] * v_data[i]; },
SumReducer<real_t> {}, use_dev, vector_workspace());
return res;
// All other CPU backends
return compute_dot();
}
real_t Vector::Min() const
{
if (size == 0) { return infinity(); }
const bool use_dev = UseDevice();
auto m_data = Read(use_dev);
if (use_dev)
{
// special case for OCCA and OpenMP
const auto use_dev = UseDevice();
const auto m_data = Read(use_dev);
#ifdef MFEM_USE_OCCA
if (DeviceCanUseOcca())
{
return occa::linalg::min<real_t,real_t>(OccaMemoryRead(data, size));
}
#endif
#ifdef MFEM_USE_OPENMP
if (Device::Allows(Backend::OMP_MASK))
{
real_t minimum = m_data[0];
#pragma omp parallel for reduction(min:minimum)
for (int i = 0; i < size; i++)
{
minimum = std::min(minimum, m_data[i]);
}
return minimum;
}
#endif
if (use_dev && DeviceCanUseOcca())
{
return occa::linalg::min<real_t,real_t>(OccaMemoryRead(data, size));
}
#endif
// normal path for everything else (cuda, hip, debug, cpu)
real_t res = infinity();
reduce(
size, res,
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r = fmin(r, m_data[i]); },
MinReducer<real_t> {}, use_dev, vector_workspace());
return res;
const auto compute_min = [&]()
{
real_t res = infinity();
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
{
r = fmin(r, m_data[i]);
},
MinReducer<real_t> {}, use_dev, vector_workspace());
return res;
};
// Device backends have top priority
if (Device::Allows(Backend::DEVICE_MASK)) { return compute_min(); }
// Special path for OpenMP
#ifdef MFEM_USE_OPENMP
if (use_dev && Device::Allows(Backend::OMP_MASK))
{
real_t minimum = m_data[0];
#pragma omp parallel for reduction(min:minimum)
for (int i = 0; i < size; i++)
{
minimum = std::min(minimum, m_data[i]);
}
return minimum;
}
#endif
// All other CPU backends
return compute_min();
}
real_t Vector::Max() const
{
if (size == 0) { return -infinity(); }
const bool use_dev = UseDevice();
auto m_data = Read(use_dev);
const auto use_dev = UseDevice();
const auto m_data = Read(use_dev);
if (use_dev)
{
// special cases where OCCA or OenMP are used
#ifdef MFEM_USE_OCCA
if (DeviceCanUseOcca())
{
return occa::linalg::max<real_t, real_t>(OccaMemoryRead(data, size));
}
#endif
#ifdef MFEM_USE_OPENMP
if (Device::Allows(Backend::OMP_MASK))
{
real_t maximum = m_data[0];
#pragma omp parallel for reduction(max : maximum)
for (int i = 0; i < size; i++)
{
maximum = fmax(maximum, m_data[i]);
}
return maximum;
}
#endif
if (use_dev && DeviceCanUseOcca())
{
return occa::linalg::max<real_t, real_t>(OccaMemoryRead(data, size));
}
#endif
// normal path for everything else (cuda, hip, debug, cpu)
real_t res = -infinity();
reduce(
size, res,
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r = fmax(r, m_data[i]); },
MaxReducer<real_t> {}, use_dev, vector_workspace());
return res;
const auto compute_max = [&]()
{
real_t res = -infinity();
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
{
r = fmax(r, m_data[i]);
},
MaxReducer<real_t> {}, use_dev, vector_workspace());
return res;
};
// Device backends have top priority
if (Device::Allows(Backend::DEVICE_MASK)) { return compute_max(); }
// Special path for OpenMP
#ifdef MFEM_USE_OPENMP
if (use_dev && Device::Allows(Backend::OMP_MASK))
{
real_t maximum = m_data[0];
#pragma omp parallel for reduction(max : maximum)
for (int i = 0; i < size; i++)
{
maximum = fmax(maximum, m_data[i]);
}
return maximum;
}
#endif
// All other CPU backends
return compute_max();
}
real_t Vector::Sum() const
{
if (size == 0) { return 0.0; }
auto m_data = Read(UseDevice());
real_t res = 0;
reduce(
size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r) { r += m_data[i]; },
const auto m_data = Read(UseDevice());
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
{
r += m_data[i];
},
SumReducer<real_t> {}, UseDevice(), vector_workspace());
return res;
}
}
} // namespace mfem
+6
View File
@@ -360,6 +360,12 @@ public:
/// (*this)(i) = 1.0 / (*this)(i)
void Reciprocal();
/// (*this)(i) = abs((*this)(i))
void Abs();
/// (*this)(i) = pow((*this)(i), p)
void Pow(const real_t p);
/// Swap the contents of two Vectors
inline void Swap(Vector &other);
+2 -2
View File
@@ -125,11 +125,11 @@ EXAMPLE_TEST_DIRS := examples
MINIAPP_SUBDIRS = common electromagnetics meshing navier performance tools \
toys nurbs gslib adjoint solvers shifted mtop parelag tribol autodiff dfem \
hooke multidomain dpg hdiv-linear-solver spde
hooke multidomain dpg hdiv-linear-solver spde diag-smoothers
MINIAPP_DIRS := $(addprefix miniapps/,$(MINIAPP_SUBDIRS))
MINIAPP_TEST_DIRS := $(filter-out %/common,$(MINIAPP_DIRS))
MINIAPP_USE_COMMON := $(addprefix miniapps/,electromagnetics meshing tools \
toys shifted dpg)
toys shifted dpg diag-smoothers)
EM_DIRS = $(EXAMPLE_DIRS) $(MINIAPP_DIRS)
+127 -181
View File
@@ -3249,6 +3249,19 @@ int Mesh::GetPatchBdrAttribute(int i) const
return NURBSext->GetPatchBdrAttribute(i);
}
void Mesh::GetNURBSPatches(Array<NURBSPatch*> &patches)
{
MFEM_VERIFY(NURBSext, "Must be a NURBS mesh");
// This sets the data in NURBSPatch(es) from the control points (Nodes)
NURBSext->ConvertToPatches(*Nodes);
// Deep copy patches
NURBSext->GetPatches(patches);
// Among other things, this deletes patches in NURBSext
UpdateNURBS();
}
void Mesh::FinalizeTetMesh(int generate_edges, int refine, bool fix_orientation)
{
FinalizeCheck();
@@ -6273,7 +6286,7 @@ void Mesh::UpdateNURBS()
GenerateFaces();
}
void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_knot)
void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_ukv)
{
SetEmpty();
@@ -6313,20 +6326,20 @@ void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_knot)
if (NumOfEdges > 0)
{
edge_vertex = new Table(NumOfEdges, 2);
edge_to_knot.SetSize(NumOfEdges);
edge_to_ukv.SetSize(NumOfEdges);
for (int j = 0; j < NumOfEdges; j++)
{
int *v = edge_vertex->GetRow(j);
input >> edge_to_knot[j] >> v[0] >> v[1];
input >> edge_to_ukv[j] >> v[0] >> v[1];
if (v[0] > v[1])
{
edge_to_knot[j] = -1 - edge_to_knot[j];
edge_to_ukv[j] = -1 - edge_to_ukv[j];
}
}
}
else
{
edge_to_knot.SetSize(0);
edge_to_ukv.SetSize(0);
}
skip_comment_lines(input, '#');
@@ -6338,196 +6351,129 @@ void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_knot)
FinalizeTopology();
CheckBdrElementOrientation(); // check and fix boundary element orientation
/* Generate knot 2 edge mapping -- if edges are not specified in the mesh file
See data/two-squares-nurbs-autoedge.mesh for an example */
if (edge_to_knot.Size() == 0)
/* Generate edge to knotvector mapping if edges are not specified in the
mesh file. See miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh
for an example */
if (edge_to_ukv.Size() == 0)
{
edge_vertex = new Table(NumOfEdges, 2);
edge_to_knot.SetSize(NumOfEdges);
constexpr int notset = -9999999;
edge_to_knot = notset;
Array<int> edges;
Array<int> oedge;
int knot = 0;
Array<int> ukv_to_rpkv;
GetEdgeToUniqueKnotvector(edge_to_ukv, ukv_to_rpkv);
}
}
Array<int> edge0, edge1;
int flip = 1;
if (Dimension() == 2)
void Mesh::GetEdgeToUniqueKnotvector(Array<int> &edge_to_ukv,
Array<int> &ukv_to_rpkv) const
{
const int dim = Dimension(); // topological (not physical) dimension
const int NP = NumOfElements; // number of patches
const int NPKV = NP * dim; // number of patch knotvectors
constexpr int notset = -9999999;
// Sign convention
auto sign = [](int i) { return -1 - i; };
auto unsign = [](int i) { return (i < 0) ? -1 - i : i; };
// Edge index -> dimension convention
auto edge_to_dim = [](int i) { return (i < 8) ? ((i & 1) ? 1 : 0) : 2; };
Array<int> v(2); // vertices of an edge
// 1D case is special: edge index = signed element index
// ukv_to_rpkv = Identity
if (dim == 1)
{
edge_to_ukv.SetSize(NP);
ukv_to_rpkv.SetSize(NP);
for (int i = 0; i < NP; i++)
{
edge0.SetSize(2);
edge1.SetSize(2);
edge0[0] = 0; edge1[0] = 2;
edge0[1] = 1; edge1[1] = 3;
flip = 1;
GetElementVertices(i, v);
// Sign is based on the edge's vertex indices
edge_to_ukv[i] = (v[1] > v[0]) ? i : sign(i);
ukv_to_rpkv[i] = i;
}
else if (Dimension() == 3)
return;
}
// Local (per-patch) variables
Array<int> edges, oedges;
// Edge index -> signed patch knotvector index (p*dim + d)
Array<int> edge_to_pkv(NumOfEdges);
edge_to_pkv.SetSize(NumOfEdges);
edge_to_pkv = notset;
// Initialize pkv_map as identity - this is the storage for the
// disjoint-set/union-find algorithm which will later be used
// to get the map pkv_to_rpkv
Array<int> pkv_map(NPKV);
for (int i = 0; i < NPKV; i++)
{
pkv_map[i] = i;
}
std::function<int(int)> get_root;
get_root = [&pkv_map, &get_root](int i) -> int
{
return (pkv_map[i] == i) ? i : get_root(pkv_map[i]);
};
auto unite = [&pkv_map, &get_root](int i, int j)
{
const int ri = get_root(i);
const int rj = get_root(j);
if (ri == rj) return;
// keep the lowest index
(ri < rj) ? pkv_map[rj] = ri : pkv_map[ri] = rj;
};
// Get edge_to_pkv (one edge can link to multiple pkv) and pkv_map
for (int p = 0; p < NP; p++)
{
GetElementEdges(p, edges, oedges);
// First loop checks for if edge has already been set
for (int i = 0; i < edges.Size(); i++)
{
edge0.SetSize(9);
edge1.SetSize(9);
const int edge = edges[i];
const int d = edge_to_dim(i);
const int pkv = p*dim+d;
edge0[0] = 0; edge1[0] = 2;
edge0[1] = 0; edge1[1] = 4;
edge0[2] = 0; edge1[2] = 6;
edge0[3] = 1; edge1[3] = 3;
edge0[4] = 1; edge1[4] = 5;
edge0[5] = 1; edge1[5] = 7;
edge0[6] = 8; edge1[6] = 9;
edge0[7] = 8; edge1[7] = 10;
edge0[8] = 8; edge1[8] = 11;
flip = -1;
}
/* Initial assignment of knots to edges. This is an algorithm that loops over the
patches and assigns knot vectors to edges. It starts with assigning knot vector 0
and 1 to the edges of the first patch. Then it uses: 1) patches can share edges
2) knot vectors on opposing edges in a patch are equal, to create edge_to_knot */
int e0, e1, v0, v1, df;
int p,j,k;
for (p = 0; p < GetNE(); p++)
{
GetElementEdges(p, edges, oedge);
const int *v = elements[p]->GetVertices();
for (j = 0; j < edges.Size(); j++)
// We've set this edge already - link this index to it
if (edge_to_pkv[edge] != notset)
{
int *vv = edge_vertex->GetRow(edges[j]);
const int *e = elements[p]->GetEdgeVertices(j);
if (oedge[j] == 1)
{
vv[0] = v[e[0]];
vv[1] = v[e[1]];
}
else
{
vv[0] = v[e[1]];
vv[1] = v[e[0]];
}
const int pkv_other = unsign(edge_to_pkv[edge]);
unite(pkv, pkv_other);
}
for (j = 0; j < edge1.Size(); j++)
else
{
e0 = edges[edge0[j]];
e1 = edges[edge1[j]];
v0 = edge_to_knot[e0];
v1 = edge_to_knot[e1];
df = flip*oedge[edge0[j]]*oedge[edge1[j]];
// Case 1: knot vector is not set
if ((v0 == notset) && (v1 == notset))
{
edge_to_knot[e0] = knot;
edge_to_knot[e1] = knot;
knot++;
}
// Case 2 & 3: knot vector on one of the two edges
// is set earlier (in another patch). We just have
// to copy it for the opposing edge.
else if ((v0 != notset) && (v1 == notset))
{
edge_to_knot[e1] = (df >= 0 ? -v0-1 : v0);
}
else if ((v0 == notset) && (v1 != notset))
{
edge_to_knot[e0] = (df >= 0 ? -v1-1 : v1);
}
GetEdgeVertices(edge, v);
// Sign is based on the edge's vertex indices
edge_to_pkv[edge] = (v[1] > v[0]) ? pkv : sign(pkv);
}
}
}
/* Verify correct assignment, make sure that corresponding edges
within patch point to same knot vector. If not assign the lowest number.
// Construct the pkv_to_rpkv map by finding the lowest/root index
Array<int> pkv_to_rpkv(NPKV);
ukv_to_rpkv.SetSize(NPKV);
for (int i = 0; i < NPKV; i++)
{
pkv_to_rpkv[i] = get_root(pkv_map[i]);
ukv_to_rpkv[i] = pkv_to_rpkv[i];
}
ukv_to_rpkv.Sort(); // ukv is just a renumbering of rpkv
ukv_to_rpkv.Unique();
We bound the while by GetNE() + 1 as this is probably the most unlucky
case. +1 to finish without corrections. Note that this is a check and
in general the initial assignment is correct. Then the while is performed
only once. Only on very tricky meshes it might need corrections.*/
int corrections;
int passes = 0;
do
{
corrections = 0;
for (p = 0; p < GetNE(); p++)
{
GetElementEdges(p, edges, oedge);
for (j = 0; j < edge1.Size(); j++)
{
e0 = edges[edge0[j]];
e1 = edges[edge1[j]];
v0 = edge_to_knot[e0];
v1 = edge_to_knot[e1];
v0 = ( v0 >= 0 ? v0 : -v0-1);
v1 = ( v1 >= 0 ? v1 : -v1-1);
if (v0 != v1)
{
corrections++;
if (v0 < v1)
{
edge_to_knot[e1] = (oedge[edge1[j]] >= 0 ? v0 : -v0-1);
}
else if (v1 < v0)
{
edge_to_knot[e0] = (oedge[edge0[j]] >= 0 ? v1 : -v1-1);
}
}
}
}
// Create inverse map
std::map<int, int> rpkv_to_ukv;
for (int i = 0; i < ukv_to_rpkv.Size(); i++)
{
rpkv_to_ukv[ukv_to_rpkv[i]] = i;
}
passes++;
}
while (corrections > 0 && passes < GetNE() + 1);
// Check the validity of corrections applied
if (corrections > 0)
{
mfem::err<<"Edge_to_knot mapping potentially incorrect"<<endl;
mfem::err<<" passes = "<<passes<<endl;
mfem::err<<" corrections = "<<corrections<<endl;
}
/* Renumber knotvectors, such that:
-- numbering is consecutive
-- starts at zero */
Array<int> cnt(NumOfEdges);
cnt = 0;
for (j = 0; j < NumOfEdges; j++)
{
k = edge_to_knot[j];
cnt[(k >= 0 ? k : -k-1)]++;
}
k = 0;
for (j = 0; j < cnt.Size(); j++)
{
cnt[j] = (cnt[j] > 0 ? k++ : -1);
}
for (j = 0; j < NumOfEdges; j++)
{
k = edge_to_knot[j];
edge_to_knot[j] = (k >= 0 ? cnt[k]:-cnt[-k-1]-1);
}
// Print knot to edge mapping
mfem::out<<"Generated edge to knot mapping:"<<endl;
for (j = 0; j < NumOfEdges; j++)
{
int *v = edge_vertex->GetRow(j);
k = edge_to_knot[j];
v0 = v[0];
v1 = v[1];
if (k < 0)
{
v[0] = v1;
v[1] = v0;
}
mfem::out<<(k >= 0 ? k:-k-1)<<" "<< v[0] <<" "<<v[1]<<endl;
}
// Terminate here upon failure after printing to have an idea of edge_to_knot.
if (corrections > 0 ) {mfem_error("Mesh::LoadPatchTopo");}
// Get edge_to_ukv = edge_to_pkv -> pkv_to_rpkv -> rpkv_to_ukv
edge_to_ukv.SetSize(NumOfEdges);
for (int i = 0; i < NumOfEdges; i++)
{
const int pkv = unsign(edge_to_pkv[i]);
const int rpkv = pkv_to_rpkv[pkv];
const int ukv = rpkv_to_ukv[rpkv];
edge_to_ukv[i] = (edge_to_pkv[i] < 0) ? sign(ukv) : ukv;
}
}
+31 -1
View File
@@ -39,6 +39,7 @@ namespace mfem
class GeometricFactors;
class FaceGeometricFactors;
class KnotVector;
class NURBSPatch;
class NURBSExtension;
class FiniteElementSpace;
class GridFunction;
@@ -472,7 +473,7 @@ protected:
const int *fine, int nfine, int op);
/// Read NURBS patch/macro-element mesh
void LoadPatchTopo(std::istream &input, Array<int> &edge_to_knot);
void LoadPatchTopo(std::istream &input, Array<int> &edge_to_ukv);
void UpdateNURBS();
@@ -789,6 +790,29 @@ public:
/// Destroys Mesh.
virtual ~Mesh() { DestroyPointers(); }
/** Get the edge to unique knotvector map used by NURBS patch topology meshes
Various index maps are defined using the following indices:
edge: Edge index in the patch topology mesh
pkv: Patch knotvector index, equivalent to (p * dim + d) where
p is the patch index, dim is the topological dimension of
the patch, and d is the local dimension
rpkv: Root patch knotvector index; the lowest index pkv for all
equivalent pkv.
ukv: (signed) Unique knotvector index. Equivalent to rpkv reordered
from 0 to N-1, where N is the number of unique knotvectors +
sign, which indicates the orientation of the edge.
@param[in,out] edge_to_ukv Array<int> Map from edge index to (signed)
unique knotvector index. Will be resized
to the number of edges.
@param[in,out] ukv_to_rpkv Array<int> Map from (unsigned) unique
knotvector index to the (unsigned) root
patch knotvector index. Will be resized
to the number of unique knotvectors.
*/
void GetEdgeToUniqueKnotvector(Array<int> &edge_to_ukv,
Array<int> &ukv_to_rpkv) const;
/// @}
/** @anchor mfem_Mesh_named_ctors @name Named mesh constructors.
@@ -1435,6 +1459,12 @@ public:
/// Set the attribute of patch boundary element i, for a NURBS mesh.
void SetPatchBdrAttribute(int i, int attr);
/** Returns a deep copy of all patches. This method is not const
as it first sets the patches in NURBSext using control points
defined by Nodes. Caller gets ownership of the returned object,
and is responsible for deletion.*/
void GetNURBSPatches(Array<NURBSPatch*> &patches);
/// Returns the type of element i.
Element::Type GetElementType(int i) const;
+61 -60
View File
@@ -1984,7 +1984,7 @@ NURBSExtension::NURBSExtension(const NURBSExtension &orig)
activeDof(orig.activeDof),
patchTopo(new Mesh(*orig.patchTopo)),
own_topo(true),
edge_to_knot(orig.edge_to_knot),
edge_to_ukv(orig.edge_to_ukv),
knotVectors(orig.knotVectors.Size()), // knotVectors are copied in the body
knotVectorsCompr(orig.knotVectorsCompr.Size()),
weights(orig.weights),
@@ -2025,7 +2025,7 @@ NURBSExtension::NURBSExtension(std::istream &input, bool spacing)
{
// Read topology
patchTopo = new Mesh;
patchTopo->LoadPatchTopo(input, edge_to_knot);
patchTopo->LoadPatchTopo(input, edge_to_ukv);
own_topo = true;
CheckPatches();
@@ -2227,7 +2227,7 @@ NURBSExtension::NURBSExtension(NURBSExtension *parent, int newOrder)
patchTopo = parent->patchTopo;
own_topo = false;
parent->edge_to_knot.Copy(edge_to_knot);
parent->edge_to_ukv.Copy(edge_to_ukv);
NumOfKnotVectors = parent->GetNKV();
knotVectors.SetSize(NumOfKnotVectors);
@@ -2285,7 +2285,7 @@ NURBSExtension::NURBSExtension(NURBSExtension *parent,
patchTopo = parent->patchTopo;
own_topo = false;
parent->edge_to_knot.Copy(edge_to_knot);
parent->edge_to_ukv.Copy(edge_to_ukv);
NumOfKnotVectors = parent->GetNKV();
MFEM_VERIFY(mOrders.Size() == NumOfKnotVectors, "invalid newOrders array");
@@ -2344,7 +2344,7 @@ NURBSExtension::NURBSExtension(Mesh *mesh_array[], int num_pieces)
own_topo = true;
parent->own_topo = false;
parent->edge_to_knot.Copy(edge_to_knot);
parent->edge_to_ukv.Copy(edge_to_ukv);
parent->GetOrders().Copy(mOrders);
mOrder = parent->GetOrder();
@@ -2377,70 +2377,61 @@ NURBSExtension::NURBSExtension(Mesh *mesh_array[], int num_pieces)
}
NURBSExtension::NURBSExtension(const Mesh *patch_topology,
const Array<const NURBSPatch*> p)
const Array<const NURBSPatch*> patches_)
{
// Basic topology checks
MFEM_VERIFY(patches_.Size() > 0, "Must have at least one patch");
MFEM_VERIFY(patches_.Size() == patch_topology->GetNE(),
"Number of patches must equal number of elements in patch_topology");
// Copy patch_topology mesh and NURBSPatch(es)
patchTopo = new Mesh( *patch_topology );
patchTopo->GetEdgeVertexTable();
own_topo = 1;
patches.Reserve(p.Size());
Array<int> edges;
Array<int> oedges;
Array<int> kvs(3);
edge_to_knot.SetSize(patch_topology->GetNEdges());
NumOfKnotVectors = 0;
NumOfElements = 0;
for (int ielem = 0; ielem < patch_topology->GetNE(); ++ielem)
patches.SetSize(patches_.Size());
for (int p = 0; p < patches.Size(); p++)
{
patches.Append(new NURBSPatch(*p[ielem]));
NURBSPatch& patch = *patches[ielem];
int num_patch_elems = 1;
for (int ikv = 0; ikv < patch.GetNKV(); ++ikv)
{
kvs[ikv] = knotVectors.Size();
knotVectors.Append(new KnotVector(*patch.GetKV(ikv)));
num_patch_elems *= patch.GetKV(ikv)->GetNE();
++NumOfKnotVectors;
}
NumOfElements += num_patch_elems;
patch_topology->GetElementEdges(ielem, edges, oedges);
for (int iedge = 0; iedge < edges.Size(); ++iedge)
{
if (iedge < 8)
{
if (iedge & 1)
{
edge_to_knot[edges[iedge]] = kvs[1];
}
else
{
edge_to_knot[edges[iedge]] = kvs[0];
}
}
else
{
edge_to_knot[edges[iedge]] = kvs[2];
}
}
patches[p] = new NURBSPatch(*patches_[p]);
}
GenerateOffsets();
CountBdrElements();
NumOfActiveElems = NumOfElements;
activeElem.SetSize(NumOfElements);
activeElem = true;
Array<int> ukv_to_rpkv;
patchTopo->GetEdgeToUniqueKnotvector(edge_to_ukv, ukv_to_rpkv);
own_topo = true;
CheckPatches(); // This is checking the edge_to_ukv mapping
// Set number of unique (not comprehensive) knot vectors
NumOfKnotVectors = ukv_to_rpkv.Size();
knotVectors.SetSize(NumOfKnotVectors);
knotVectors = NULL;
// Assign the unique knot vectors from patches
for (int i = 0; i < NumOfKnotVectors; i++)
{
// pkv = p*dim + d for an arbitrarily chosen patch p,
// in its reference direction d
const int pkv = ukv_to_rpkv[i];
const int p = pkv / Dimension();
const int d = pkv % Dimension();
knotVectors[i] = new KnotVector(*patches[p]->GetKV(d));
}
CreateComprehensiveKV();
SetOrdersFromKnotVectors();
GenerateOffsets();
CountElements();
CountBdrElements();
NumOfActiveElems = NumOfElements;
activeElem.SetSize(NumOfElements);
activeElem = true;
GenerateActiveVertices();
InitDofMap();
GenerateElementDofTable();
GenerateActiveBdrElems();
GenerateBdrElementDofTable();
weights.SetSize(GetNDof());
CheckPatches();
ConnectBoundaries();
}
NURBSExtension::~NURBSExtension()
@@ -2481,7 +2472,7 @@ void NURBSExtension::Print(std::ostream &os, const std::string &comments) const
}
const int version = kvSpacing.Size() > 0 ? 11 : 10; // v1.0 or v1.1
patchTopo->PrintTopo(os, edge_to_knot, version, comments);
patchTopo->PrintTopo(os, edge_to_ukv, version, comments);
if (patches.Size() == 0)
{
os << "\nknotvectors\n" << NumOfKnotVectors << '\n';
@@ -2936,7 +2927,7 @@ void NURBSExtension::CheckPatches()
for (int i = 0; i < edges.Size(); i++)
{
edges[i] = edge_to_knot[edges[i]];
edges[i] = edge_to_ukv[edges[i]];
if (oedge[i] < 0)
{
edges[i] = -1 - edges[i];
@@ -2954,7 +2945,7 @@ void NURBSExtension::CheckPatches()
edges[8] != edges[11])))
{
mfem::err << "NURBSExtension::CheckPatch (patch = " << p
<< ")\n Inconsistent edge-to-knot mapping!\n";
<< ")\n Inconsistent edge-to-knotvector mapping!";
mfem_error();
}
}
@@ -2971,7 +2962,7 @@ void NURBSExtension::CheckBdrPatches()
for (int i = 0; i < edges.Size(); i++)
{
edges[i] = edge_to_knot[edges[i]];
edges[i] = edge_to_ukv[edges[i]];
if (oedge[i] < 0)
{
edges[i] = -1 - edges[i];
@@ -4878,6 +4869,16 @@ void NURBSExtension::GetElementIJK(int elem, Array<int> & ijk)
el_to_IJK.GetRow(elem, ijk);
}
void NURBSExtension::GetPatches(Array<NURBSPatch*> &patches_copy)
{
const int NP = patches.Size();
patches_copy.SetSize(NP);
for (int p = 0; p < NP; p++)
{
patches_copy[p] = new NURBSPatch(*GetPatch(p));
}
}
void NURBSExtension::SetPatchToElements()
{
const int np = GetNP();
@@ -4982,7 +4983,7 @@ ParNURBSExtension::ParNURBSExtension(MPI_Comm comm, NURBSExtension *parent,
own_topo = true;
parent->own_topo = false;
parent->edge_to_knot.Copy(edge_to_knot);
parent->edge_to_ukv.Copy(edge_to_ukv);
parent->GetOrders().Copy(mOrders);
mOrder = parent->GetOrder();
@@ -5045,7 +5046,7 @@ ParNURBSExtension::ParNURBSExtension(NURBSExtension *parent,
own_topo = parent->own_topo;
parent->own_topo = false;
Swap(edge_to_knot, parent->edge_to_knot);
Swap(edge_to_ukv, parent->edge_to_ukv);
NumOfKnotVectors = parent->NumOfKnotVectors;
Swap(knotVectors, parent->knotVectors);
+17 -6
View File
@@ -469,7 +469,7 @@ protected:
/// Orders of all KnotVectors
Array<int> mOrders;
/// Number of KnotVectors
/// Number of unique (not comprehensive) KnotVectors
int NumOfKnotVectors;
/// Global entity counts
@@ -490,8 +490,8 @@ protected:
/// Whether this object owns patchTopo
bool own_topo;
/// Map from edge indices to KnotVector indices
Array<int> edge_to_knot;
/// Map from patchTopo edge indices to unique KnotVector indices
Array<int> edge_to_ukv;
/// Set of unique KnotVectors
Array<KnotVector *> knotVectors;
@@ -555,7 +555,7 @@ protected:
if the KnotVector index associated with edge @a edge is negative. */
inline const KnotVector *KnotVec(int edge, int oedge, int *okv) const;
/// Throw an error if any patch has an inconsistent edge-to-knot mapping.
/// Throw an error if any patch has an inconsistent edge_to_ukv mapping.
void CheckPatches();
/// Throw an error if any boundary patch has invalid KnotVector orientation.
@@ -673,6 +673,9 @@ protected:
/// Set @a patch_to_bel.
void SetPatchToBdrElements();
/// Return NURBSPatch object; returned object should NOT be deleted.
const NURBSPatch* GetPatch(int patch) const { return patches[patch]; }
/// To be used by ParNURBSExtension constructor(s)
NURBSExtension() : el_dof(nullptr), bel_dof(nullptr) { }
@@ -911,6 +914,14 @@ public:
Cartesian order. */
void GetPatchDofs(const int patch, Array<int> &dofs);
/// Returns a deep copy of the patch topology mesh
Mesh GetPatchTopology() const { return Mesh(*patchTopo); }
/** Returns a deep copy of all instantiated patches. To ensure that patches
are instantiated, use Mesh::GetNURBSPatches() instead. Caller gets
ownership of the returned object, and is responsible for deletion.*/
void GetPatches(Array<NURBSPatch*> &patches);
/// Return the array of indices of all elements in patch @a patch.
const Array<int>& GetPatchElements(int patch);
/// Return the array of indices of all boundary elements in patch @a patch.
@@ -1138,7 +1149,7 @@ inline const real_t &NURBSPatch::operator()(int i, int j, int k, int l) const
inline int NURBSExtension::KnotInd(int edge) const
{
int kv = edge_to_knot[edge];
int kv = edge_to_ukv[edge];
return (kv >= 0) ? kv : (-1-kv);
}
@@ -1155,7 +1166,7 @@ inline const KnotVector *NURBSExtension::KnotVec(int edge) const
inline const KnotVector *NURBSExtension::KnotVec(int edge, int oedge, int *okv)
const
{
int kv = edge_to_knot[edge];
int kv = edge_to_ukv[edge];
if (kv >= 0)
{
*okv = oedge;
+1
View File
@@ -38,3 +38,4 @@ add_subdirectory(hooke)
add_subdirectory(dpg)
add_subdirectory(hdiv-linear-solver)
add_subdirectory(dfem)
add_subdirectory(diag-smoothers)
+17
View File
@@ -253,6 +253,23 @@ void AttrToMarker(int max_attr, const Array<int> &attrs, Array<int> &marker)
}
}
void AffineTransformation::Eval(Vector &V, ElementTransformation &T,
const IntegrationPoint &ip)
{
V = 0.0;
T.Transform(ip, x);
if (A.Height() == vdim)
{
A.Mult(x, V);
}
if (b.Size() == vdim)
{
V.Add(1.0, b);
}
}
void KershawTransformation::Eval(Vector &V, ElementTransformation &T,
const IntegrationPoint &ip)
{
+32
View File
@@ -37,6 +37,38 @@ void MergeMeshNodes(Mesh * mesh, int logging);
marker array will contain all ones. */
void AttrToMarker(int max_attr, const Array<int> &attrs, Array<int> &marker);
/// Transform a mesh according to an arbitrary affine transformation
/// y = A x + b
/// Where A is a spaceDim x spaceDim matrix and b is a vector of size spaceDim.
/// If A is of size zero the transformation will be y = b.
/// If b is of size zero the transformation will be y = A x.
///
/// Note that no error checking related to the determinant of A is performed.
/// If A has a non-positive determinant it is likely to produce an invalid
/// transformed mesh.
class AffineTransformation : public VectorCoefficient
{
private:
DenseMatrix A;
Vector b;
Vector x;
public:
AffineTransformation(int dim_, const DenseMatrix &A_, const Vector & b_)
: VectorCoefficient(dim_), A(A_), b(b_), x(dim_)
{
MFEM_VERIFY((A.Height() == dim_ && A.Width() == dim_) ||
(A.Height() == 0 && A.Width() == 0),
"Affine transformation given an invalid matrix");
MFEM_VERIFY(b.Size() == dim_ || b.Size() == 0,
"Affine transformation given an invalid vector");
}
void Eval(Vector &V, ElementTransformation &T,
const IntegrationPoint &ip) override;
using VectorCoefficient::Eval;
};
/// Generalized Kershaw mesh transformation in 2D and 3D, see D. Kershaw,
/// "Differencing of the diffusion equation in Lagrangian hydrodynamic codes",
+59
View File
@@ -0,0 +1,59 @@
# 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.
if(MFEM_USE_MPI)
add_custom_target(clean_common
COMMAND ${CMAKE_COMMAND} -E echo "Cleaning common directory..."
COMMAND make -C ${CMAKE_CURRENT_SOURCE_DIR}/../common clean
COMMENT "Cleaning common directory..."
)
# Define miniapps
set(MINIAPPS
abs-l1-jacobi
mg-abs-l1-jacobi
)
# Add miniapps
foreach(APP ${MINIAPPS})
add_mfem_miniapp(${APP}
MAIN ${APP}.cpp
${MFEM_MINIAPPS_COMMON_HEADERS}
EXTRA_SOURCES ds-common.cpp
EXTRA_HEADERS ds-common.hpp
LIBRARIES mfem-common mfem
)
endforeach()
# Add the corresponding tests to the "test" target
if (MFEM_ENABLE_TESTING)
add_test(NAME abs-l1-jacobi_np${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS}
$<TARGET_FILE:abs-l1-jacobi> -m ../../data/ref-cube.mesh -rs 2 -rp 2
-s 1 -i 1 -a 3 -pc 1 -no-mon -no-vis
${MPIEXEC_POSTFLAGS})
add_test(NAME mg-abs-l1-jacobi_np${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS}
$<TARGET_FILE:mg-abs-l1-jacobi> -m ../../data/ref-cube.mesh -rs 2 -rp 1
-ol 1 -gl 1 -s 1 -i 1 -a 3 -no-mon -no-vis
${MPIEXEC_POSTFLAGS})
endif()
# Add custom target for cleaning everything
add_custom_target(clean_all
COMMAND ${CMAKE_MAKE_PROGRAM} clean
COMMAND ${CMAKE_MAKE_PROGRAM} -C ${CMAKE_CURRENT_SOURCE_DIR}/../common clean
COMMENT "Cleaning all build artifacts..."
)
endif()
+48
View File
@@ -0,0 +1,48 @@
```
Finite Element Discretization Library
__
_ __ ___ / _| ___ _ __ ___
| '_ ` _ \ | |_ / _ \| '_ ` _ \
| | | | | || _|| __/| | | | | |
|_| |_| |_||_| \___||_| |_| |_|
https://mfem.org
```
This directory contains some drivers reimplementing basic examples in MFEM,
making use of the Abs-Value-L(1)-Jacobi family of preconditioners/smoothers.
Make sure you are familiar with the following examples:
- `ex1p` [Laplace Problem](https://github.com/mfem/mfem/blob/master/examples/ex1p.cpp)
- `ex2p` [Linear Elasticity](https://github.com/mfem/mfem/blob/master/examples/ex2p.cpp)
- `ex3p` [Definite Maxwell Problem](https://github.com/mfem/mfem/blob/master/examples/ex3p.cpp)
- `ex26p` [Multigrid Preconditioner](https://github.com/mfem/mfem/blob/master/examples/ex26p.cpp)
The code has *two* drivers: `abs-l1-jacobi` and `mg-abs-l1-jacobi`. All these
drivers have the capability to solve the following problems:
- An L2-projection into a conforming H1-space.
- A diffusion problem.
- A linear elasticity problem.
- A definite Maxwell problem.
For later reference, we say a smoother `M` is `A`-convergent if `M + M^T - A` is
SPD, this is `(Ax,x) < (Mx, x) + (M^T x, x) = 2 (Mx,x)`. It suffices to find a
constant `c < 2` such that `(Ax,x) < c(Mx,x)` to say that `M` is `A`-convergent.
# Absolute-value L(1)-Jacobi preconditioner for different assembly levels
Our interest lies on `AssemblyLevel::PARTIAL`. As the FEM operator has the
structure `A = P^T G^T B^T D B G P` (see [this](https://mfem.org/performance/)),
and the standard L(1)-Jacobi can be writen as `D_1 = diag( |A|1 ). A triangle
inequality implies `D_{abs} = diag( |P^T| |G^T| |B^T| |D| |B| |G| |P| 1 )` is
also `A`-convergent.
The MFEM interface allows to make use of `AbsMult` with the purpose of unwrap a
composition (of different kind) of operators as their absolute-value
application. Similar run-time options are available.
# Multigrid wrapper
The driver `mg-abs-l1-jacobi` is basically the multigrid counterparts of the
previously mentioned driver. The wrapper (akin to
[`ex26p`](https://github.com/mfem/mfem/blob/master/examples/ex26p.cpp))
allows the user to do geometric refinement or order refinement.
+432
View File
@@ -0,0 +1,432 @@
// 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.
//
// -----------------------------------------
// Absolute L(1)-Jacobi smoothers miniapp
// -----------------------------------------
//
// This miniapp illustrates the implementation of an (slightly generalized)
// absolute-L(1) Jacobi preconditioner. This preconditioner is tested in
// different settings. We use Stationary Linear Iterations and Preconditioned
// Conjugate Gradient as the main solvers.
// We consider a H1-mass matrix, a diffusion matrix, and a definite Maxwell system.
//
// The preconditioner can be defined at run-time. Similarly, the mesh can be
// modified by a Kershaw transformation at run-time. Relative tolerance and
// maximum number of iterations can be modified as well.
//
// Compile with: make abs-l1-jacobi
//
// Sample runs:
// mpirun -np 4 abs-l1-jacobi
// mpirun -np 4 abs-l1-jacobi -s 0 -i 0
// mpirun -np 4 abs-l1-jacobi -m ../meshing/icf.mesh -f 0.5
// mpirun -np 4 abs-l1-jacobi -rs 3 -rp 1
// mpirun -np 4 abs-l1-jacobi -t 1e-5 -ni 100
// mpirun -np 4 abs-l1-jacobi -m ../../data/beam-quad.mesh -a 3 -Ky 0.5 -Kz 0.5
// mpirun -np 4 abs-l1-jacobi --device cuda
#include "ds-common.hpp"
using namespace std;
using namespace mfem;
using namespace ds_common;
int main(int argc, char *argv[])
{
// 1. Initialize MPI and HYPRE.
Mpi::Init(argc, argv);
Hypre::Init();
// 2. Parse command line options.
string mesh_file = "../../data/ref-cube.mesh";
// System properties
int order = 1;
SolverType solver_type = cg;
IntegratorType integrator_type = diffusion;
PCType pc_type = abs_global;
int assembly_type_int = 3; // Default is PARTIAL
AssemblyLevel assembly_type;
// Number of refinements
int refine_serial = 4;
int refine_parallel = 0;
// Preconditioner parameters, only for L(p,q)-Jacobi
real_t p_order = 1.0;
real_t q_order = 0.0;
// Solver parameters
real_t rel_tol = 1e-10;
real_t max_iter = 3000;
// Kershaw Transformation
real_t eps_y = 0.0;
real_t eps_z = 0.0;
// Other options
string device_config = "cpu";
bool use_monitor = false;
bool visualization = true;
// Construct argument parser
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree)");
args.AddOption((int*)&solver_type, "-s", "--solver",
"Solvers to be considered:"
"\n\t0: Stationary Linear Iteration"
"\n\t1: Preconditioned Conjugate Gradient");
args.AddOption((int*)&integrator_type, "-i", "--integrator",
"Integrators to be considered:"
"\n\t0: MassIntegrator"
"\n\t1: DiffusionIntegrator"
"\n\t2: CurlCurlIntegrator + VectorFEMassIntegrator");
args.AddOption(&assembly_type_int, "-a", "--assembly",
"Assembly level to be considered:"
"\n\t0: LEGACY"
"\n\t1: FULL"
"\n\t2: ELEMENT"
"\n\t3: PARTIAL"
"\n\t4: NONE");
args.AddOption((int*)&pc_type, "-pc", "--preconditioner",
"Preconditioners to be considered:"
"\n\t0: No preconditioner"
"\n\t1: Absolute L(1)-Jacobi preconditioner"
"\n\t2: Element L(p,q)-Jacobi preconditioner");
args.AddOption(&refine_serial, "-rs", "--refine-serial",
"Number of serial refinements");
args.AddOption(&refine_parallel, "-rp", "--refine-parallel",
"Number of parallel refinements");
args.AddOption(&p_order, "-p", "--p-order",
"P-order for L(p,q)-Jacobi preconditioner");
args.AddOption(&q_order, "-q", "--q-order",
"Q-order for L(p,q)-Jacobi preconditioner");
args.AddOption(&rel_tol, "-t", "--tolerance",
"Relative tolerance for the iterative solver");
args.AddOption(&max_iter, "-ni", "--iterations",
"Maximum number of iterations");
args.AddOption(&eps_y, "-Ky", "--Kershaw-y",
"Kershaw transform factor, eps_y in (0,1]");
args.AddOption(&eps_z, "-Kz", "--Kershaw-z",
"Kershaw transform factor, eps_z in (0,1]");
args.AddOption(&freq, "-f", "--frequency", "Set the frequency for the exact"
" solution.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&use_monitor, "-mon", "--monitor", "-no-mon",
"--no-monitor",
"Enable or disable Data Monitor.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.ParseCheck();
MFEM_VERIFY(p_order > 0.0, "p needs to be positive");
MFEM_VERIFY((0 <= solver_type) && (solver_type < num_solvers),
"invalid solver type: " << solver_type);
MFEM_VERIFY((0 <= integrator_type) && (integrator_type < num_integrators),
"invalid integrator type: " << integrator_type);
MFEM_VERIFY((0 <= assembly_type_int) && (assembly_type_int < 5),
"invalid assembly type: " << assembly_type_int);
MFEM_VERIFY((0 <= pc_type) && (pc_type < num_pc),
"invalid preconditioner type: " << pc_type);
MFEM_VERIFY((0.0 <= eps_y) && (eps_y <= 1.0), "eps_y must be in [0,1]");
MFEM_VERIFY((0.0 <= eps_z) && (eps_z <= 1.0), "eps_z must be in [0,1]");
kappa = freq * M_PI;
ostringstream file_name;
if (use_monitor)
{
file_name << "ABS-"
<< "O" << order
<< "I" << (int) integrator_type
<< "S" << (int) solver_type
<< "A" << assembly_type_int
<< ".csv";
}
switch (assembly_type_int)
{
case 0:
assembly_type = AssemblyLevel::LEGACY;
break;
case 1:
assembly_type = AssemblyLevel::FULL;
break;
case 2:
assembly_type = AssemblyLevel::ELEMENT;
break;
case 3:
assembly_type = AssemblyLevel::PARTIAL;
break;
case 4:
assembly_type = AssemblyLevel::NONE;
break;
default:
MFEM_ABORT("Unsupported option!");
}
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
// 3. Read the serial mesh from the given mesh file. The number of serial and
// parallel refinements can be set by the user on the command line.
Mesh *serial_mesh = new Mesh(mesh_file);
for (int ls = 0; ls < refine_serial; ls++)
{
serial_mesh->UniformRefinement();
}
// 4. Define a parallel mesh by a partitioning of the serial mesh. The number
// of parallel refinements can be set by the user. If defined, apply
// Kershaw transformation.
ParMesh *mesh = new ParMesh(MPI_COMM_WORLD, *serial_mesh);
delete serial_mesh;
for (int lp = 0; lp < refine_parallel; lp++)
{
mesh->UniformRefinement();
}
dim = mesh->Dimension();
space_dim = mesh->SpaceDimension();
bool cond_z = (dim < 3) ? true : (eps_z != 0); // lazy check
if (eps_y != 0.0 && cond_z)
{
if (dim < 3) { eps_z = 0.0; }
common::KershawTransformation kershawT(dim, eps_y, eps_z);
mesh->Transform(kershawT);
}
// 5. Define a finite element space on the mesh. We use different spaces and
// collections for different systems.
// - H1-conforming Lagrange elements for the H1-mass matrix and the
// diffusion problem.
// - H(curl)-conforming Nedelec elements for the definite Maxwell problem.
FiniteElementCollection *fec;
ParFiniteElementSpace *fespace;
switch (integrator_type)
{
case mass:
case diffusion:
fec = new H1_FECollection(order, dim);
fespace = new ParFiniteElementSpace(mesh, fec);
break;
case maxwell:
fec = new ND_FECollection(order, dim);
fespace = new ParFiniteElementSpace(mesh, fec);
break;
default:
mfem_error("Invalid integrator type! Check FiniteElementCollection");
}
HYPRE_BigInt sys_size = fespace->GlobalTrueVSize();
if (Mpi::Root())
{
mfem::out << "Number of unknowns: " << sys_size << endl;
}
// 6. Extract the list of the essential boundary DoFs. We mark all boundary
// attibutes as essential. Then we get the list of essential DoFs.
Array<int> ess_tdof_list;
Array<int> ess_bdr(mesh->bdr_attributes.Max());
if (mesh->bdr_attributes.Size())
{
ess_bdr = 1;
fespace->GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
}
// 7. Define the linear system. Set up the bilinear form a(.,.) and the
// linear form b(.). The currently implemented systems are the following:
// - (u,v), i.e., L2-projection.
// - (grad(u), grad(v)), i.e., diffusion operator.
// - (curl(u), curl(v)) + (u,v), i.e., definite Maxwell operator.
// The linear form has the standard form (f,v).
// Also, define the matrices and vectors associated with the forms, and
// project the required boundary data into the GridFunction solution.
ParBilinearForm *a = new ParBilinearForm(fespace);
ParLinearForm *b = new ParLinearForm(fespace);
// These pointers are owned by the forms
LinearFormIntegrator *lfi = nullptr;
BilinearFormIntegrator *bfi = nullptr;
// Required for a static_cast
SumIntegrator *sum_bfi = nullptr;
// These pointers are not owned by the integrators
FunctionCoefficient *scalar_u = nullptr;
FunctionCoefficient *scalar_f = nullptr;
VectorFunctionCoefficient *vector_u = nullptr;
VectorFunctionCoefficient *vector_f = nullptr;
ConstantCoefficient one(1.0);
// These variables will define the linear system
ParGridFunction x(fespace), y(fespace);
OperatorPtr A;
Vector B, X;
x = 0.0;
switch (integrator_type)
{
case mass:
scalar_u = new FunctionCoefficient(diffusion_solution);
lfi = new DomainLFIntegrator(*scalar_u);
bfi = new MassIntegrator(one);
x.ProjectBdrCoefficient(*scalar_u, ess_bdr);
break;
case diffusion:
scalar_u = new FunctionCoefficient(diffusion_solution);
scalar_f = new FunctionCoefficient(diffusion_source);
lfi = new DomainLFIntegrator(*scalar_f);
bfi = new DiffusionIntegrator(one);
x.ProjectBdrCoefficient(*scalar_u, ess_bdr);
break;
case maxwell:
vector_u = new VectorFunctionCoefficient(space_dim, maxwell_solution);
vector_f = new VectorFunctionCoefficient(space_dim, maxwell_source);
lfi = new VectorFEDomainLFIntegrator(*vector_f);
bfi = new SumIntegrator();
sum_bfi = static_cast<SumIntegrator*>(bfi);
sum_bfi->AddIntegrator(new CurlCurlIntegrator(one));
sum_bfi->AddIntegrator(new VectorFEMassIntegrator(one));
x.ProjectBdrCoefficientTangent(*vector_u, ess_bdr);
break;
default:
mfem_error("Invalid integrator type! Check ParLinearForm");
}
a->SetAssemblyLevel(assembly_type);
a->AddDomainIntegrator(bfi);
a->Assemble();
b->AddDomainIntegrator(lfi);
b->Assemble();
a->FormLinearSystem(ess_tdof_list, x, *b, A, X, B);
// 8. Construct the preconditioner. Uses AbsMult to construct an appoximation
// of the diagonal of the matrix.
Solver *jacobi = nullptr;
Vector ones(fespace->GetTrueVSize());
Vector diag(fespace->GetTrueVSize());
switch (pc_type)
{
case none:
break;
case abs_global:
ones = 1.0;
A->AbsMult(ones, diag);
jacobi = new OperatorJacobiSmoother(diag, ess_tdof_list);
break;
case pq_element:
AssembleElementLpqJacobiDiag(*a, p_order, q_order, diag);
jacobi = new OperatorJacobiSmoother(diag, ess_tdof_list);
break;
default:
mfem_error("Invalid preconditioner type!");
}
// 9. Construct the solver. The implemented solvers are the following:
// - Stationary Linear Iteration
// - Preconditioned Conjugate Gradient
// Then, solve the system with the used-selected solver.
Solver *solver = nullptr;
DataMonitor *monitor = nullptr;
switch (solver_type)
{
case sli:
solver = new SLISolver(MPI_COMM_WORLD);
break;
case cg:
solver = new CGSolver(MPI_COMM_WORLD);
break;
default:
mfem_error("Invalid solver type!");
}
solver->SetOperator(*A);
IterativeSolver *it_solver = dynamic_cast<IterativeSolver *>(solver);
if (it_solver)
{
it_solver->SetRelTol(rel_tol);
it_solver->SetMaxIter(max_iter);
it_solver->SetPrintLevel(1);
if (use_monitor)
{
monitor = new DataMonitor(file_name.str(), MONITOR_DIGITS);
it_solver->SetMonitor(*monitor);
}
if (jacobi)
{
it_solver->SetPreconditioner(*jacobi);
}
}
solver->Mult(B, X);
// 10. Recover the solution x as a grid function. Send the data by socket to
// a GLVis server.
a->RecoverFEMSolution(X, *b, x);
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream sol_sock(vishost, visport);
sol_sock << "parallel " << Mpi::WorldSize() << " " << Mpi::WorldRank()
<< "\n";
sol_sock.precision(8);
sol_sock << "solution\n" << *mesh << x << flush;
}
// 11. Compute and print the L^2 norm of the error.
{
real_t error = 0.0;
switch (integrator_type)
{
case mass:
case diffusion:
error = x.ComputeL2Error(*scalar_u);
break;
case maxwell:
error = x.ComputeL2Error(*vector_u);
break;
default:
mfem_error("Invalid integrator type! Check ComputeL2Error");
}
if (Mpi::Root())
{
mfem::out << "\n|| u_h - u ||_{L^2} = " << error << "\n" << endl;
}
}
// 12. Free the memory used.
delete solver;
if (jacobi) { delete jacobi; }
delete a;
delete b;
delete fespace;
delete fec;
delete mesh;
if (monitor) { delete monitor; }
if (scalar_u) { delete scalar_u; }
if (scalar_f) { delete scalar_f; }
if (vector_u) { delete vector_u; }
if (vector_f) { delete vector_f; }
return 0;
}
+287
View File
@@ -0,0 +1,287 @@
// 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 "ds-common.hpp"
using namespace std;
using namespace mfem;
namespace ds_common
{
int MONITOR_DIGITS = 20;
int MG_MAX_ITER = 10;
real_t MG_REL_TOL = std::sqrt(1e-10);
int dim = 0;
int space_dim = 0;
real_t freq = 1.0;
real_t kappa = 1.0;
// Custom monitor that prints a csv-formatted file
DataMonitor::DataMonitor(string file_name, int ndigits)
: os(file_name),
precision(ndigits)
{
if (Mpi::Root())
{
mfem::out << "Saving iterations into: " << file_name << endl;
}
os << "it,res,sol" << endl;
os << fixed << setprecision(precision);
}
void DataMonitor::MonitorResidual(int it, real_t norm, const Vector &x,
bool final)
{
os << it << "," << norm << ",";
}
void DataMonitor::MonitorSolution(int it, real_t norm, const Vector &x,
bool final)
{
os << norm << endl;
}
// Abs-L(1) general geometric multigrid method, derived from GeometricMultigrid
AbsL1GeometricMultigrid::AbsL1GeometricMultigrid(
ParFiniteElementSpaceHierarchy& fes_hierarchy,
Array<int>& ess_bdr,
IntegratorType it,
SolverType st,
AssemblyLevel al)
: GeometricMultigrid(fes_hierarchy, ess_bdr),
integrator_type(it),
solver_type(st),
assembly_level(al),
coarse_pc(nullptr),
one(1.0)
{
// BilinearForm::FormSystemMatrix does not handle the ownership of A_l.
// GeometricMultigrid owns the forms, and deletes them.
mg_owned = !(AssemblyLevel::LEGACY == assembly_level);
ConstructCoarseOperatorAndSolver(fes_hierarchy.GetFESpaceAtLevel(0));
for (int l = 1; l < fes_hierarchy.GetNumLevels(); ++l)
{
ConstructOperatorAndSmoother(fes_hierarchy.GetFESpaceAtLevel(l), l);
}
}
void AbsL1GeometricMultigrid::ConstructCoarseOperatorAndSolver(
ParFiniteElementSpace& coarse_fespace)
{
ConstructBilinearForm(coarse_fespace);
OperatorPtr coarse_mat;
coarse_mat.SetType(Operator::ANY_TYPE);
bfs[0]->FormSystemMatrix(*essentialTrueDofs[0], coarse_mat);
coarse_mat.SetOperatorOwner(false);
// Create smoother
Vector local_ones(coarse_mat->Height());
Vector result(coarse_mat->Height());
local_ones = 1.0;
coarse_mat->AbsMult(local_ones, result);
coarse_pc = new OperatorJacobiSmoother(result, *essentialTrueDofs[0]);
Solver* coarse_solver = nullptr;
switch (solver_type)
{
case sli:
coarse_solver = new SLISolver(MPI_COMM_WORLD);
break;
case cg:
coarse_solver = new CGSolver(MPI_COMM_WORLD);
break;
default:
mfem_error("Invalid solver type!");
}
coarse_solver->SetOperator(*coarse_mat);
IterativeSolver *it_solver = dynamic_cast<IterativeSolver*>(coarse_solver);
if (it_solver)
{
it_solver->SetRelTol(MG_REL_TOL);
it_solver->SetMaxIter(MG_MAX_ITER);
it_solver->SetPrintLevel(-1);
it_solver->SetPreconditioner(*coarse_pc);
}
AddLevel(coarse_mat.Ptr(), coarse_solver, mg_owned, true);
}
void AbsL1GeometricMultigrid::ConstructOperatorAndSmoother(
ParFiniteElementSpace& fespace, int level)
{
const Array<int> &ess_tdof_list = *essentialTrueDofs[level];
ConstructBilinearForm(fespace);
OperatorPtr level_mat;
level_mat.SetType(Operator::ANY_TYPE);
bfs.Last()->FormSystemMatrix(ess_tdof_list, level_mat);
level_mat.SetOperatorOwner(false);
// Create smoother
Vector local_ones(level_mat->Height());
Vector result(level_mat->Height());
local_ones = 1.0;
level_mat->AbsMult(local_ones, result);
Solver* smoother = new OperatorJacobiSmoother(result, ess_tdof_list);
AddLevel(level_mat.Ptr(), smoother, mg_owned, true);
}
void AbsL1GeometricMultigrid::ConstructBilinearForm(
ParFiniteElementSpace &fespace)
{
ParBilinearForm* form = new ParBilinearForm(&fespace);
form->SetAssemblyLevel(assembly_level);
switch (integrator_type)
{
case mass:
form->AddDomainIntegrator(new MassIntegrator(one));
break;
case diffusion:
form->AddDomainIntegrator(new DiffusionIntegrator(one));
break;
case maxwell:
form->AddDomainIntegrator(new CurlCurlIntegrator(one));
form->AddDomainIntegrator(new VectorFEMassIntegrator(one));
break;
default:
mfem_error("Invalid integrator type! Check ParBilinearForm");
}
form->Assemble();
bfs.Append(form);
}
void AssembleElementLpqJacobiDiag(ParBilinearForm& form, real_t p, real_t q,
Vector& diag)
{
ParBilinearForm temp_form(form.ParFESpace());
temp_form.AllocateMatrix();
for (int i = 0; i < form.ParFESpace()->GetNE(); ++i)
{
DenseMatrix emat_i;
form.ComputeElementMatrix(i, emat_i);
Vector right(emat_i.Height());
Vector temp(emat_i.Height());
Vector left(emat_i.Height());
DenseMatrix temp_emat_i = emat_i;
for (int j = 0; j < emat_i.Height(); ++j)
{
for (int k = 0; k < emat_i.Width(); ++k)
{
temp_emat_i(j, k) = std::pow(std::abs(emat_i(j, k)), p);
}
}
if (q!=0.0)
{
emat_i.GetDiag(right);
right.Abs();
right.Pow(q);
}
else
{
right = 1.0;
}
temp_emat_i.Mult(right, temp);
if (1.0 + q - p!= 0.0)
{
emat_i.GetDiag(left);
left.Abs();
left.Pow(1.0 + q - p);
left *= temp;
}
else
{
left = temp;
}
temp_emat_i.Clear();
temp_emat_i.Diag(left.GetData(), left.Size());
temp_form.AssembleElementMatrix(i, temp_emat_i, 1);
}
temp_form.Finalize();
auto mat = temp_form.ParallelAssemble();
mat->AssembleDiagonal(diag);
delete mat;
}
real_t diffusion_solution(const Vector &x)
{
if (dim == 3)
{
return sin(kappa * x(0)) * sin(kappa * x(1)) * sin(kappa * x(2)) + 1.0;
}
else
{
return sin(kappa * x(0)) * sin(kappa * x(1)) + 1.0;
}
}
real_t diffusion_source(const Vector &x)
{
if (dim == 3)
{
return dim * kappa * kappa * sin(kappa * x(0)) * sin(kappa * x(1)) *
sin(kappa * x(2));
}
else
{
return dim * kappa * kappa * sin(kappa * x(0)) * sin(kappa * x(1));
}
}
void maxwell_solution(const Vector &x, Vector &u)
{
if (dim == 3)
{
u(0) = sin(kappa * x(1));
u(1) = sin(kappa * x(2));
u(2) = sin(kappa * x(0));
}
else
{
u(0) = sin(kappa * x(1));
u(1) = sin(kappa * x(0));
if (x.Size() == 3) { u(2) = 0.0; }
}
}
void maxwell_source(const Vector &x, Vector &f)
{
if (dim == 3)
{
f(0) = (1. + kappa * kappa) * sin(kappa * x(1));
f(1) = (1. + kappa * kappa) * sin(kappa * x(2));
f(2) = (1. + kappa * kappa) * sin(kappa * x(0));
}
else
{
f(0) = (1. + kappa * kappa) * sin(kappa * x(1));
f(1) = (1. + kappa * kappa) * sin(kappa * x(0));
if (x.Size() == 3) { f(2) = 0.0; }
}
}
} // end namespace ds_common
+124
View File
@@ -0,0 +1,124 @@
// 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_DS_COMMON_HPP
#define MFEM_DS_COMMON_HPP
#include "mfem.hpp"
#include "../common/mfem-common.hpp"
#include <fstream>
#include <iostream>
using namespace std;
using namespace mfem;
namespace ds_common
{
extern int MONITOR_DIGITS;
extern int MG_MAX_ITER;
extern real_t MG_REL_TOL;
extern int dim;
extern int space_dim;
extern real_t freq;
extern real_t kappa;
// Enumerator for the different solvers to implement
enum SolverType
{
sli,
cg,
num_solvers, // last
};
// Enumerator for the different integrators to implement
enum IntegratorType
{
mass,
diffusion,
maxwell,
num_integrators, // last
};
// Enumerator for the different types of preconditioners
enum PCType
{
none,
abs_global,
pq_element,
num_pc, // last
};
/// @brief Custom monitor that prints a csv-formatted file
class DataMonitor : public IterativeSolverMonitor
{
private:
ofstream os;
int precision;
public:
DataMonitor(string file_name, int ndigits);
void MonitorResidual(int it, real_t norm, const Vector &x, bool final);
void MonitorSolution(int it, real_t norm, const Vector &x, bool final);
};
/// @brief Abs-L(1)-Jacobi custom general geometric multigrid method.
///
/// Intermediate levels use Abs-L(1)-Jacobi preconditioner by applying the
/// level matrix to the constant vector one. These are wrapped by an
/// OperatorJacobiSmoother. Coarsest level uses a used-selected solver
/// with an Abs-L(1)-Jacobi smoother. The assembly level is user-defined.
///
/// @warning The construction of the smoother is based on the application of
/// AbsMult, which usually unfolds component-wise. E.g., if `A = B C`, then
/// `|A|x = |B|(|C| x)`.
class AbsL1GeometricMultigrid : public GeometricMultigrid
{
public:
AbsL1GeometricMultigrid(ParFiniteElementSpaceHierarchy& fes_hierarchy,
Array<int>& ess_bdr,
IntegratorType it,
SolverType st,
AssemblyLevel al);
~AbsL1GeometricMultigrid() { delete coarse_pc; }
bool GetOwnershipLevelOperators() const { return mg_owned; }
private:
IntegratorType integrator_type;
SolverType solver_type;
AssemblyLevel assembly_level;
bool mg_owned;
OperatorJacobiSmoother* coarse_pc;
ConstantCoefficient one;
void ConstructCoarseOperatorAndSolver(ParFiniteElementSpace& coarse_fespace);
void ConstructOperatorAndSmoother(ParFiniteElementSpace& fespace, int level);
void ConstructBilinearForm(ParFiniteElementSpace& fespace);
};
void AssembleElementLpqJacobiDiag(ParBilinearForm& form, real_t p, real_t q,
Vector& diag);
real_t diffusion_solution(const Vector &x);
real_t diffusion_source(const Vector &x);
void maxwell_solution(const Vector &x, Vector &u);
void maxwell_source(const Vector &x, Vector &f);
} // namespace ds_common
#endif // MFEM_DS_COMMON_HPP
+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.
# Use the MFEM build directory
MFEM_DIR ?= ../..
MFEM_BUILD_DIR ?= ../..
SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/diag-smoothers/,)
CONFIG_MK = $(MFEM_BUILD_DIR)/config/config.mk
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
MFEM_LIB_FILE = mfem_is_not_built
-include $(DEFAULTS_MK)
-include $(CONFIG_MK)
DS_COMMON_SRC = ds-common.cpp
DS_COMMON_OBJ = $(DS_COMMON_SRC:.cpp=.o)
PAR_MINIAPPS = abs-l1-jacobi mg-abs-l1-jacobi
MINIAPPS = $(if $(MFEM_USE_MPI:NO=),$(PAR_MINIAPPS),)
COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
$(MFEM_BUILD_DIR)/miniapps/common))
APP_DEPS = $(DS_COMMON_OBJ) $(MFEM_LIB_FILE) $(CONFIG_MK)
APP_LIBS = $(COMMON_LIB) $(MFEM_LIBS)
# Phony targets
.PHONY: all lib-common clean clean-build clean-exec test
# Remove built-in rule
%: %.cpp
%.o: %.cpp
# Main targets
all: $(MINIAPPS)
# Build rules
%.o: $(SRC)%.cpp $(wildcard $(SRC)%.hpp) $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) -c $< -o $@
$(MINIAPPS): %: %.o $(APP_DEPS) | lib-common
$(MFEM_CXX) $(MFEM_LINK_FLAGS) $< $(DS_COMMON_OBJ) -o $@ $(APP_LIBS)
# Library and mesh targets
lib-common:
$(MAKE) -C $(MFEM_BUILD_DIR)/miniapps/common
# Test targets
MFEM_TESTS = MINIAPPS
include $(MFEM_TEST_MK)
RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
abs-l1-jacobi-test-par: abs-l1-jacobi
@$(call mfem-test,$<, $(RUN_MPI), Abs-value-L1-Jqcobi miniapp, \
-m ../../data/ref-cube.mesh \
-rs 2 -rp 2 \
-s 1 -i 1 -a 3 \
-pc 1 \
-no-mon)
mg-abs-l1-jacobi-test-par: mg-abs-l1-jacobi
@$(call mfem-test,$<, $(RUN_MPI), MG Abs-value-L1-Jqcobi miniapp, \
-m ../../data/ref-cube.mesh \
-rs 2 -rp 1\
-ol 1 -gl 1 \
-s 1 -i 1 -a 3 \
-no-mon)
# Clean targets
clean: clean-build clean-exec
$(MAKE) -C $(MFEM_BUILD_DIR)/miniapps/common clean
clean-build:
rm -f *.o *~ $(MINIAPPS)
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@true
# Error handling
$(MFEM_LIB_FILE):
$(error The MFEM library is not built)
@@ -0,0 +1,414 @@
// 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.
//
// --------------------------------------
// MG Abs L(1)-Jacobi smoothers miniapp
// --------------------------------------
//
// (See abs-l1-jacobi.cpp first)
//
// This miniapp illustrates the use of an absolute value L(1)-Jacobi smoother.
// We use a multigrid approach (cf. ex26(p)). The global solver and the coarse
// level solver are user-selected. The current options are SLI and PCG. The
// intermediate levels are directy smoothed with the absolute value L(1)-Jacobi
// preconditioner. The systems to solve correspond to a mass matrix, and a
// difussion system.
//
// The preconditioner can be defined at run-time. Similarly, the mesh can be
// modified by a Kershaw transformation at run-time. Relative tolerance and
// maximum number of iterations can be modified as well.
//
// Compile with: make mg-abs-l1-jacobi
//
// Sample runs:
// mpirun -np 4 mg-abs-l1-jacobi
// mpirun -np 4 mg-abs-l1-jacobi -s 0 -i 0
// mpirun -np 4 mg-abs-l1-jacobi -m ../meshing/icf.mesh -f 0.5
// mpirun -np 4 mg-abs-l1-jacobi -rs 2 -rp 1
// mpirun -np 4 mg-abs-l1-jacobi -t 1e-5 -ni 100
// mpirun -np 4 mg-abs-l1-jacobi -m ../../data/beam-quad.mesh -a 3 -Ky 0.5 -Kz 0.5
// mpirun -np 4 mg-abs-l1-jacobi --device cuda
#include "ds-common.hpp"
using namespace std;
using namespace mfem;
using namespace ds_common;
int main(int argc, char *argv[])
{
// 1. Initialize MPI and HYPRE.
Mpi::Init(argc, argv);
Hypre::Init();
// 2. Parse command line options.
string mesh_file = "../../data/ref-cube.mesh";
// System properties
int order = 1;
SolverType solver_type = cg;
IntegratorType integrator_type = diffusion;
int assembly_type_int = 3; // Default is PARTIAL
AssemblyLevel assembly_type;
// Number of refinements
int refine_serial = 3;
int refine_parallel = 0;
// Number of geometric and order levels
int geometric_levels = 1;
int order_levels = 1;
// Solver parameters
real_t rel_tol = 1e-10;
real_t max_iter = 3000;
// Kershaw Transformation
real_t eps_y = 0.0;
real_t eps_z = 0.0;
// Other options
string device_config = "cpu";
bool use_monitor = false;
bool visualization = true;
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree)");
args.AddOption(&geometric_levels, "-gl", "--geometric-levels",
"Number of geometric refinements (levels) done prior to order"
" refinements.");
args.AddOption(&order_levels, "-ol", "--order-levels",
"Number of order refinements (levels). "
"Finest level in the hierarchy has order 2^{or}.");
args.AddOption((int*)&solver_type, "-s", "--solver",
"Solvers to be considered:"
"\n\t0: Stationary Linear Iteration"
"\n\t1: Preconditioned Conjugate Gradient");
args.AddOption((int*)&integrator_type, "-i", "--integrator",
"Integrators to be considered:"
"\n\t0: MassIntegrator"
"\n\t1: DiffusionIntegrator");
args.AddOption(&assembly_type_int, "-a", "--assembly",
"Assembly level to be considered:"
"\n\t0: LEGACY"
"\n\t1: FULL"
"\n\t2: ELEMENT"
"\n\t3: PARTIAL"
"\n\t4: NONE");
args.AddOption(&refine_serial, "-rs", "--refine-serial",
"Number of serial refinements");
args.AddOption(&refine_parallel, "-rp", "--refine-parallel",
"Number of parallel refinements");
args.AddOption(&rel_tol, "-t", "--tolerance",
"Relative tolerance for the iterative solver");
args.AddOption(&max_iter, "-ni", "--iterations",
"Maximum number of iterations");
args.AddOption(&eps_y, "-Ky", "--Kershaw-y",
"Kershaw transform factor, eps_y in (0,1]");
args.AddOption(&eps_z, "-Kz", "--Kershaw-z",
"Kershaw transform factor, eps_z in (0,1]");
args.AddOption(&freq, "-f", "--frequency", "Set the frequency for the exact"
" solution.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&use_monitor, "-mon", "--monitor", "-no-mon",
"--no-monitor",
"Enable or disable Data Monitor.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.ParseCheck();
MFEM_VERIFY((0 <= solver_type) && (solver_type < num_solvers),
"invalid solver type: " << solver_type);
MFEM_VERIFY((0 <= integrator_type) && (integrator_type < num_integrators),
"invalid integrator type: " << integrator_type);
MFEM_VERIFY((0 <= assembly_type_int) && (assembly_type_int < 6),
"invalid assembly type: " << assembly_type_int);
MFEM_VERIFY(geometric_levels >= 0,
"geometric_levels needs to be non-negative");
MFEM_VERIFY(order_levels >= 0, "order_levels needs to be non-negative");
MFEM_VERIFY((0.0 <= eps_y) && (eps_y <= 1.0), "eps_y must be in [0,1]");
MFEM_VERIFY((0.0 <= eps_z) && (eps_z <= 1.0), "eps_z must be in [0,1]");
kappa = freq * M_PI;
ostringstream file_name;
if (use_monitor)
{
file_name << "MGABS-"
<< "G" << geometric_levels
<< "O" << order_levels
<< "O" << order
<< "I" << (int) integrator_type
<< "S" << (int) solver_type
<< "A" << assembly_type_int
<< ".csv";
}
string assembly_description;
switch (assembly_type_int)
{
case 0:
assembly_type = AssemblyLevel::LEGACY;
assembly_description = "Using Legacy type of assembly level...";
break;
case 1:
assembly_type = AssemblyLevel::FULL;
assembly_description = "Using Full type of assembly level...";
break;
case 2:
assembly_type = AssemblyLevel::ELEMENT;
assembly_description = "Using Element type of assembly level...";
break;
case 3:
assembly_type = AssemblyLevel::PARTIAL;
assembly_description = "Using Partial type of assembly level...";
break;
case 4:
assembly_type = AssemblyLevel::NONE;
assembly_description = "Using matrix-free type of assembly level...";
break;
default:
MFEM_ABORT("Unsupported option!");
}
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
// 3. Read the serial mesh from the given mesh file. The number of serial and
// parallel refinements can be set by the user on the command line.
Mesh *serial_mesh = new Mesh(mesh_file);
for (int ls = 0; ls < refine_serial; ls++)
{
serial_mesh->UniformRefinement();
}
// 4. Define a parallel mesh by a partitioning of the serial mesh. The number
// of parallel refinements can be set by the user. If defined, apply
// Kershaw transformation.
ParMesh *mesh = new ParMesh(MPI_COMM_WORLD, *serial_mesh);
delete serial_mesh;
for (int lp = 0; lp < refine_parallel; lp++)
{
mesh->UniformRefinement();
}
dim = mesh->Dimension();
space_dim = mesh->SpaceDimension();
bool cond_z = (dim < 3) ? true : (eps_z != 0.0); // lazy check
if (eps_y != 0.0 && cond_z)
{
if (dim < 3) { eps_z = 0.0; }
common::KershawTransformation kershawT(dim, eps_y, eps_z);
mesh->Transform(kershawT);
}
// 5. Define a finite element space on the mesh. We use different spaces and
// collections for different systems.
// - H1-conforming Lagrange elements for the H1-mass matrix and the
// diffusion problem.
FiniteElementCollection *fec;
ParFiniteElementSpace *coarse_fes;
switch (integrator_type)
{
case mass:
case diffusion:
fec = new H1_FECollection(order, dim);
coarse_fes = new ParFiniteElementSpace(mesh, fec);
break;
case maxwell:
mfem_error("Maxwell integrator not supported in this miniapp!");
default:
mfem_error("Invalid integrator type! Check FiniteElementCollection");
}
if (order > 1)
{
if (Mpi::Root())
{
mfem::out << "Warning! Polynomial order provided. "
<< "Ignoring order level..." << endl;
}
order_levels = 0;
}
// 6. Define a finite element space hierarchy for the multigrid solver.
// Define a FEC array for the order-refinement levels. Add the refinements
// to the hierarchy.
Array<FiniteElementCollection*> fec_array;
fec_array.Append(fec);
// Transfer ownership of mesh and coarse_fes to fes_hierarchy
ParFiniteElementSpaceHierarchy* fes_hierarchy = new
ParFiniteElementSpaceHierarchy(mesh, coarse_fes, true, true);
for (int lg = 0; lg < geometric_levels; ++lg)
{
fes_hierarchy->AddUniformlyRefinedLevel();
}
for (int lo = 0; lo < order_levels; ++lo)
{
switch (integrator_type)
{
case mass:
case diffusion:
fec_array.Append(new H1_FECollection(std::pow(2, lo + 1), dim));
break;
default:
mfem_error("Invalid integrator type! Check "
"FiniteElementCollection for order refinements...");
}
fes_hierarchy->AddOrderRefinedLevel(fec_array.Last());
}
HYPRE_BigInt sys_size = fes_hierarchy->GetFinestFESpace().GlobalTrueVSize();
if (Mpi::Root())
{
mfem::out << "Number of unknowns: " << sys_size << endl;
mfem::out << assembly_description << endl;
}
// 7. Extract the list of the essential boundary DoFs. We mark all boundary
// attibutes as essential. AbsL1GeometricMultigrid will determine the
// DoFs per level.
Array<int> ess_bdr(mesh->bdr_attributes.Max());
if (mesh->bdr_attributes.Size()) { ess_bdr = 1; }
// 8. Define the linear system. Set up the linear form b(.) which has the
// standard form (f,v).
ParLinearForm *b = new ParLinearForm(&fes_hierarchy->GetFinestFESpace());
LinearFormIntegrator *lfi = nullptr;
// These pointers are not owned by the integrators
FunctionCoefficient *scalar_u = nullptr;
FunctionCoefficient *scalar_f = nullptr;
ConstantCoefficient one(1.0);
// These variables will define the linear system
ParGridFunction x(&fes_hierarchy->GetFinestFESpace());
OperatorPtr A;
Vector B, X;
x = 0.0;
switch (integrator_type)
{
case mass:
scalar_u = new FunctionCoefficient(diffusion_solution);
lfi = new DomainLFIntegrator(*scalar_u);
x.ProjectBdrCoefficient(*scalar_u, ess_bdr);
break;
case diffusion:
scalar_u = new FunctionCoefficient(diffusion_solution);
scalar_f = new FunctionCoefficient(diffusion_source);
lfi = new DomainLFIntegrator(*scalar_f);
x.ProjectBdrCoefficient(*scalar_u, ess_bdr);
break;
default:
mfem_error("Invalid integrator type! Check ParLinearForm");
}
b->AddDomainIntegrator(lfi);
b->Assemble();
// 9. Define a geometric multigrid solver. The bilinear form a(.,.) is
// assembled internally. Set up the type of cycles and form the linear
// system.
auto mg = new AbsL1GeometricMultigrid(*fes_hierarchy,
ess_bdr,
integrator_type,
solver_type,
assembly_type);
mg->SetCycleType(Multigrid::CycleType::VCYCLE, 1, 1);
mg->FormFineLinearSystem(x, *b, A, X, B);
A.SetOperatorOwner(mg->GetOwnershipLevelOperators());
Solver *solver = nullptr;
DataMonitor *monitor = nullptr;
switch (solver_type)
{
case sli:
solver = new SLISolver(MPI_COMM_WORLD);
break;
case cg:
solver = new CGSolver(MPI_COMM_WORLD);
break;
default:
mfem_error("Invalid solver type!");
}
solver->SetOperator(*A.Ptr());
IterativeSolver *it_solver = dynamic_cast<IterativeSolver*>(solver);
if (it_solver)
{
it_solver->SetRelTol(rel_tol);
it_solver->SetMaxIter(max_iter);
it_solver->SetPrintLevel(1);
it_solver->SetPreconditioner(*mg);
if (use_monitor)
{
monitor = new DataMonitor(file_name.str(), MONITOR_DIGITS);
it_solver->SetMonitor(*monitor);
}
}
solver->Mult(B, X);
// 10. Recover the solution x as a grid function. Send the data by socket to
// a GLVis server.
mg->RecoverFineFEMSolution(X, *b, x);
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream sol_sock(vishost, visport);
sol_sock << "parallel " << Mpi::WorldSize() << " " << Mpi::WorldRank()
<< "\n";
sol_sock.precision(8);
sol_sock << "solution\n"
<< *fes_hierarchy->GetFinestFESpace().GetParMesh()
<< x << flush;
}
// 11. Compute and print the L^2 norm of the error.
{
real_t error = 0.0;
switch (integrator_type)
{
case mass:
case diffusion:
error = x.ComputeL2Error(*scalar_u);
break;
default:
mfem_error("Invalid integrator type! Check ComputeL2Error");
}
if (Mpi::Root())
{
mfem::out << "\n|| u_h - u ||_{L^2} = " << error << "\n" << endl;
}
}
// 12. Free the memory used.
delete mg;
delete solver;
delete b;
if (monitor) { delete monitor; }
if (scalar_u) { delete scalar_u; }
if (scalar_f) { delete scalar_f; }
for (int level = 0; level < fec_array.Size(); ++level)
{
delete fec_array[level];
}
delete fes_hierarchy;
return 0;
}
+6
View File
@@ -121,6 +121,11 @@ endif()
# Parallel apps.
if (MFEM_USE_MPI)
add_mfem_miniapp(mesh-bounding-boxes
MAIN mesh-bounding-boxes.cpp
${MFEM_MINIAPPS_COMMON_HEADERS}
LIBRARIES mfem-common)
add_mfem_miniapp(pmesh-optimizer
MAIN pmesh-optimizer.cpp
${MFEM_MINIAPPS_COMMON_HEADERS}
@@ -150,6 +155,7 @@ if (MFEM_USE_MPI)
# Add parallel tests.
if (MFEM_ENABLE_TESTING)
set(PARALLEL_TESTS
mesh-bounding-boxes
pmesh-optimizer
pmesh-fitting
fit-node-position
+20 -5
View File
@@ -4,6 +4,8 @@
//
// Sample runs: hpref -dim 2 -n 1000
// hpref -dim 3 -n 500
// hpref -m ../../data/star-mixed.mesh -pref -n 100
// hpref -m ../../data/fichera-mixed.mesh -pref -n 30
//
// Description: This example demonstrates h- and p-refinement in a serial
// finite element discretization of the Poisson problem (cf. ex1)
@@ -38,6 +40,7 @@ void f_exact(const Vector &x, Vector &f);
int main(int argc, char *argv[])
{
// 1. Parse command-line options.
const char *mesh_file = "";
int order = 1;
const char *device_config = "cpu";
bool visualization = true;
@@ -45,8 +48,11 @@ int main(int argc, char *argv[])
int dim = 2;
bool deterministic = true;
bool projectSolution = false;
bool onlyPref = false;
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree) or -1 for"
" isoparametric space.");
@@ -59,10 +65,13 @@ int main(int argc, char *argv[])
args.AddOption(&dim, "-dim", "--dim", "Mesh dimension (2 or 3)");
args.AddOption(&deterministic, "-det", "--deterministic", "-not-det",
"--not-deterministic",
"Whether to use deterministic random refinements");
"Use deterministic random refinements");
args.AddOption(&projectSolution, "-proj", "--project-solution", "-no-proj",
"--no-project",
"Whether to project a coefficient to solution");
"Project a coefficient to solution");
args.AddOption(&onlyPref, "-pref", "--only-p-refinement", "-no-pref",
"--hp-refinement",
"Use only p-refinement");
args.Parse();
if (!args.Good())
{
@@ -76,9 +85,15 @@ int main(int argc, char *argv[])
Device device(device_config);
device.Print();
// 3. Construct a uniform coarse mesh on all processors.
// 3. Construct or load a coarse mesh.
std::string mesh_filename(mesh_file);
Mesh mesh;
if (dim == 3)
if (!mesh_filename.empty())
{
mesh = Mesh::LoadFromFile(mesh_filename, 1, 1);
dim = mesh.Dimension();
}
else if (dim == 3)
{
mesh = Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON);
}
@@ -126,7 +141,7 @@ int main(int argc, char *argv[])
const int r1 = deterministic ? DetRand(seed) : rand();
const int r2 = deterministic ? DetRand(seed) : rand();
const int elem = r1 % mesh.GetNE();
const int hp = r2 % 2;
const int hp = onlyPref ? 1 : r2 % 2;
cout << "hp-refinement iteration " << iter << ": "
<< hp_char[hp] << "-refinement" << endl;
+6 -5
View File
@@ -27,7 +27,8 @@ MFEM_LIB_FILE = mfem_is_not_built
SEQ_MINIAPPS = mobius-strip klein-bottle toroid trimmer twist mesh-explorer\
shaper extruder mesh-optimizer minimal-surface polar-nc reflector\
ref321 mesh-quality hpref
PAR_MINIAPPS = pmesh-optimizer pminimal-surface pmesh-fitting fit-node-position phpref
PAR_MINIAPPS = pmesh-optimizer pminimal-surface pmesh-fitting fit-node-position\
phpref mesh-bounding-boxes
ifeq ($(MFEM_USE_MPI),NO)
MINIAPPS = $(SEQ_MINIAPPS)
else
@@ -103,6 +104,8 @@ hpref-test-seq: hpref
@$(call mfem-test,$<,, Serial hp-refinement)
phpref-test-par: phpref
@$(call mfem-test,$<, $(RUN_MPI), Parallel hp-refinement)
mesh-bounding-boxes-test-par: mesh-bounding-boxes
@$(call mfem-test,$<, $(RUN_MPI), Parallel bounding boxes)
ref321-test-seq: ref321
@$(call mfem-test-file,$<,, Meshing miniapp,ref321.mesh)
@@ -121,10 +124,7 @@ $(MFEM_LIB_FILE):
clean: clean-build clean-exec
clean-build:
rm -f *.o *~ mobius-strip klein-bottle toroid twist
rm -f mesh-explorer shaper extruder trimmer reflector ref321
rm -f mesh-optimizer pmesh-optimizer pmesh-fitting polar-nc hpref phpref
rm -f minimal-surface pminimal-surface mesh-quality fit-node-position
rm -f *.o *~ $(SEQ_MINIAPPS) $(PAR_MINIAPPS)
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@@ -134,3 +134,4 @@ clean-exec:
@rm -f optimized* perturbed* polar-nc.mesh
@rm -rf mesh-explorer-{visit,paraview}*
@rm -f mesh.* order.* sol.* refined.mesh
@rm -rf jacobian-determinant-bounds* bounding-box*
+392
View File
@@ -0,0 +1,392 @@
// 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.
//
// ---------------------------------------------------------------------
// Bounding Boxes Miniapp: Construct Bounding Boxes of Quad/Hex Meshes
// ---------------------------------------------------------------------
//
// This miniapp computes bounding boxes for each element in a given mesh, and
// also computes the bounds on the determinant of the Jacobian of the
// transformation for each element. The bounding approach is based on the
// method described in:
//
// (1) Section 3 of Mittal et al., "General Field Evaluation in High-Order
// Meshes on GPUs"
// and
// (2) Dzanic et al., "A method for bounding high-order finite element
// functions: Applications to mesh validity and bounds-preserving limiters".
//
//
// Compile with: make mesh-bounding-boxes
//
// Sample runs:
// mpirun -np 4 mesh-bounding-boxes -m ../../data/klein-bottle.mesh
// mpirun -np 4 mesh-bounding-boxes -m ../gslib/triple-pt-1.mesh
// mpirun -np 4 mesh-bounding-boxes -m ../../data/star-surf.mesh
// mpirun -np 4 mesh-bounding-boxes -m ../../data/fichera-q2.mesh
#include "mfem.hpp"
#include <iostream>
#include <fstream>
using namespace mfem;
using namespace std;
Mesh MakeBoundingBoxMesh(Mesh &mesh, GridFunction &nodal_bb_gf);
void GetDeterminantJacobianGF(ParMesh *mesh, ParGridFunction *detgf);
void VisualizeBB(Mesh &mesh, char *title, int pos_x, int pos_y);
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
char *title, int pos_x, int pos_y);
int main (int argc, char *argv[])
{
// 0. Initialize MPI and HYPRE.
Mpi::Init(argc, argv);
Hypre::Init();
// Set the method's default parameters.
const char *mesh_file = "../../data/klein-bottle.mesh";
int mesh_poly_deg = 2;
bool visualization = true;
bool visit = false;
bool jacobian = true;
// Parse command-line options.
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&mesh_poly_deg, "-o", "--order",
"Polynomial degree of mesh finite element space.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&visit, "-visit", "--visit", "-no-visit",
"--no-visit",
"Enable or disable VisIt output.");
args.AddOption(&jacobian, "-jac", "--jacobian", "-no-jac",
"--no-jacobian",
"Compute bounds on determinant of mesh Jacobian");
args.ParseCheck();
// Initialize and refine the starting mesh.
Mesh mesh(mesh_file, 1, 1, false);
const int rdim = mesh.Dimension();
const int sdim = mesh.SpaceDimension();
ParMesh pmesh(MPI_COMM_WORLD, mesh);
if (pmesh.GetNodes() == NULL) { pmesh.SetCurvature(mesh_poly_deg); }
else { mesh_poly_deg = pmesh.GetNodes()->FESpace()->GetMaxElementOrder(); }
mesh.Clear();
// Setup finite element space and gridfunction to store bounding box
// x/y/z min & max for each element.
L2_FECollection fec_pc(0, rdim);
ParFiniteElementSpace fes_l2_bb(&pmesh, &fec_pc, sdim*2, Ordering::byVDIM);
ParGridFunction nodal_bb(&fes_l2_bb);
Array<int> vdofs;
GridFunction *nodes = pmesh.GetNodes();
int nelem = pmesh.GetNE();
// Compute bounds on nodal positions and save in nodal_bb gridfunction.
Vector lower, upper;
nodes->GetElementBounds(lower, upper, 2, -1);
for (int e = 0; e < nelem; e++)
{
fes_l2_bb.GetElementVDofs(e, vdofs);
Vector lower_upper(vdofs.Size());
for (int d = 0; d < sdim; d++)
{
lower_upper(d) = lower(e + d*nelem);
lower_upper(d+sdim) = upper(e + d*nelem);
}
nodal_bb.SetSubVector(vdofs, lower_upper);
}
// Make a mesh of bounding boxes to output.
Mesh pmesh_ser = pmesh.GetSerialMesh(0);
GridFunction nodal_bb_ser = nodal_bb.GetSerialGridFunction(0, pmesh_ser);
Mesh meshbb = MakeBoundingBoxMesh(pmesh_ser, nodal_bb_ser);
// Output in GLVis and VisIt
if (visualization && Mpi::Root())
{
char title1[] = "Input mesh";
VisualizeBB(pmesh_ser, title1, 0, 0);
char title2[] = "Bounding box mesh";
VisualizeBB(meshbb, title2, 400, 0);
}
if (visit && Mpi::Root())
{
VisItDataCollection visit_dc("bounding-box-input", &pmesh_ser);
visit_dc.SetFormat(DataCollection::SERIAL_FORMAT);
visit_dc.Save();
VisItDataCollection visit_dc_bb("bounding-box", &meshbb);
visit_dc_bb.SetFormat(DataCollection::SERIAL_FORMAT);
visit_dc_bb.Save();
}
// Print min and max bound of nodal gridfunction
int ref_factor = 4;
nodes->GetBounds(lower, upper, ref_factor);
if (Mpi::Root())
{
out << "Nodal position minimum bounds:" << endl;
lower.Print();
out << "Nodal position maximum bounds:" << endl;
upper.Print();
}
if (!jacobian) { return 0; }
// Setup gridfunction for the determinant of the Jacobian.
// Note: determinant order = rdim*mesh_order - 1 for quads/hexes
int det_order = rdim*mesh_poly_deg-1;
L2_FECollection fec_det(det_order, rdim, BasisType::GaussLobatto);
ParFiniteElementSpace fespace_det(&pmesh, &fec_det);
ParGridFunction detgf(&fespace_det);
GetDeterminantJacobianGF(&pmesh, &detgf);
// Setup piecewise constant gridfunction to save bounds on the determinant
// of the Jacobian
L2_FECollection fec_det_pc(0, rdim);
ParFiniteElementSpace fes_det_pc(&pmesh, &fec_det_pc);
ParGridFunction bounds_detgf_lower(&fes_det_pc);
ParGridFunction bounds_detgf_upper(&fes_det_pc);
// Compute bounds
detgf.GetElementBounds(bounds_detgf_lower, bounds_detgf_upper, ref_factor);
// GLVis Visualization
if (visualization)
{
char title1[] = "Determinant of Jacobian (det J)";
VisualizeField(pmesh, detgf, title1, 0, 465);
char title2[] = "Element-wise lower bound on det J";
VisualizeField(pmesh, bounds_detgf_lower, title2, 400, 465);
char title3[] = "Element-wise upper bound on det J";
VisualizeField(pmesh, bounds_detgf_upper, title3, 800, 465);
}
// Visit Visualization
if (visit)
{
VisItDataCollection visit_dc("jacobian-determinant-bounds", &pmesh);
visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);
visit_dc.RegisterField("determinant", &detgf);
visit_dc.RegisterField("det-lower-bound", &bounds_detgf_lower);
visit_dc.RegisterField("det-upper-bound", &bounds_detgf_upper);
visit_dc.Save();
}
// Print min and max bound of determinant gridfunction
detgf.GetBounds(lower, upper, ref_factor);
if (Mpi::Root())
{
out << "Jacobian determinant minimum bound: " << lower(0) << endl;
out << "Jacobian determinant maximum bound: " << upper(0) << endl;
}
return 0;
}
Mesh MakeBoundingBoxMesh(Mesh &mesh, GridFunction &nodal_bb_gf)
{
int nelem = mesh.GetNE();
int sdim = mesh.SpaceDimension();
int nverts = pow(2,sdim)*nelem;
Mesh meshbb(sdim, nverts, nelem, 0, sdim);
int eidx = 0;
int vidx = 0;
for (int e = 0; e < nelem; e++)
{
Vector xyzminmax_el;
nodal_bb_gf.GetElementDofValues(e, xyzminmax_el);
if (sdim == 2)
{
Vector xyz(2);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(1);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(2);
xyz(1) = xyzminmax_el(1);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(2);
xyz(1) = xyzminmax_el(3);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(3);
meshbb.AddVertex(xyz);
const int inds[4] = {vidx++, vidx++, vidx++, vidx++};
int attr = eidx+1;
meshbb.AddQuad(inds, attr);
eidx++;
}
else if (sdim == 3)
{
Vector xyz(3);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(1);
xyz(2) = xyzminmax_el(2);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(3);
xyz(1) = xyzminmax_el(1);
xyz(2) = xyzminmax_el(2);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(3);
xyz(1) = xyzminmax_el(4);
xyz(2) = xyzminmax_el(2);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(4);
xyz(2) = xyzminmax_el(2);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(1);
xyz(2) = xyzminmax_el(5);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(3);
xyz(1) = xyzminmax_el(1);
xyz(2) = xyzminmax_el(5);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(3);
xyz(1) = xyzminmax_el(4);
xyz(2) = xyzminmax_el(5);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(4);
xyz(2) = xyzminmax_el(5);
meshbb.AddVertex(xyz);
const int inds[8] = {vidx++, vidx++, vidx++, vidx++,
vidx++, vidx++, vidx++, vidx++
};
meshbb.AddHex(inds, (eidx++)+1);
}
}
if (sdim == 2)
{
meshbb.FinalizeQuadMesh(1, 1, true);
}
else
{
meshbb.FinalizeHexMesh(1, 1, true);
}
return meshbb;
}
IntegrationRule PermuteIR(const IntegrationRule &irule,
const Array<int> ordering)
{
const int np = irule.GetNPoints();
MFEM_VERIFY(np == ordering.Size(), "Invalid permutation size");
IntegrationRule ir(np);
ir.SetOrder(irule.GetOrder());
for (int i = 0; i < np; i++)
{
IntegrationPoint &ip_new = ir.IntPoint(i);
const IntegrationPoint &ip_old = irule.IntPoint(ordering[i]);
ip_new.Set(ip_old.x, ip_old.y, ip_old.z, ip_old.weight);
}
return ir;
}
void GetDeterminantJacobianGF(ParMesh *mesh, ParGridFunction *detgf)
{
int dim = mesh->Dimension();
FiniteElementSpace *fespace = detgf->FESpace();
Array<int> dofs;
for (int e = 0; e < mesh->GetNE(); e++)
{
const FiniteElement *fe = fespace->GetFE(e);
const IntegrationRule ir = fe->GetNodes();
ElementTransformation *transf = mesh->GetElementTransformation(e);
DenseMatrix Jac(fe->GetDim());
const NodalFiniteElement *nfe = dynamic_cast<const NodalFiniteElement*>
(fe);
const Array<int> &irordering = nfe->GetLexicographicOrdering();
IntegrationRule ir2 = irordering.Size() ?
PermuteIR(ir, irordering) :
ir;
Vector detvals(ir2.GetNPoints());
Vector loc(dim);
for (int q = 0; q < ir2.GetNPoints(); q++)
{
IntegrationPoint ip = ir2.IntPoint(q);
transf->SetIntPoint(&ip);
transf->Transform(ip, loc);
Jac = transf->Jacobian();
detvals(q) = Jac.Weight();
}
fespace->GetElementDofs(e, dofs);
if (irordering.Size())
{
for (int i = 0; i < dofs.Size(); i++)
{
(*detgf)(dofs[i]) = detvals(irordering[i]);
}
}
else
{
detgf->SetSubVector(dofs, detvals);
}
}
}
void VisualizeBB(Mesh &mesh, char *title, int pos_x, int pos_y)
{
socketstream sock;
sock.open("localhost", 19916);
sock << "mesh\n";
mesh.Print(sock);
std::string keystrokes = mesh.SpaceDimension() == 2 ? "keys em" : "keys )";
sock << "window_title '"<< title << "'\n"
<< "window_geometry "
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
// << "keys jRmclA//]]]]]]]]" << endl;
<< keystrokes << endl;
}
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
char *title, int pos_x, int pos_y)
{
socketstream sock;
if (pmesh.GetMyRank() == 0)
{
sock.open("localhost", 19916);
sock << "solution\n";
}
pmesh.PrintAsOne(sock);
input.SaveAsOne(sock);
if (pmesh.GetMyRank() == 0)
{
sock << "window_title '"<< title << "'\n"
<< "window_geometry "
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
<< "keys jRmclApppppppppppp//]]]]]]]]" << endl;
}
}
+129
View File
@@ -371,6 +371,7 @@ int main (int argc, char *argv[])
cout << "What would you like to do?\n"
"r) Refine\n"
"c) Change curvature\n"
"i) Increase space dimension\n"
"s) Scale\n"
"t) Transform\n"
"j) Jitter\n"
@@ -534,6 +535,37 @@ int main (int argc, char *argv[])
print_char = 1;
}
if (mk == 'i')
{
int curr_sdim = mesh->SpaceDimension();
cout << "Current space dimension is " << curr_sdim << "\n";
cout << "Enter new space dimension --> " << flush;
int new_sdim;
cin >> new_sdim;
if (new_sdim > curr_sdim && new_sdim <= 3)
{
if (mesh->GetNodes() == NULL)
{
mesh->SetCurvature(1, false, new_sdim); // Set Space Dimension
mesh->SetCurvature(-1); // Remove Nodes GridFunction created
// // by the previous line
}
else
{
const FiniteElementSpace *fes = mesh->GetNodalFESpace();
const int order = fes->GetMaxElementOrder();
const FiniteElementCollection *fec = fes->FEColl();
const bool discont = dynamic_cast<const L2_FECollection*>(fec);
mesh->SetCurvature(order, discont, new_sdim);
}
}
else
{
cout << "New space dimension must be greater than current space "
<< "dimension and less than 4." << endl;
}
}
if (mk == 'c')
{
int p;
@@ -576,6 +608,7 @@ int main (int argc, char *argv[])
char type;
cout << "Choose a transformation:\n"
"u) User-defined transform through mesh-explorer::transformation()\n"
"a) Affine transform\n"
"k) Kershaw transform\n"
"s) Spiral transform\n"<< "---> " << flush;
cin >> type;
@@ -583,6 +616,102 @@ int main (int argc, char *argv[])
{
mesh->Transform(transformation);
}
else if (type == 'a')
{
DenseMatrix A(sdim);
Vector b(sdim);
char tmtype;
cout << "Type of transformation matrix:\n"
"i) Identity\n"
"r) Rotation\n"
"s) Scale\n"
"g) General\n" << " ---> " << flush;
cin >> tmtype;
if (tmtype == 'i')
{
A = 0.0;
A(0,0) = 1.0;
if (sdim > 1) { A(1,1) = 1.0; }
if (sdim > 2) { A(2,2) = 1.0; }
}
if (tmtype == 'r')
{
if (sdim == 2)
{
real_t angle_deg;
cout << "Rotation angle (degrees) --> " << flush;
cin >> angle_deg;
const real_t angle = angle_deg * M_PI / 180.0;
A(0,0) = cos(angle);
A(1,0) = sin(angle);
A(0,1) = -A(1,0);
A(1,1) = A(0,0);
}
else
{
real_t a_deg, b_deg, c_deg;
cout << "Euler angles z-x-z (degrees) --> " << flush;
cin >> a_deg >> b_deg >> c_deg;
const real_t alpha = a_deg * M_PI / 180.0;
const real_t beta = b_deg * M_PI / 180.0;
const real_t gamma = c_deg * M_PI / 180.0;
const real_t ca = cos(alpha), sa = sin(alpha);
const real_t cb = cos(beta ), sb = sin(beta );
const real_t cc = cos(gamma), sc = sin(gamma);
A(0,0) = ca * cc - cb * sa * sc;
A(0,1) = -ca * sc - cb * cc * sa;
A(0,2) = sa * sb;
A(1,0) = cc * sa + ca * cb * sc;
A(1,1) = ca * cb * cc - sa * sc;
A(1,2) = -ca * sb;
A(2,0) = sb * sc;
A(2,1) = cc * sb;
A(2,2) = cb;
}
}
if (tmtype == 's')
{
A = 0.0;
cout << "Scale factors for each cartesian direction --> "
<< flush;
cin >> A(0,0);
if (sdim > 1) { cin >> A(1,1); }
if (sdim > 2) { cin >> A(2,2); }
}
if (tmtype == 'g')
{
cout << "General matrix entries in column major order --> "
<< flush;
for (int j=0; j<sdim; j++)
for (int i=0; i<sdim; i++)
{
cin >> A(i,j);
}
const real_t detA = A.Det();
if (detA <= 0.0)
{
cout << "Warning - transformation matrix has non-positive "
<< "determinant. Elements may be flattened or "
<< "inverted.\n";
}
}
cout << "Translation vector components --> " << flush;
cin >> b(0);
if (sdim > 1) { cin >> b(1); }
if (sdim > 2) { cin >> b(2); }
common::AffineTransformation affineT(sdim, A, b);
mesh->Transform(affineT);
}
else if (type == 'k')
{
cout << "Note: For Kershaw transformation, the input must be "
+2 -2
View File
@@ -65,10 +65,10 @@ int main(int argc, char *argv[])
args.AddOption(&dim, "-dim", "--dim", "Mesh dimension (2 or 3)");
args.AddOption(&deterministic, "-det", "--deterministic", "-not-det",
"--not-deterministic",
"Whether to use deterministic random refinements");
"Use deterministic random refinements");
args.AddOption(&projectSolution, "-proj", "--project-solution", "-no-proj",
"--no-project",
"Whether to project a coefficient to solution");
"Project a coefficient to solution");
args.Parse();
if (!args.Good())
+2 -2
View File
@@ -38,13 +38,13 @@ if (MFEM_USE_MPI)
${MFEM_MINIAPPS_COMMON_HEADERS}
LIBRARIES mfem mfem-common)
add_dependencies(multidomain_nd copy_miniapps_multidomain_data)
add_mfem_miniapp(multidomain_rt
MAIN multidomain_rt.cpp
${MFEM_MINIAPPS_COMMON_HEADERS}
LIBRARIES mfem mfem-common)
add_dependencies(multidomain_rt copy_miniapps_multidomain_data)
# Add parallel tests.
if (MFEM_ENABLE_TESTING)
add_test(NAME multidomain_np${MFEM_MPI_NP}
+12 -2
View File
@@ -11,9 +11,14 @@
if (MFEM_USE_MPI)
list(APPEND NAVIER_COMMON_SOURCES
navier_solver.cpp)
navier_solver.cpp
incompressible_navier_solver.cpp
stokes_solver.cpp)
list(APPEND NAVIER_COMMON_HEADERS
navier_solver.hpp)
navier_solver.hpp
incompressible_navier_solver.hpp
stokes_solver.hpp)
convert_filenames_to_full_paths(NAVIER_COMMON_SOURCES)
convert_filenames_to_full_paths(NAVIER_COMMON_HEADERS)
@@ -52,6 +57,11 @@ if (MFEM_USE_MPI)
${NAVIER_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(incompNS_2Dtest
MAIN incompNS_2Dtest.cpp
${NAVIER_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(navier_turbchan
MAIN navier_turbchan.cpp
${NAVIER_COMMON_FILES}
+134
View File
@@ -0,0 +1,134 @@
// Copyright (c) 2010-2024, 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.
// 3D flow over a cylinder benchmark example
#include "incompressible_navier_solver.hpp"
#include <fstream>
using namespace mfem;
using namespace incompressible_navier;
void vel(const Vector &x, real_t t, Vector &u)
{
real_t xi = x(0);
real_t yi = x(1);
u = 0.0;
}
void vel_inlet(const Vector &x, real_t t, Vector &u)
{
u = 0.0;
if (x(0) < 0.001) {
u(0) = -0.001 * (std::pow(x(1) - 0.5, 2.0) - 0.25);
}
}
int main(int argc, char *argv[])
{
Mpi::Init(argc, argv);
Hypre::Init();
int serial_refinements = 1;
int vOrder = 2;
int pOrder = 1;
int tOrder = 1;
real_t kin_vis = 20.0;
real_t dt = 1e-2;
real_t t = 0.0;
real_t t_final = 1.0;
bool last_step = false;
//Mesh *mesh = new Mesh("box-cylinder.mesh");
Mesh mesh = Mesh::MakeCartesian2D(90, 30, mfem::Element::QUADRILATERAL, true, 3, 1);
for (int i = 0; i < serial_refinements; ++i)
{
mesh.UniformRefinement();
}
if (Mpi::Root())
{
std::cout << "Number of elements: " << mesh.GetNE() << std::endl;
}
auto *pmesh = new ParMesh(MPI_COMM_WORLD, mesh);
// Create the flow solver.
IncompressibleNavierSolver flowsolver(pmesh, vOrder, pOrder, tOrder, kin_vis);
flowsolver.EnablePA(false);
// // Set the initial condition.
// ParGridFunction *u_ic = flowsolver.GetCurrentVelocity();
// VectorFunctionCoefficient u_excoeff(pmesh->Dimension(), vel);
// u_ic->ProjectCoefficient(u_excoeff);
// Add Dirichlet boundary conditions to velocity space restricted to
// selected attributes on the mesh.
Array<int> attr(pmesh->bdr_attributes.Max()); attr = 0;
Array<int> attr_inlet(pmesh->bdr_attributes.Max()); attr_inlet = 0;
// Inlet is attribute 1.
attr[0] = 1;
// Walls is attribute 3.
attr[2] = 1;
flowsolver.AddVelDirichletBC(vel, attr);
attr_inlet[3] = 1;
flowsolver.AddVelDirichletBC(vel_inlet, attr_inlet);
flowsolver.Setup(dt);
ParGridFunction *u_gf = flowsolver.GetCurrentVelocity();
ParGridFunction *p_gf = flowsolver.GetCurrentPressure();
ParGridFunction *psi_gf = flowsolver.GetCurrentPsi();
ParaViewDataCollection pvdc("3dfoc", pmesh);
pvdc.SetDataFormat(VTKFormat::BINARY32);
//pvdc.SetHighOrderOutput(true);
pvdc.SetCycle(0);
pvdc.SetTime(t);
pvdc.RegisterField("velocity", u_gf);
pvdc.RegisterField("pressure", p_gf);
pvdc.RegisterField("psi", psi_gf);
pvdc.Save();
for (int step = 0; !last_step; ++step)
{
if (t + dt >= t_final - dt / 2)
{
last_step = true;
}
flowsolver.Step(t, dt, step);
if (step % 1 == 0)
{
pvdc.SetCycle(step);
pvdc.SetTime(t);
pvdc.Save();
}
if (Mpi::Root())
{
printf("%11s %11s\n", "Time", "dt");
printf("%.5E %.5E\n", t, dt);
fflush(stdout);
}
}
// flowsolver.PrintTimingData();
delete pmesh;
return 0;
}
@@ -0,0 +1,515 @@
// Copyright (c) 2010-2024, 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 "incompressible_navier_solver.hpp"
#include "../../general/forall.hpp"
#include <fstream>
#include <iomanip>
using namespace mfem;
using namespace incompressible_navier;
IncompressibleNavierSolver::IncompressibleNavierSolver(ParMesh *mesh, int velorder, int porder, int torder_, real_t kin_vis)
: pmesh(mesh), velorder(velorder), porder(porder), torder(torder_), kin_vis(kin_vis),
gll_rules(0, Quadrature1D::GaussLobatto), velGF(torder_+1,nullptr), pGF(torder_+1,nullptr)
{
vfec = new H1_FECollection(velorder, pmesh->Dimension());
psifec = new H1_FECollection(porder);
pfec = new H1_FECollection(porder);
vfes = new ParFiniteElementSpace(pmesh, vfec, pmesh->Dimension());
psifes = new ParFiniteElementSpace(pmesh, pfec);
pfes = new ParFiniteElementSpace(pmesh, pfec);
// Check if fully periodic mesh
if (!(pmesh->bdr_attributes.Size() == 0))
{
vel_ess_attr.SetSize(pmesh->bdr_attributes.Max());
vel_ess_attr = 0;
pres_ess_attr.SetSize(pmesh->bdr_attributes.Max());
pres_ess_attr = 0;
}
int vfes_truevsize = vfes->GetTrueVSize();
int pfes_truevsize = pfes->GetTrueVSize();
for( int i = 0; i<torder+1; i++)
{
velGF[i] = new ParGridFunction(vfes); *velGF[i] = 0.0;
pGF[i] = new ParGridFunction(pfes); *pGF[i] = 0.0;
}
psiGF.SetSpace(psifes);
DvGF.SetSpace(vfes);
divVelGF.SetSpace(pfes);
pRHS.SetSpace(pfes);
}
void IncompressibleNavierSolver::Setup(real_t dt)
{
if (verbose && pmesh->GetMyRank() == 0)
{
mfem::out << "Setup" << std::endl;
if (partial_assembly)
{
mfem::out << "Using Partial Assembly" << std::endl;
}
else
{
mfem::out << "Using Full Assembly" << std::endl;
}
}
this->Setup_velocity( dt );
this->Setup_auxiliary( dt );
this->Setup_pressure( dt );
}
void IncompressibleNavierSolver::Setup_velocity(real_t dt)
{
// GLL integration rule (Numerical Integration)
const IntegrationRule &ir_ni = gll_rules.Get(vfes->GetFE(0)->GetGeomType(),
2 * velorder - 1);
vfes->GetEssentialTrueDofs(vel_ess_attr, vel_ess_tdof);
//-------------------------------------------------------------------------
//Setup of coefficient for mass term of Eq(13)
dtCoeff = new ConstantCoefficient(1.0/dt);
auto *vmass_blfi = new VectorMassIntegrator(*dtCoeff);
//Setup of coefficient for stiffness term of Eq(13)
kinvisCoeff = new ConstantCoefficient(kin_vis);
auto *vdiff_blfi = new VectorDiffusionIntegrator(*kinvisCoeff);
// setup of Bilinear form of Eq(13)
velBForm = new ParBilinearForm(vfes);
if (numerical_integ)
{
vmass_blfi->SetIntRule(&ir_ni);
vdiff_blfi->SetIntRule(&ir_ni);
}
velBForm->AddDomainIntegrator(vmass_blfi);
velBForm->AddDomainIntegrator(vdiff_blfi);
if (partial_assembly)
{
velBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL);
}
velBForm->Assemble();
velBForm->FormSystemMatrix(vel_ess_tdof, vOp);
//-------------------------------------------------------------------------
//Setup of coefficient for Eq(18)
pUnitVectorCoeff = new UnitVectorGridFunctionCoeff(pmesh->Dimension());
auto *pvel_lfi = new VectorDomainLFGradIntegrator(*pUnitVectorCoeff);
//Setup of coefficient for Eq(20)
nonlinTermCoeff = new NonLinTermVectorGridFunctionCoeff(pmesh->Dimension());
auto *p_nonlintermlfi = new VectorDomainLFIntegrator(*nonlinTermCoeff);
//Setup of coefficient for Eq(21)
prevVelLoadCoeff = new PrevVelVectorGridFunctionCoeff(pmesh->Dimension());
auto *prevVelLoadLFi = new VectorDomainLFIntegrator(*prevVelLoadCoeff);
//Setup of linear form of Eq(13)
velLForm = new ParLinearForm(vfes);
if (numerical_integ)
{
prevVelLoadLFi->SetIntRule(&ir_ni);
pvel_lfi->SetIntRule(&ir_ni);
p_nonlintermlfi->SetIntRule(&ir_ni);
}
velLForm->AddDomainIntegrator(prevVelLoadLFi);
velLForm->AddDomainIntegrator(pvel_lfi);
velLForm->AddDomainIntegrator(p_nonlintermlfi);
//-------------------------------------------------------------------------
if (partial_assembly)
{
Vector diag_pa(vfes->GetTrueVSize());
velBForm->AssembleDiagonal(diag_pa);
velInvPC = new OperatorJacobiSmoother(diag_pa, vel_ess_tdof);
}
else
{
velInvPC = new HypreSmoother(*vOp.As<HypreParMatrix>());
dynamic_cast<HypreSmoother *>(velInvPC)->SetType(HypreSmoother::Jacobi, 1);
}
velInv = new CGSolver(vfes->GetComm());
velInv->iterative_mode = true;
velInv->SetOperator(*vOp);
velInv->SetPreconditioner(*velInvPC);
velInv->SetPrintLevel(pl_velsolve);
velInv->SetRelTol(rtol_velsolve);
velInv->SetMaxIter(1200);
}
void IncompressibleNavierSolver::Setup_auxiliary(real_t dt)
{
// GLL integration rule (Numerical Integration)
const IntegrationRule &ir_ni = gll_rules.Get(vfes->GetFE(0)->GetGeomType(),
2 * velorder - 1);
Array<int> empty;
// setup of Bilinear form of Eq(14)
psiBForm = new ParBilinearForm(psifes);
auto *psidiff_blfi = new DiffusionIntegrator;
if (numerical_integ)
{
psidiff_blfi->SetIntRule(&ir_ni);
}
psiBForm->AddDomainIntegrator(psidiff_blfi);
if (partial_assembly)
{
psiBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL);
}
psiBForm->Assemble();
psiBForm->FormSystemMatrix(empty, psiOp);
//-------------------------------------------------------------------------
//Setup of coefficient for linear form in Eq(14)
DvelCoeff = new VectorGridFunctionCoefficient;
auto *Dvel_lfi = new DomainLFGradIntegrator(*DvelCoeff);
//Setup of linear form of Eq(14)
psiLForm = new ParLinearForm(psifes);
if (numerical_integ)
{
Dvel_lfi->SetIntRule(&ir_ni);
}
psiLForm->AddDomainIntegrator(Dvel_lfi);
//-------------------------------------------------------------------------
if (partial_assembly)
{
int psifes_truevsize = psifes->GetTrueVSize();
mfem::Vector psin(psifes_truevsize); psin = 0.0;
mfem::Vector respsi(psifes_truevsize); respsi = 0.0;
lor = new ParLORDiscretization(*psiBForm, empty);
psiInvPC = new HypreBoomerAMG(lor->GetAssembledMatrix());
psiInvPC->SetPrintLevel(0);
psiInvPC->Mult(respsi, psin);
SpInvOrthoPC = new OrthoSolver(psifes->GetComm());
SpInvOrthoPC->SetSolver(*psiInvPC);
}
else
{
psiInvPC = new HypreBoomerAMG(*psiOp.As<HypreParMatrix>());
psiInvPC->SetPrintLevel(0);
SpInvOrthoPC = new OrthoSolver(psifes->GetComm());
SpInvOrthoPC->SetSolver(*psiInvPC);
}
psiInv = new CGSolver(psifes->GetComm());
psiInv->iterative_mode = true;
psiInv->SetOperator(*psiOp);
psiInv->SetPreconditioner(*SpInvOrthoPC);
psiInv->SetPrintLevel(pl_psisolve);
psiInv->SetRelTol(rtol_psisolve);
psiInv->SetMaxIter(1000);
}
void IncompressibleNavierSolver::Setup_pressure(real_t dt)
{
// GLL integration rule (Numerical Integration)
const IntegrationRule &ir_ni = gll_rules.Get(vfes->GetFE(0)->GetGeomType(),
2 * velorder - 1);
Array<int> empty;
//-------------------------------------------------------------------------
// setup of Bilinear form of Eq(15)
pBForm = new ParBilinearForm(pfes);
auto *pmass_blfi = new MassIntegrator;
if (numerical_integ)
{
pmass_blfi->SetIntRule(&ir_ni);
}
pBForm->AddDomainIntegrator(pmass_blfi);
if (partial_assembly)
{
pBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL);
}
pBForm->Assemble();
pBForm->FormSystemMatrix(empty, pOp);
//-------------------------------------------------------------------------
//Setup of divergence of velocity coefficient for linear form in Eq(15)
divVelCoeff = new DivergenceGridFunctionCoefficient(velGF[0]);
//Setup of coefficient for linear form in Eq(14)
pRHSCoeff = new GridFunctionCoefficient(&pRHS);
auto *p_lfi = new DomainLFIntegrator(*pRHSCoeff);
//Setup of linear form of Eq(15)
pLForm = new ParLinearForm(pfes);
if (numerical_integ)
{
p_lfi->SetIntRule(&ir_ni);
}
pLForm->AddDomainIntegrator(p_lfi);
//-------------------------------------------------------------------------
if (partial_assembly)
{
Vector diag_pa(pfes->GetTrueVSize());
pBForm->AssembleDiagonal(diag_pa);
pInvPC = new OperatorJacobiSmoother(diag_pa, empty);
}
else
{
pInvPC = new HypreSmoother(*pOp.As<HypreParMatrix>());
dynamic_cast<HypreSmoother *>(pInvPC)->SetType(HypreSmoother::Jacobi, 1);
}
pInv = new CGSolver(pfes->GetComm());
pInv->iterative_mode = true;
pInv->SetOperator(*pOp);
pInv->SetPreconditioner(*pInvPC);
pInv->SetPrintLevel(pl_psolve);
pInv->SetRelTol(rtol_psolve);
pInv->SetMaxIter(1000);
}
void IncompressibleNavierSolver::UpdateTimestepHistory(real_t dt)
{
}
void IncompressibleNavierSolver::Step(real_t &time, real_t dt, int current_step)
{
this->Step_velocity(time, dt, current_step);
this->Step_auxiliary(time, dt, current_step);
this->Step_pressure(time, dt, current_step);
*velGF[1] = *velGF[0];
*pGF[1] = *pGF[0];
mfem::out << "It: " << iter << " | Iter_U: " << iter_vsolve << " | Iter_Psi: " << iter_psisolve << " | Iter_P: " << iter_psolve << "\n";
mfem::out << "It: " << iter << " | Resid_U: " << res_vsolve << " | Resid_Psi: " << res_psisolve << " | Resid_P: " << res_psisolve << "\n";
time += dt;
iter ++;
}
void IncompressibleNavierSolver::Step_velocity(real_t &time, real_t dt, int current_step)
{
for (auto &vel_dbc : vel_dbcs)
{
velGF[0]->ProjectBdrCoefficient(*vel_dbc.coeff, vel_dbc.attr);
velGF[1]->ProjectBdrCoefficient(*vel_dbc.coeff, vel_dbc.attr);
}
//Update state in coefficient for Eq(18)
pUnitVectorCoeff->SetGridFunction( pGF[1] );
//Update state in coefficient for Eq(20)
nonlinTermCoeff->SetGridFunction( velGF[1] );
//Update state in coefficient for Eq(21)
prevVelLoadCoeff ->SetGridFunction( velGF[1], dt );
velLForm->Assemble();
velLForm->ParallelAssemble(velLF);
Vector X1, B1;
if (partial_assembly)
{
auto *vpC = vOp.As<ConstrainedOperator>();
EliminateRHS(*velBForm, *vpC, vel_ess_tdof, *velGF[0], velLF, X1, B1, 1);
}
else
{
velBForm->FormLinearSystem(vel_ess_tdof, *velGF[0], velLF, vOp , X1, B1, 1);
}
velInv->Mult(B1, X1);
iter_vsolve = velInv->GetNumIterations();
res_vsolve = velInv->GetFinalNorm();
velBForm->RecoverFEMSolution(X1, velLF, *velGF[0]);
}
void IncompressibleNavierSolver::Step_auxiliary(real_t &time, real_t dt, int current_step)
{
// Compute new increment GF for LF of Eq(14) and update state in coefficient
subtract(1.0/dt, *velGF[0], *velGF[1], DvGF);
DvelCoeff->SetGridFunction( &DvGF );
psiLForm->Assemble();
psiLForm->ParallelAssemble(psiLF);
Vector X2, B2;
Array<int> empty;
if (partial_assembly)
{
auto *psipC = psiOp.As<ConstrainedOperator>();
EliminateRHS(*psiBForm, *psipC, empty, psiGF, psiLF, X2, B2, 1);
}
else
{
psiBForm->FormLinearSystem(empty, psiGF, psiLF, psiOp, X2, B2, 1);
}
psiInv->Mult(B2, X2);
iter_psisolve = psiInv->GetNumIterations();
res_psisolve = psiInv->GetFinalNorm();
psiBForm->RecoverFEMSolution(X2, psiLF, psiGF);
}
void IncompressibleNavierSolver::Step_pressure(real_t &time, real_t dt, int current_step)
{
Array<int> empty;
// Compute new GF for LF of Eq(15) and update state in coefficient
divVelCoeff->SetGridFunction( velGF[0]);
divVelGF.ProjectCoefficient( *divVelCoeff );
add( *pGF[1], psiGF, pRHS);
add( pRHS, -1.0*kin_vis, divVelGF, pRHS);
pRHSCoeff->SetGridFunction( &pRHS );
pLForm->Assemble();
pLForm->ParallelAssemble(pLF);
Vector X3, B3;
if (partial_assembly)
{
auto *ppC = pOp.As<ConstrainedOperator>();
EliminateRHS(*pBForm, *ppC, empty, *pGF[0], pLF, X3, B3, 1);
}
else
{
pBForm->FormLinearSystem(empty, *pGF[0] , pLF , pOp , X3, B3, 1);
}
pInv->Mult(B3, X3);
iter_psolve = pInv->GetNumIterations();
res_psisolve = pInv->GetFinalNorm();
pBForm->RecoverFEMSolution(X3, pLF, *pGF[0]);
}
void IncompressibleNavierSolver::EliminateRHS(Operator &A,
ConstrainedOperator &constrainedA,
const Array<int> &ess_tdof_list,
Vector &x,
Vector &b,
Vector &X,
Vector &B,
int copy_interior)
{
const Operator *Po = A.GetOutputProlongation();
const Operator *Pi = A.GetProlongation();
const Operator *Ri = A.GetRestriction();
A.InitTVectors(Po, Ri, Pi, x, b, X, B);
if (!copy_interior)
{
X.SetSubVectorComplement(ess_tdof_list, 0.0);
}
constrainedA.EliminateRHS(X, B);
}
real_t IncompressibleNavierSolver::ComputeCFL(ParGridFunction &u, real_t dt)
{
return 0;
}
void IncompressibleNavierSolver::AddVelDirichletBC(VectorCoefficient *coeff, Array<int> &attr)
{
vel_dbcs.emplace_back(attr, coeff);
if (verbose && pmesh->GetMyRank() == 0)
{
mfem::out << "Adding Velocity Dirichlet BC to attributes ";
for (int i = 0; i < attr.Size(); ++i)
{
if (attr[i] == 1)
{
mfem::out << i << " ";
}
}
mfem::out << std::endl;
}
for (int i = 0; i < attr.Size(); ++i)
{
MFEM_ASSERT((vel_ess_attr[i] && attr[i]) == 0,
"Duplicate boundary definition deteceted.");
if (attr[i] == 1)
{
vel_ess_attr[i] = 1;
}
}
}
void IncompressibleNavierSolver::AddVelDirichletBC(VecFuncT *f, Array<int> &attr)
{
AddVelDirichletBC(new VectorFunctionCoefficient(pmesh->Dimension(), f), attr);
}
IncompressibleNavierSolver::~IncompressibleNavierSolver()
{
delete velBForm;
delete psiBForm;
delete pBForm;
delete kinvisCoeff;
delete dtCoeff;
for( int i = 0; i<torder+1; i++)
{
delete velGF[i];
delete pGF[i];
}
delete DvelCoeff;
delete divVelCoeff;
delete pRHSCoeff;
delete pUnitVectorCoeff;
delete velInv;
delete velInvPC;
delete psiInv;
delete SpInvOrthoPC;
delete psiInvPC;
delete lor;
delete pInv;
delete pInvPC;
delete vfec;
delete psifec;
delete pfec;
delete vfes;
delete psifes;
delete pfes;
}
@@ -0,0 +1,362 @@
// Copyright (c) 2010-2024, 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_INCOMP_NAVIER_SOLVER_HPP
#define MFEM_INCOMP_NAVIER_SOLVER_HPP
#define INCOMP_NAVIER_VERSION 0.1
#include "mfem.hpp"
namespace mfem
{
namespace incompressible_navier
{
using VecFuncT = void(const Vector &x, real_t t, Vector &u);
using ScalarFuncT = real_t(const Vector &x, real_t t);
//Coefficient which computed contribution of Eq(18)
class UnitVectorGridFunctionCoeff : public VectorCoefficient
{
public:
UnitVectorGridFunctionCoeff( int dim)
: VectorCoefficient(dim*dim)
{ }
void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip)
{
real_t coeffVal = gridfunc_->GetValue(T, ip);
V.SetSize(vdim); V = 0.0; // FIXME
V[0] = coeffVal;
V[3] = coeffVal;
}
void SetGridFunction( GridFunction * gridfunc )
{
gridfunc_ = gridfunc;
}
GridFunction *gridfunc_ = nullptr;
};
//Coefficient which computed contribution of Eq(21)
class PrevVelVectorGridFunctionCoeff : public VectorCoefficient
{
public:
PrevVelVectorGridFunctionCoeff( int dim)
: VectorCoefficient(dim)
{ }
void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip)
{
V.SetSize(vdim);
gridFuncCoeff->Eval(V, T, ip);
V *= 1.0/dt_;
}
void SetGridFunction( GridFunction * gridfunc, real_t dt )
{
gridfunc_ = gridfunc;
dt_ = dt;
delete gridFuncCoeff;
gridFuncCoeff = new VectorGridFunctionCoefficient( gridfunc );
}
GridFunction *gridfunc_ = nullptr;
VectorGridFunctionCoefficient *gridFuncCoeff = nullptr;
real_t dt_;
};
//Coefficient which computed contribution of Eq(20)
class NonLinTermVectorGridFunctionCoeff : public VectorCoefficient
{
public:
NonLinTermVectorGridFunctionCoeff( int dim)
: VectorCoefficient(dim)
{ }
void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip)
{
Vector val(vdim);
Vector resultVal(vdim);
DenseMatrix vecGrad;
V.SetSize(vdim);
gridFuncCoeff->Eval(val, T, ip);
gridfunc_->GetVectorGradient(T, vecGrad);
vecGrad.MultTranspose( val, V );
V *= -1.0;
}
void SetGridFunction( ParGridFunction * gridfunc )
{
delete gridFuncCoeff;
gridfunc_ = gridfunc;
gridFuncCoeff = new VectorGridFunctionCoefficient( gridfunc );
}
VectorGridFunctionCoefficient *gridFuncCoeff = nullptr;
ParGridFunction *gridfunc_ = nullptr;
};
/// Container for a Dirichlet boundary condition of the velocity field.
class VelDirichletBC_T
{
public:
VelDirichletBC_T(Array<int> attr, VectorCoefficient *coeff)
: attr(attr), coeff(coeff)
{}
VelDirichletBC_T(VelDirichletBC_T &&obj)
{
// Deep copy the attribute array
this->attr = obj.attr;
// Move the coefficient pointer
this->coeff = obj.coeff;
obj.coeff = nullptr;
}
~VelDirichletBC_T() { delete coeff; }
Array<int> attr;
VectorCoefficient *coeff;
};
/// Transient incompressible Navier Stokes solver in a split scheme formulation.
/**
* This implementation of a transient incompressible Navier Stokes solver uses
* the non-dimensionalized formulation. The coupled momentum and
* incompressibility equations are decoupled using the split scheme described in
* [1]. This leads to three solving steps.
*
*/
class IncompressibleNavierSolver
{
public:
/// Initialize data structures, set FE space order and kinematic viscosity.
/**
* The ParMesh @a mesh can be a linear or curved parallel mesh. The @a order
* of the finite element spaces is
*/
IncompressibleNavierSolver(ParMesh *mesh, int velorder, int porder, int tOrder, real_t kin_vis);
/// Initialize forms, solvers and preconditioners.
void Setup(real_t dt);
void Setup_velocity(real_t dt);
void Setup_auxiliary(real_t dt);
void Setup_pressure(real_t dt);
/// Compute solution at the next time step t+dt.
/**
* This method can
*/
void Step(real_t &time, real_t dt, int cur_step);
void Step_velocity(real_t &time, real_t dt, int cur_step);
void Step_auxiliary(real_t &time, real_t dt, int cur_step);
void Step_pressure(real_t &time, real_t dt, int cur_step);
/// Return a pointer to the provisional velocity ParGridFunction.
ParGridFunction *GetProvisionalVelocity() { return velGF[1]; }
/// Return a pointer to the current velocity ParGridFunction.
ParGridFunction *GetCurrentVelocity() { return velGF[0]; }
/// Return a pointer to the current pressure ParGridFunction.
ParGridFunction *GetCurrentPressure() { return pGF[0]; }
/// Return a pointer to the current pressure ParGridFunction.
ParGridFunction *GetCurrentPsi() { return &psiGF ; }
/// Add a Dirichlet boundary condition to the velocity field.
void AddVelDirichletBC(VectorCoefficient *coeff, Array<int> &attr);
void AddVelDirichletBC(VecFuncT *f, Array<int> &attr);
/// Add a Dirichlet boundary condition to the pressure field.
// void AddPresDirichletBC(Coefficient *coeff, Array<int> &attr);
// void AddPresDirichletBC(ScalarFuncT *f, Array<int> &attr);
/// Enable partial assembly for every operator.
void EnablePA(bool pa) { partial_assembly = pa; }
/// Enable numerical integration rules. This means collocated quadrature at
/// the nodal points.
void EnableNI(bool ni) { numerical_integ = ni; }
/// Print timing summary of the solving routine.
/**
* The summary shows the timing in seconds in the first row of
*
*/
void PrintTimingData();
~IncompressibleNavierSolver();
/// Rotate entries in the time step and solution history arrays.
void UpdateTimestepHistory(real_t dt);
/// Compute CFL
real_t ComputeCFL(ParGridFunction &u, real_t dt);
protected:
/// Eliminate essential BCs in an Operator and apply to RHS.
void EliminateRHS(Operator &A,
ConstrainedOperator &constrainedA,
const Array<int> &ess_tdof_list,
Vector &x,
Vector &b,
Vector &X,
Vector &B,
int copy_interior = 0);
/// Enable/disable debug output.
bool debug = false;
/// Enable/disable verbose output.
bool verbose = true;
/// Enable/disable partial assembly of forms.
bool partial_assembly = false;
/// Enable/disable numerical integration rules of forms.
bool numerical_integ = false;
/// The parallel mesh.
ParMesh *pmesh = nullptr;
/// The order of the velocity and pressure space.
int velorder;
int porder;
int torder;
/// Kinematic viscosity (dimensionless).
real_t kin_vis;
Coefficient * kinvisCoeff = nullptr;
Coefficient *dtCoeff = nullptr;
IntegrationRules gll_rules;
/// Velocity $H^1$ finite element collection.
FiniteElementCollection *vfec = nullptr;
/// Psi $H^1$ finite element collection.
FiniteElementCollection *psifec = nullptr;
/// Pressure $H^1$ finite element collection.
FiniteElementCollection *pfec = nullptr;
/// Velocity $(H^1)^d$ finite element space.
ParFiniteElementSpace *vfes = nullptr;
/// Psi $(H^1)^d$ finite element space.
ParFiniteElementSpace *psifes = nullptr;
/// Pressure $H^1$ finite element space.
ParFiniteElementSpace *pfes = nullptr;
ParBilinearForm *velBForm = nullptr;
ParBilinearForm *psiBForm = nullptr;
ParBilinearForm *pBForm = nullptr;
ParLinearForm *velLForm = nullptr;
ParLinearForm *psiLForm = nullptr;
ParLinearForm *pLForm = nullptr;
std::vector<ParGridFunction*> velGF;
std::vector<ParGridFunction*> pGF;
ParGridFunction psiGF;
ParGridFunction DvGF, divVelGF, pRHS;
VectorGridFunctionCoefficient * DvelCoeff = nullptr;
DivergenceGridFunctionCoefficient * divVelCoeff = nullptr;
GridFunctionCoefficient * pRHSCoeff = nullptr;
UnitVectorGridFunctionCoeff * pUnitVectorCoeff = nullptr;
NonLinTermVectorGridFunctionCoeff * nonlinTermCoeff = nullptr;
PrevVelVectorGridFunctionCoeff * prevVelLoadCoeff = nullptr;
OperatorHandle vOp;
OperatorHandle psiOp;
OperatorHandle pOp;
Solver *velInvPC = nullptr;
CGSolver *velInv = nullptr;
ParLORDiscretization *lor = nullptr;
HypreBoomerAMG *psiInvPC = nullptr;
OrthoSolver *SpInvOrthoPC = nullptr;
CGSolver *psiInv = nullptr;
Solver *pInvPC = nullptr;
CGSolver *pInv = nullptr;
Vector velLF, psiLF, pLF;
// All essential attributes.
Array<int> vel_ess_attr;
Array<int> pres_ess_attr;
// All essential true dofs.
Array<int> vel_ess_tdof;
Array<int> pres_ess_tdof;
// Bookkeeping for velocity dirichlet bcs.
std::vector<VelDirichletBC_T> vel_dbcs;
// Print levels.
int pl_psolve = 0;
int pl_psisolve = 0;
int pl_velsolve = 0;
int pl_amg = 0;
#if defined(MFEM_USE_DOUBLE)
real_t rtol_psolve = 1e-10;
real_t rtol_psisolve = 1e-10;
real_t rtol_velsolve = 1e-12;
#elif defined(MFEM_USE_SINGLE)
real_t rtol_psolve = 1e-9;
real_t rtol_psisolve = 1e-5;
real_t rtol_velsolve = 1e-7;
#else
#error "Only single and double precision are supported!"
real_t rtol_psolve = 1e-12;
real_t rtol_psisolve = 1e-6;
real_t rtol_velsolve = 1e-8;
#endif
// Iteration counts.
int iter = 1, iter_vsolve = 0, iter_psolve = 0, iter_psisolve = 0;
// Residuals.
real_t res_vsolve = 0.0, res_psolve = 0.0, res_psisolve = 0.0;
};
} // namespace incompressible_navier
} // namespace mfem
#endif
+119
View File
@@ -0,0 +1,119 @@
#include "stokes_solver.hpp"
namespace mfem {
StokesOperator::StokesOperator(ParFiniteElementSpace &vel_fes,
ParFiniteElementSpace &pres_fes):
Operator(vel_fes.GetTrueVSize()+pres_fes.GetTrueVSize()),
vfes(vel_fes),
pfes(pres_fes),
offsets({0, vel_fes.GetTrueVSize(), pres_fes.GetTrueVSize()}),
intrules(0, Quadrature1D::GaussLobatto),
zero_coeff(0.0)
{
if (vel_fes.GetParMesh()->bdr_attributes.Size() > 0)
{
vel_ess_bdr.SetSize(vel_fes.GetParMesh()->bdr_attributes.Max());
vel_ess_bdr = 0.0;
pres_ess_bdr.SetSize(vel_fes.GetParMesh()->bdr_attributes.Max());
pres_ess_bdr = 0.0;
}
vfes.GetEssentialTrueDofs(vel_ess_bdr, vel_ess_tdofs);
pfes.GetEssentialTrueDofs(pres_ess_bdr, pres_ess_tdofs);
offsets.PartialSum();
vel_bc_gf.reset(new ParGridFunction(&vfes));
*vel_bc_gf = 0.0; //set the velocity grid function to zero
pres_bc_gf.reset(new ParGridFunction(&pfes));
*pres_bc_gf = 0.0; //set the pressure grid function to zero
// The nonlinear convective integrators use over-integration (dealiasing) as
// a stabilization mechanism.
ir_nl = intrules.Get(vfes.GetFE(0)->GetGeomType(),
(int)(ceil(1.5 * 2*(vel_fes.GetOrder(0)+1) - 3)));
ir = intrules.Get(vfes.GetFE(0)->GetGeomType(),
(int)(2*(vel_fes.GetOrder(0)+1) - 3));
ir_face = intrules.Get(vfes.GetFaceElement(0)->GetGeomType(),
(int)(2*(vel_fes.GetOrder(0)+1) - 3));
b11_form=nullptr;
b22_form=nullptr;
b12_form=nullptr;
b21_form=nullptr;
}
void StokesOperator::SetVelBC(std::vector<VelDirichletBC>& vvbc)
{
for(auto vbc=vvbc.begin();vbc!=vvbc.end();vbc++)
{
for (int i = 0; i < vbc->second->Size(); i++)
{
if (*(vbc->second)[i] == 1)
{
vel_ess_bdr[i] = 1;
}
}
}
vfes.GetEssentialTrueDofs(vel_ess_bdr, vel_ess_tdofs);
}
void StokesOperator::SetPressBC(std::vector<PresDirichletBC>& vpbc)
{
for(auto pbc=vpbc.begin();pbc!=vpbc.end();pbc++)
{
for(int i=0;i<pbc->second->Size();i++){
if (*(pbc->second)[i] == 1)
{
vel_ess_bdr[i] = 1;
}
}
}
pfes.GetEssentialTrueDofs(pres_ess_bdr, pres_ess_tdofs);
}
void StokesOperator::Mult(const Vector &x, Vector &y) const
{
}
void StokesOperator::Setup()
{
BilinearFormIntegrator *integrator;
delete b11_form;
b11_form=new ParBilinearForm(&vfes);
integrator=new ElasticityIntegrator(zero_coeff,*viscosity);
integrator->SetIntRule(&ir);
b11_form->AddDomainIntegrator(integrator);
delete b12_form;
b12_form=new ParMixedBilinearForm(&pfes,&vfes);
integrator=new VectorDivergenceIntegrator();
integrator->SetIntRule(&ir);
b12_form->AddDomainIntegrator(integrator);
delete b21_form;
b21_form=new ParMixedBilinearForm(&vfes,&pfes);
integrator=new GradientIntegrator();
integrator->SetIntRule(&ir);
b21_form->AddDomainIntegrator(integrator);
if (matrix_free)
{
b11_form->SetAssemblyLevel(AssemblyLevel::PARTIAL);
b12_form->SetAssemblyLevel(AssemblyLevel::PARTIAL);
b21_form->SetAssemblyLevel(AssemblyLevel::PARTIAL);
}
}
}
+76
View File
@@ -0,0 +1,76 @@
#ifndef STOKESSOLVER_H
#define STOKESSOLVER_H
#define STOKES_VERSION 0.1
#include "mfem.hpp"
namespace mfem {
using VelDirichletBC = std::pair<VectorCoefficient *, Array<int> *>;
using PresDirichletBC = std::pair<Coefficient *, Array<int> *>;
class StokesOperator:public Operator
{
public:
StokesOperator(ParFiniteElementSpace &vel_fes,
ParFiniteElementSpace &pres_fes);
void SetVelBC(std::vector<VelDirichletBC>& vvbc);
void SetPressBC(std::vector<PresDirichletBC>& vpbc);
virtual
void Mult(const Vector &x, Vector &y) const override;
const Array<int>& GetOffsets() const
{
return offsets;
}
void Setup();
void Assemble();
private:
ParFiniteElementSpace &vfes;
ParFiniteElementSpace &pfes;
// ParGridFunction &kinematic_viscosity;
std::unique_ptr<ParGridFunction> vel_bc_gf;
std::unique_ptr<ParGridFunction> pres_bc_gf;
Array<int> vel_ess_bdr;
Array<int> pres_ess_bdr;
Array<int> vel_ess_tdofs;
Array<int> pres_ess_tdofs;
bool matrix_free;
Array<int> offsets;
IntegrationRules intrules;
IntegrationRule ir; //general integraion rule
IntegrationRule ir_nl; //non-linear integration rule
IntegrationRule ir_face; //face integration rule
ConstantCoefficient zero_coeff;
std::unique_ptr<Coefficient> viscosity;
ParBilinearForm *b11_form; //velocity
ParBilinearForm *b22_form; //pressure
ParMixedBilinearForm *b12_form; //mixed (velocity,pressure)
ParMixedBilinearForm *b21_form; //mized (pressure,velocity)
BlockOperator* A;
};
}
#endif // STOKESSOLVER_H
+67 -15
View File
@@ -9,41 +9,76 @@
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
set(MESH_FILES
cube-nurbs.mesh
ijk-hex-nurbs.mesh
plus-nurbs-permuted.mesh
plus-nurbs.mesh
square-nurbs.mesh
two-cubes-nurbs-autoedge.mesh
two-cubes-nurbs-rot.mesh
two-cubes-nurbs.mesh
two-squares-nurbs-autoedge.mesh
two-squares-nurbs-rot.mesh
two-squares-nurbs.mesh
)
# Add a target to copy the mesh files from the source directory; used by sample
# runs.
set(SRC_MESH_FILES)
foreach(MESH_FILE ${MESH_FILES})
list(APPEND SRC_MESH_FILES ${CMAKE_CURRENT_SOURCE_DIR}/meshes/${MESH_FILE})
endforeach()
add_custom_command(OUTPUT data_is_copied
COMMAND ${CMAKE_COMMAND} -E make_directory meshes
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SRC_MESH_FILES} meshes/
COMMAND ${CMAKE_COMMAND} -E touch data_is_copied
COMMENT "Copying nurbs miniapps data files ...")
add_custom_target(copy_miniapps_nurbs_data DEPENDS data_is_copied)
add_mfem_miniapp(nurbs_ex1
MAIN nurbs_ex1.cpp
LIBRARIES mfem)
add_dependencies(nurbs_ex1 copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_ex3
MAIN nurbs_ex3.cpp
LIBRARIES mfem)
add_dependencies(nurbs_ex3 copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_ex5
MAIN nurbs_ex5.cpp
LIBRARIES mfem)
add_dependencies(nurbs_ex5 copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_ex24
MAIN nurbs_ex24.cpp
LIBRARIES mfem)
add_dependencies(nurbs_ex24 copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_curveint
MAIN nurbs_curveint.cpp
LIBRARIES mfem)
add_dependencies(nurbs_curveint copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_naca_cmesh
MAIN nurbs_naca_cmesh.cpp
LIBRARIES mfem)
add_dependencies(nurbs_naca_cmesh copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_printfunc
MAIN nurbs_printfunc.cpp
LIBRARIES mfem)
add_dependencies(nurbs_printfunc copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_patch_ex1
MAIN nurbs_patch_ex1.cpp
LIBRARIES mfem)
add_dependencies(nurbs_patch_ex1 copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_solenoidal
MAIN nurbs_solenoidal.cpp
LIBRARIES mfem)
add_dependencies(nurbs_solenoidal copy_miniapps_nurbs_data)
if (MFEM_ENABLE_TESTING)
add_test(NAME nurbs_ex1_1d_r1_o2_ser
@@ -90,25 +125,30 @@ if (MFEM_ENABLE_TESTING)
add_test(NAME nurbs_ex1_weak_patch_format_ser
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 0)
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh
-o 2 --weak-bc -r 0)
add_test(NAME nurbs_ex1_weak_patch_format_r1_ser
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 1)
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh
-o 2 --weak-bc -r 1)
add_test(NAME nurbs_printfunc
COMMAND $<TARGET_FILE:nurbs_printfunc>)
if (MFEM_USE_LAPACK)
add_test(NAME nurbs_patch_ex1_o4_r2_iro8_patcha_ser
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8 -patcha)
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8
-patcha)
endif()
add_test(NAME nurbs_patch_ex1_o4_r2_iro8_patcha_pa_ser
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8 -patcha -pa)
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8
-patcha -pa)
add_test(NAME nurbs_patch_ex1_o4_r2_iro8_patcha_fint_ser
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8 -patcha -fint)
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8
-patcha -fint)
add_test(NAME nurbs_curveint_unit_weight_ser
COMMAND $<TARGET_FILE:nurbs_curveint> -no-vis -no-visit
@@ -119,35 +159,43 @@ if (MFEM_ENABLE_TESTING)
-nw -n 9)
add_test(NAME nurbs_naca_cmesh_ser
COMMAND $<TARGET_FILE:nurbs_naca_cmesh> -no-vis -no-visit -ntail 80 -nbnd 80 -ntip 20 -nwake 40 -sw 2.0 -sbnd 2.5 -stip 1.1 -aoa 3)
COMMAND $<TARGET_FILE:nurbs_naca_cmesh> -no-vis -no-visit -ntail 80 -nbnd 80
-ntip 20 -nwake 40 -sw 2.0 -sbnd 2.5 -stip 1.1 -aoa 3)
add_test(NAME nurbs_ex1_two_squares_knot_insert
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs.mesh -o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs.mesh -o 1
-rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
add_test(NAME nurbs_ex1_two_squares_rot_knot_insert
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs-rot.mesh -o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs-rot.mesh
-o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
add_test(NAME nurbs_ex1_two_squares_autoedge_knot_insert
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh -o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh
-o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
add_test(NAME nurbs_ex1_two_cubes_knot_insert
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs.mesh -o 1 -r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs.mesh -o 1
-r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
add_test(NAME nurbs_ex1_two_cubes_rot_knot_insert
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs-rot.mesh -o 1 -r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs-rot.mesh -o 1
-r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
add_test(NAME nurbs_ex1_two_cubes_autoedge_knot_insert
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs-autoedge.mesh -o 1 -r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs-autoedge.mesh
-o 1 -r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
add_test(NAME nurbs_ex1_periodic_2d
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
-m ${PROJECT_SOURCE_DIR}/data/pipe-nurbs-2d.mesh -o 2 -r 2 --master "3" --slave "4")
-m ${PROJECT_SOURCE_DIR}/data/pipe-nurbs-2d.mesh -o 2 -r 2 --master "3"
--slave "4")
add_test(NAME nurbs_ex1_periodic_3d
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
@@ -205,10 +253,12 @@ if (MFEM_USE_MPI)
add_mfem_miniapp(nurbs_ex1p
MAIN nurbs_ex1p.cpp
LIBRARIES mfem)
add_dependencies(nurbs_ex1p copy_miniapps_nurbs_data)
add_mfem_miniapp(nurbs_ex11p
MAIN nurbs_ex11p.cpp
LIBRARIES mfem)
add_dependencies(nurbs_ex11p copy_miniapps_nurbs_data)
if (MFEM_ENABLE_TESTING)
add_test(NAME nurbs_ex1p_np=4
@@ -238,12 +288,14 @@ if (MFEM_USE_MPI)
add_test(NAME nurbs_ex1_weak_patch_format_np=4
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:nurbs_ex1p> -no-vis
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 0)
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2
--weak-bc -r 0)
add_test(NAME nurbs_ex1_weak_patch_format_r1_np=4
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:nurbs_ex1p> -no-vis
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 1)
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2
--weak-bc -r 1)
add_test(NAME nurbs_ex11p_np=4
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
+26 -13
View File
@@ -20,7 +20,8 @@ CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
MFEM_LIB_FILE = mfem_is_not_built
-include $(CONFIG_MK)
SEQ_MINIAPPS = nurbs_ex1 nurbs_patch_ex1 nurbs_ex3 nurbs_ex5 nurbs_ex24 nurbs_curveint nurbs_printfunc nurbs_solenoidal nurbs_naca_cmesh
SEQ_MINIAPPS = nurbs_ex1 nurbs_patch_ex1 nurbs_ex3 nurbs_ex5 nurbs_ex24 \
nurbs_curveint nurbs_printfunc nurbs_solenoidal nurbs_naca_cmesh
PAR_MINIAPPS = nurbs_ex1p nurbs_ex11p
ifeq ($(MFEM_USE_MPI),NO)
MINIAPPS = $(SEQ_MINIAPPS)
@@ -42,6 +43,17 @@ endif
all: $(MINIAPPS)
# For out-of-source builds, link the meshes directory from the source tree to
# the build tree. This is needed for running the tests and the sample runs when
# building out-of-source:
ifneq ($(SRC),)
meshes: $(SRC)meshes
ln -sf $(<) .
$(MINIAPPS): | meshes
.PHONY: copy-data
copy-data: | meshes
endif
MFEM_TESTS = MINIAPPS
include $(MFEM_TEST_MK)
@@ -63,13 +75,13 @@ EX1_ARGS_8 := -m ../../data/pipe-nurbs-2d.mesh -o 2 --weak-bc -r 2
EX1_ARGS_9 := -m ../../data/ball-nurbs.mesh -o 2 --weak-bc -r 0
EX1_ARGS_10 := -m ../../data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 0
EX1_ARGS_11 := -m ../../data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 1
EX1_ARGS_12 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares-nurbs.mesh -o 1 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares.ref
EX1_ARGS_13 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares-nurbs-rot.mesh -o 1 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares.ref
EX1_ARGS_14 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh -o 1 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares.ref
EX1_ARGS_15 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes-nurbs.mesh -o 1 -r 3 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes.ref
EX1_ARGS_16 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes-nurbs-rot.mesh -o 1 -r 3 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes.ref
EX1_ARGS_17 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes-nurbs-autoedge.mesh -o 1 -r 3 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes.ref
EX1_ARGS_18 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/cube-nurbs.mesh -pm "1" -ps "2" -rf $(MFEM_DIR)/miniapps/nurbs/meshes/cube.ref
EX1_ARGS_12 := -m meshes/two-squares-nurbs.mesh -o 1 -rf meshes/two-squares.ref
EX1_ARGS_13 := -m meshes/two-squares-nurbs-rot.mesh -o 1 -rf meshes/two-squares.ref
EX1_ARGS_14 := -m meshes/two-squares-nurbs-autoedge.mesh -o 1 -rf meshes/two-squares.ref
EX1_ARGS_15 := -m meshes/two-cubes-nurbs.mesh -o 1 -r 3 -rf meshes/two-cubes.ref
EX1_ARGS_16 := -m meshes/two-cubes-nurbs-rot.mesh -o 1 -r 3 -rf meshes/two-cubes.ref
EX1_ARGS_17 := -m meshes/two-cubes-nurbs-autoedge.mesh -o 1 -r 3 -rf meshes/two-cubes.ref
EX1_ARGS_18 := -m meshes/cube-nurbs.mesh -pm "1" -ps "2" -rf meshes/cube.ref
nurbs_ex1-test-seq: nurbs_ex1
@$(call mfem-test,$<,, NURBS miniapp)
@@ -126,10 +138,10 @@ nurbs_ex24-test-seq: nurbs_ex24
@$(call mfem-test,$<,, NURBS miniapp,$(EX24_ARGS_5))
SOL_ARGS_1 := -m $(MFEM_DIR)/data/pipe-nurbs-2d.mesh -r 1 -o 2
SOL_ARGS_1 := -m $(MFEM_DIR)/data/cube-nurbs.mesh -r 1 -o 2
nurbs_sol-test-seq: nurbs_solenoidal
SOL_ARGS_2 := -m $(MFEM_DIR)/data/cube-nurbs.mesh -r 1 -o 2
nurbs_solenoidal-test-seq: nurbs_solenoidal
@$(call mfem-test,$<,, NURBS miniapp,$(SOL_ARGS_1))
@$(call mfem-test,$<,, NURBS miniapp,$(SOl_ARGS_2))
@$(call mfem-test,$<,, NURBS miniapp,$(SOL_ARGS_2))
CI_ARGS_1 := -uw -n 9 -no-visit
CI_ARGS_2 := -nw -n 9 -no-visit
@@ -151,7 +163,7 @@ EX1P_ARGS_2 := -m ../../data/pipe-nurbs-2d.mesh -o 2 -no-ibp
EX1P_ARGS_3 := -m ../../data/ball-nurbs.mesh -o 2 --weak-bc -r 0
EX1P_ARGS_4 := -m ../../data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 0
EX1P_ARGS_5 := -m ../../data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 1
EX1P_ARGS_6 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/square-nurbs.mesh -r 4 -pm "1" -ps "2"
EX1P_ARGS_6 := -m meshes/square-nurbs.mesh -r 4 -pm "1" -ps "2"
nurbs_ex1p-test-par: nurbs_ex1p
@$(call mfem-test,$<, $(RUN_MPI), NURBS miniapp,$(EX1P_ARGS_1))
@@ -179,6 +191,7 @@ clean-build:
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@rm -f refined.mesh sin-fit.mesh ex5.mesh exsol.mesh mesh.* sol.* mode_* naca-cmesh.mesh sol_?.gf
@rm -f refined.mesh sin-fit.mesh ex5.mesh exsol.mesh mesh.* sol.* mode_*
@rm -f naca-cmesh.mesh sol_?.gf
@rm -rf Example1* Example3* Example5* Solenoidal_* ParaView
@rm -rf CurveInt Naca_cmesh glvis_naca-cmesh.mesh solution.dat
+251
View File
@@ -0,0 +1,251 @@
MFEM NURBS mesh v1.0
# 3D mesh with 8 (2x2x2) patches. Each patch
# has I*J*K elements where e.g. I is the patch
# index (starting from 1) in the x-dimension.
dimension
3
elements
8
1 5 0 1 4 3 9 10 13 12
1 5 1 2 5 4 10 11 14 13
1 5 3 4 7 6 12 13 16 15
1 5 4 5 8 7 13 14 17 16
1 5 9 10 13 12 18 19 22 21
1 5 10 11 14 13 19 20 23 22
1 5 12 13 16 15 21 22 25 24
1 5 13 14 17 16 22 23 26 25
boundary
24
1 3 0 3 4 1
1 3 1 4 5 2
1 3 3 6 7 4
1 3 4 7 8 5
6 3 18 19 22 21
6 3 19 20 23 22
6 3 21 22 25 24
6 3 22 23 26 25
5 3 0 9 12 3
5 3 3 12 15 6
5 3 9 18 21 12
5 3 12 21 24 15
3 3 2 5 14 11
3 3 5 8 17 14
3 3 11 14 23 20
3 3 14 17 26 23
2 3 0 1 10 9
2 3 9 10 19 18
2 3 1 2 11 10
2 3 10 11 20 19
4 3 6 15 16 7
4 3 15 24 25 16
4 3 7 16 17 8
4 3 16 25 26 17
edges
54
0 0 1
1 1 4
0 3 4
1 0 3
0 9 10
1 10 13
0 12 13
1 9 12
2 0 9
2 1 10
2 4 13
2 3 12
3 1 2
1 2 5
3 4 5
3 10 11
1 11 14
3 13 14
2 2 11
2 5 14
4 4 7
0 6 7
4 3 6
4 13 16
0 15 16
4 12 15
2 7 16
2 6 15
4 5 8
3 7 8
4 14 17
3 16 17
2 8 17
0 18 19
1 19 22
0 21 22
1 18 21
5 9 18
5 10 19
5 13 22
5 12 21
3 19 20
1 20 23
3 22 23
5 11 20
5 14 23
4 22 25
0 24 25
4 21 24
5 16 25
5 15 24
4 23 26
3 25 26
5 17 26
vertices
27
knotvectors
6
1 2 0 0 1 1
1 2 0 0 1 1
1 2 0 0 1 1
1 3 0 0 0.5 1 1
1 3 0 0 0.5 1 1
1 3 0 0 0.5 1 1
weights
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
FiniteElementSpace
FiniteElementCollection: NURBS1
VDim: 3
Ordering: 1
0 0 0
1 0 0
2 0 0
0 1 0
1 1 0
2 1 0
0 2 0
1 2 0
2 2 0
0 0 1
1 0 1
2 0 1
0 1 1
1 1 1
2 1 1
0 2 1
1 2 1
2 2 1
0 0 2
1 0 2
2 0 2
0 1 2
1 1 2
2 1 2
0 2 2
1 2 2
2 2 2
1.5 0 0
1.5 1 0
1.5 0 1
1.5 1 1
1 1.5 0
0 1.5 0
1 1.5 1
0 1.5 1
2 1.5 0
1.5 2 0
2 1.5 1
1.5 2 1
0 0 1.5
1 0 1.5
1 1 1.5
0 1 1.5
1.5 0 2
1.5 1 2
2 0 1.5
2 1 1.5
1 1.5 2
0 1.5 2
1 2 1.5
0 2 1.5
2 1.5 2
1.5 2 2
2 2 1.5
1.5 1.5 0
1.5 1.5 1
1.5 0 1.5
1.5 1 1.5
1 1.5 1.5
0 1.5 1.5
2 1.5 1.5
1.5 2 1.5
1.5 1.5 2
1.5 1.5 1.5
@@ -0,0 +1,109 @@
MFEM NURBS mesh v1.0
# 5 2D patches arranged in a "+" sign configuration.
# The geometry is the same as plus-nurbs.mesh, but
# the patches are ordered differently:
#
# +-----+
# | 2 |
# +-----+-----+-----+
# | 1 | 4 | 3 |
# +-----+-----+-----+
# | 0 |
# +-----+
#
# This mesh can be used to test the generation of
# patch topology to knotvector index maps.
dimension
2
elements
5
1 3 0 1 4 3
1 3 2 3 7 6
1 3 7 8 11 10
1 3 4 5 9 8
1 3 3 4 8 7
boundary
0
edges
0
vertices
12
patches
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
0 0 1
1 0 1
0 1 1
1 1 1
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
-1 1 1
0 1 1
-1 2 1
0 2 1
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
0 2 1
1 2 1
0 3 1
1 3 1
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
1 1 1
2 1 1
1 2 1
2 2 1
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
0 1 1
1 1 1
0 2 1
1 2 1
+105
View File
@@ -0,0 +1,105 @@
MFEM NURBS mesh v1.0
# 5 2D patches arranged in a "+" sign configuration.
# Patches are ordered using cartesian indexing:
#
# +-----+
# | 4 |
# +-----+-----+-----+
# | 1 | 2 | 3 |
# +-----+-----+-----+
# | 0 |
# +-----+
dimension
2
elements
5
1 3 0 1 4 3
1 3 2 3 7 6
1 3 3 4 8 7
1 3 4 5 9 8
1 3 7 8 11 10
boundary
0
edges
0
vertices
12
patches
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
0 0 1
1 0 1
0 1 1
1 1 1
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
-1 1 1
0 1 1
-1 2 1
0 2 1
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
0 1 1
1 1 1
0 2 1
1 2 1
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
1 1 1
2 1 1
1 2 1
2 2 1
knotvectors
2
1 2 0 0 1 1
1 2 0 0 1 1
dimension
2
controlpoints
0 2 1
1 2 1
0 3 1
1 3 1
+1 -1
View File
@@ -388,7 +388,7 @@ int main(int argc, char *argv[])
delete hdiv_coll;
delete mesh;
if (err_div > 1e4*std::numeric_limits<real_t>::epsilon() )
if (err_div > 2e4*std::numeric_limits<real_t>::epsilon() )
{
mfem::out << "std::numeric_limits<real_t>::epsilon() = "
<< std::numeric_limits<real_t>::epsilon() << "\n";
+26 -5
View File
@@ -9,6 +9,23 @@
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
set(MESH_GF_FILES
../gslib/triple-pt-1.mesh
../gslib/triple-pt-1.gf
)
# Add a target to copy the mesh files from the source directory; used by sample
# runs.
set(SRC_MESH_GF_FILES)
foreach(MESH_FILE ${MESH_GF_FILES})
list(APPEND SRC_MESH_GF_FILES ${CMAKE_CURRENT_SOURCE_DIR}/${MESH_FILE})
endforeach()
add_custom_command(OUTPUT data_is_copied
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SRC_MESH_GF_FILES} ../gslib/.
COMMAND ${CMAKE_COMMAND} -E touch data_is_copied
COMMENT "Copying tools miniapps data files ...")
add_custom_target(copy_miniapps_tools_data DEPENDS data_is_copied)
add_mfem_miniapp(display-basis
MAIN display-basis.cpp
${MFEM_MINIAPPS_COMMON_HEADERS}
@@ -28,11 +45,6 @@ add_mfem_miniapp(convert-dc
add_mfem_miniapp(lor-transfer
MAIN lor-transfer.cpp LIBRARIES mfem)
if (MFEM_USE_MPI)
add_mfem_miniapp(plor-transfer
MAIN plor-transfer.cpp LIBRARIES mfem)
endif()
add_mfem_miniapp(tmop-check-metric
MAIN tmop-check-metric.cpp LIBRARIES mfem)
@@ -42,6 +54,15 @@ add_mfem_miniapp(tmop-metric-magnitude
LIBRARIES mfem-common)
if (MFEM_USE_MPI)
add_mfem_miniapp(gridfunction-bounds
MAIN gridfunction-bounds.cpp
${MFEM_MINIAPPS_COMMON_HEADERS}
LIBRARIES mfem-common)
add_dependencies(gridfunction-bounds copy_miniapps_tools_data)
add_mfem_miniapp(plor-transfer
MAIN plor-transfer.cpp LIBRARIES mfem)
add_mfem_miniapp(nodal-transfer
MAIN nodal-transfer.cpp LIBRARIES mfem)
endif()
+290
View File
@@ -0,0 +1,290 @@
// 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.
//
// ---------------------------------------------------------------------
// Compute bounds of the given grid-function
// ---------------------------------------------------------------------
//
// This miniapp computes piecewise linear bounds on a given gridfunction, and
// visualizes the lower and upper bound for each element. The bounding approach
// is based on the method described in:
//
// (1) Section 3 of Mittal et al., "General Field Evaluation in High-Order
// Meshes on GPUs"
// and
// (2) Dzanic et al., "A method for bounding high-order finite element
// functions: Applications to mesh validity and bounds-preserving limiters".
//
//
// Compile with: make gridfunction-bounds
//
// Sample runs:
// mpirun -np 4 gridfunction-bounds
// mpirun -np 4 gridfunction-bounds -nb 100 -ref 5 -bt 2 -l2
#include "mfem.hpp"
#include <memory>
#include <iostream>
#include <fstream>
using namespace mfem;
using namespace std;
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
char *title, int pos_x, int pos_y);
int main (int argc, char *argv[])
{
// 0. Initialize MPI and HYPRE.
Mpi::Init(argc, argv);
Hypre::Init();
// Set the method's default parameters.
const char *mesh_file = "../gslib/triple-pt-1.mesh";
const char *sltn_file = "../gslib/triple-pt-1.gf";
int ref = 2;
bool visualization = true;
bool visit = false;
int b_type = -1;
bool continuous = true;
int nbrute = 0;
// Parse command-line options.
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&sltn_file, "-s", "--sltn",
"Solution file to use.");
args.AddOption(&ref, "-ref", "--piecewise-linear-ref-factor",
"Scaling factor for resolution of piecewise linear bounds."
" If less than 2, the resolution is picked automatically");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&visit, "-visit", "--visit", "-no-visit",
"--no-visit",
"Enable or disable VisIt output.");
args.AddOption(&b_type, "-bt", "--basis-type",
"Project input function to a different bases. "
"-1 = don't project (default)."
"0 = Gauss-Legendre nodes. "
"1 = Gauss-Lobatto nodes. "
"2 = uniformly spaced nodes. ");
args.AddOption(&continuous, "-h1", "--h1", "-l2", "--l2",
"Use continuous or discontinuous space.");
args.AddOption(&nbrute, "-nb", "--nbrute",
"Brute force search for minimum in an array of nxnxn points "
"in each element.");
args.ParseCheck();
Mesh mesh(mesh_file, 1, 1, false);
const int dim = mesh.Dimension();
if (continuous && b_type != -1)
{
MFEM_VERIFY(b_type > 0, "Continuous space do not support GL nodes. "
"Please use basis type: 1 for Lagrange interpolants on GLL "
" nodes 2 for positive bases on uniformly spaced nodes.");
}
std::unique_ptr<int[]> partition(
mesh.GeneratePartitioning(Mpi::WorldSize())
);
ifstream mat_stream_1(sltn_file);
std::unique_ptr<GridFunction> func(new GridFunction(&mesh, mat_stream_1));
ParMesh pmesh(MPI_COMM_WORLD, mesh, partition.get());
ParGridFunction pfunc(&pmesh, func.get(), partition.get());
int func_order = func->FESpace()->GetMaxElementOrder();
int vdim = pfunc.FESpace()->GetVDim();
int nel = pmesh.GetNE();
func.reset();
mesh.Clear();
partition.reset();
// Project input function based on user input
ParGridFunction *pfunc_proj = NULL;
if (b_type >= 0)
{
FiniteElementCollection *fec = NULL;
if (continuous)
{
fec = new H1_FECollection(func_order, dim, b_type);
}
else
{
fec = new L2_FECollection(func_order, dim, b_type);
}
int ordering = pfunc.FESpace()->GetOrdering();
ParFiniteElementSpace *fes = new ParFiniteElementSpace(&pmesh, fec,
vdim, ordering);
pfunc_proj = new ParGridFunction(fes);
pfunc_proj->MakeOwner(fec);
pfunc_proj->ProjectGridFunction(pfunc);
if (Mpi::Root())
{
cout << "fec name orig: " << pfunc.FESpace()->FEColl()->Name() <<
endl;
cout << "fec name: " << fec->Name() << endl;
}
}
else
{
pfunc_proj = &pfunc;
if (Mpi::Root())
{
cout << "fec name: " << pfunc.FESpace()->FEColl()->Name() << endl;
}
}
L2_FECollection fec_pc(0, dim);
ParFiniteElementSpace fes_pc(&pmesh, &fec_pc, vdim, Ordering::byNODES);
ParGridFunction lowerb(&fes_pc), upperb(&fes_pc);
// Compute bounds
pfunc_proj->GetElementBounds(lowerb, upperb, ref);
Vector bound_min(vdim), bound_max(vdim);
for (int d = 0; d < vdim; d++)
{
Vector lowerT(lowerb.GetData() + d*nel, nel);
Vector upperT(upperb.GetData() + d*nel, nel);
bound_min(d) = lowerT.Min();
bound_max(d) = upperT.Max();
}
MPI_Allreduce(MPI_IN_PLACE, bound_min.GetData(), vdim,
MPITypeMap<real_t>::mpi_type, MPI_MIN, pmesh.GetComm());
MPI_Allreduce(MPI_IN_PLACE, bound_max.GetData(), vdim,
MPITypeMap<real_t>::mpi_type, MPI_MAX, pmesh.GetComm());
// GLVis Visualization
if (visualization)
{
char title1[] = "Input gridfunction";
VisualizeField(pmesh, pfunc, title1, 0, 0);
if (b_type >= 0)
{
char title1p[] = "Projected gridfunction";
VisualizeField(pmesh, *pfunc_proj, title1p, 0, 400);
}
char title2[] = "Element-wise lower bound";
VisualizeField(pmesh, lowerb, title2, 400, 0);
char title3[] = "Element-wise upper bound";
VisualizeField(pmesh, upperb, title3, 800, 0);
}
// Visit Visualization
if (visit)
{
VisItDataCollection visit_dc("jacobian-determinant-bounds", &pmesh);
visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);
visit_dc.RegisterField("input-function", &pfunc);
if (b_type >= 0)
{
visit_dc.RegisterField("projected-function", pfunc_proj);
}
visit_dc.RegisterField("lower-bound", &lowerb);
visit_dc.RegisterField("upper-bound", &upperb);
visit_dc.Save();
}
if (nbrute > 0)
{
Vector global_min(vdim), global_max(vdim);
global_min = numeric_limits<real_t>::max();
global_max = numeric_limits<real_t>::min();
// search for the minimum value of pfunc_proj in each element at
// an array of integration points
for (int e = 0; e < pmesh.GetNE(); e++)
{
IntegrationPoint ip;
for (int k = 0; k < (dim > 2 ? nbrute : 1); k++)
{
ip.z = k/(nbrute-1.0);
for (int j = 0; j < (dim > 1 ? nbrute : 1); j++)
{
ip.y = j/(nbrute-1.0);
for (int i = 0; i < nbrute; i++)
{
ip.x = i/(nbrute-1.0);
for (int d = 0; d < vdim; d++)
{
real_t val = pfunc_proj->GetValue(e, ip, d+1);
global_min(d) = min(global_min(d), val);
global_max(d) = max(global_max(d), val);
}
}
}
}
}
MPI_Allreduce(MPI_IN_PLACE, global_min.GetData(), vdim,
MPITypeMap<real_t>::mpi_type, MPI_MIN, pmesh.GetComm());
MPI_Allreduce(MPI_IN_PLACE, global_max.GetData(), vdim,
MPITypeMap<real_t>::mpi_type, MPI_MAX, pmesh.GetComm());
if (Mpi::Root())
{
for (int d = 0; d < vdim; d++)
{
cout << "Brute force and bounding comparison for component " <<
d << endl;
cout << "Brute force minimum and minimum bound: " << global_min(d)
<< " " << bound_min(d) << endl;
cout << "Brute force maximum and maximum bound: " << global_max(d)
<< " " << bound_max(d) << endl;
cout << "The difference in bounds is: " <<
global_min(d)-bound_min(d) << " " <<
bound_max(d)-global_max(d) << endl;
}
}
}
if (nbrute == 0 && Mpi::Root())
{
for (int d = 0; d < vdim; d++)
{
cout << "Minimum bound for component " << d << " is " <<
bound_min(d) << endl;
cout << "Maximum bound for component " << d << " is " <<
bound_max(d) << endl;
}
}
if (b_type >= 0)
{
delete pfunc_proj;
}
return 0;
}
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
char *title, int pos_x, int pos_y)
{
socketstream sock;
if (pmesh.GetMyRank() == 0)
{
sock.open("localhost", 19916);
sock << "solution\n";
}
pmesh.PrintAsOne(sock);
input.SaveAsOne(sock);
if (pmesh.GetMyRank() == 0)
{
sock << "window_title '"<< title << "'\n"
<< "window_geometry "
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
<< "keys jRmclApppppppppppp//]]]]]]]]" << endl;
}
}
+3 -3
View File
@@ -27,7 +27,7 @@ MFEM_LIB_FILE = mfem_is_not_built
SEQ_MINIAPPS = display-basis load-dc convert-dc get-values lor-transfer \
tmop-check-metric tmop-metric-magnitude
PAR_MINIAPPS = nodal-transfer plor-transfer
PAR_MINIAPPS = nodal-transfer plor-transfer gridfunction-bounds
ifeq ($(MFEM_USE_MPI),NO)
MINIAPPS = $(SEQ_MINIAPPS)
@@ -83,7 +83,7 @@ RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
# Testing: Specific execution options
# Do not test: display-basis, load-dc, convert-dc, get-values, lor-transfer, plor-transfer
NO_TEST_APPS = display-basis load-dc convert-dc get-values lor-transfer \
plor-transfer tmop-check-metric tmop-metric-magnitude
plor-transfer tmop-check-metric tmop-metric-magnitude gridfunction-bounds
$(foreach app,$(NO_TEST_APPS),$(app)-test-seq $(app)-test-par):
@true
@@ -100,5 +100,5 @@ clean-build:
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@rm -rf mesh_* gridfunc_*
@rm -rf mesh_* gridfunc_* jacobian-determinant-bounds*
@true
+2
View File
@@ -161,6 +161,8 @@ add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} unit_tests)
add_dependencies(unit_tests copy_data)
# ParSubMesh tests need meshes in ../../miniapps/multidomain
add_dependencies(unit_tests copy_miniapps_multidomain_data)
# NURBS tests need meshes in ../../miniapps/nurbs
add_dependencies(unit_tests copy_miniapps_nurbs_data)
# Copy data to the build directory.
add_custom_command(TARGET unit_tests POST_BUILD
+6 -7
View File
@@ -9,16 +9,15 @@
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "mfem.hpp"
// must be included after mfem.hpp
#include "general/forall.hpp"
#include "unit_tests.hpp"
#include <algorithm>
#include <limits>
#include "mfem.hpp"
#include "unit_tests.hpp"
// must be included after mfem.hpp
#include "general/reducers.hpp"
using namespace mfem;
TEST_CASE("Reduce Sum", "[Reduction],[CUDA]")
+6 -1
View File
@@ -181,11 +181,16 @@ $(eval $(call psedov_tests,cuda_uvm,CUDA_UVM,cuda:uvm))
# For out-of-source builds, copy the meshes in ../../miniapps/multidomain from
# the source location; these are used by 'punit_tests'.
# Also, link the directory 'meshes' in ../../miniapps/nurbs from the source
# location; this is needed by 'unit_tests'.
ifneq ($(SRC),)
.PHONY: copy-miniapps-multidomain-data
.PHONY: copy-miniapps-multidomain-data copy-miniapps-nurbs-meshes
copy-miniapps-multidomain-data:
$(MAKE) -C ../../miniapps/multidomain copy-data
punit_tests: | copy-miniapps-multidomain-data
copy-miniapps-nurbs-meshes:
$(MAKE) -C ../../miniapps/nurbs copy-data
unit_tests: | copy-miniapps-nurbs-meshes
endif
# For out-of-source builds, copy the meshes in ../../miniapps/meshing from the
+47
View File
@@ -84,3 +84,50 @@ TEST_CASE("NURBS refinement and coarsening by spacing formulas", "[NURBS]")
const real_t error = d.Norml2();
REQUIRE(error == MFEM_Approx(0.0));
}
TEST_CASE("NURBS mesh reconstruction", "[NURBS]")
{
auto mesh_fname =
GENERATE("../../data/segment-nurbs.mesh",
"../../data/square-nurbs.mesh",
"../../data/beam-quad-nurbs.mesh",
"../../data/pipe-nurbs.mesh",
"../../miniapps/nurbs/meshes/two-squares-nurbs.mesh",
"../../miniapps/nurbs/meshes/two-squares-nurbs-rot.mesh",
"../../miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh",
"../../miniapps/nurbs/meshes/plus-nurbs.mesh",
"../../miniapps/nurbs/meshes/plus-nurbs-permuted.mesh",
"../../miniapps/nurbs/meshes/ijk-hex-nurbs.mesh");
Mesh mesh1(mesh_fname, 1, 1);
// Reconstruct mesh using patches + topology
Array<NURBSPatch*> patches;
mesh1.GetNURBSPatches(patches);
const Mesh patchtopo = mesh1.NURBSext->GetPatchTopology();
NURBSExtension ne(&patchtopo, patches);
Mesh mesh2(ne);
// Meshes should be identical
REQUIRE(mesh1.GetNodes()->Size() > 0);
REQUIRE(mesh1.GetNodes()->Size() == mesh2.GetNodes()->Size());
Vector diff(*mesh1.GetNodes());
diff -= *mesh2.GetNodes();
const real_t error = diff.Norml2();
REQUIRE(error == MFEM_Approx(0.0));
// Compare weights (these are stored separately from nodes)
REQUIRE(mesh1.NURBSext->GetWeights().Size() > 0);
REQUIRE(mesh1.NURBSext->GetWeights().Size() ==
mesh2.NURBSext->GetWeights().Size());
Vector wdiff = mesh1.NURBSext->GetWeights();
wdiff -= mesh2.NURBSext->GetWeights();
const real_t werror = wdiff.Norml2();
REQUIRE(werror == MFEM_Approx(0.0));
// Cleanup
for (auto *p : patches) { delete p; }
}