Compare commits

...
Author SHA1 Message Date
John Camier f47a53e514 Merge branch 'master' into checkpointing 2026-07-30 10:02:58 -04:00
Veselin Dobrev 5c7073d4ea Merge pull request #5407 from mfem/skip-branch-history
Add option to skip branch-history check
2026-07-28 12:17:52 -07:00
Tzanio Kolev dc68ebcf57 Merge pull request #5387 from mfem/tdrwenski/add-ccache-ci
Add ccache to GitHub CI builds
2026-07-28 10:17:25 -07:00
John Camier 49bd65fe46 Merge branch 'master' into checkpointing 2026-07-23 13:28:57 -04:00
John Camier bbecf57ea0 Merge branch 'master' into tdrwenski/add-ccache-ci 2026-07-23 13:27:08 -04:00
John Camier 278e7f1fe6 Merge branch 'master' into skip-branch-history 2026-07-23 13:26:46 -04:00
Veselin Dobrev 9c17c781e1 Merge pull request #5274 from AnnevanGils/complexgridfunction_Lp_error
ComplexGridFunction Lp error implementation
2026-07-22 11:12:17 -07:00
Veselin Dobrev f72b0151e2 Merge pull request #5406 from mfem/gslib-uint-fix
Bug fix for GSLIB: undefined type uint in  header file
2026-07-22 11:07:26 -07:00
Will Pazner c8ef9676e8 Merge pull request #5402 from nmnobre/output
Fix output of H(curl) vars in 1d
2026-07-21 11:22:03 -07:00
Will Pazner 10e95b9246 Add note about branch-history to README 2026-07-20 10:02:33 -07:00
Will Pazner 57dd7a5c9a Change branch-history check logic 2026-07-20 10:00:41 -07:00
Tara Drwenski 1f551b5aac Add ccache for enzyme to CI 2026-07-17 09:02:59 -07:00
Tara Drwenski 5d909dbcf1 Inject ccache via OMPI_CXX for MPI builds 2026-07-16 11:00:48 -07:00
Tara Drwenski 896c3bbe30 Improve cleanup cache workflow to ensure all caches get removed 2026-07-16 11:00:48 -07:00
Sohail Reddy b1def1e3fa Added check to ensure gslib's integer-type 'uint' is 'unsigned int' 2026-07-15 20:50:59 -07:00
Veselin Dobrev 891b675c7f Use 'hypot' instead of 'sqrt' + squares. 2026-07-15 15:50:01 -07:00
John Camier cd79baee29 Merge branch 'master' into checkpointing 2026-07-15 09:02:09 -07:00
John Camier 6e26615ef9 Merge branch 'master' into tdrwenski/add-ccache-ci 2026-07-15 08:31:36 -07:00
John Camier 861f629b58 Merge branch 'master' into skip-branch-history 2026-07-15 07:40:30 -07:00
Will Pazner 951cf8886b Merge pull request #5411 from Pennycook/cudss-0.8.0
Add support for cuDSS 0.8.0
2026-07-14 18:18:46 -07:00
Ketan Mittal f0f1052c99 Merge branch 'master' into gslib-uint-fix 2026-07-14 13:45:26 -07:00
Veselin Dobrev 9a87b34c47 Merge pull request #5397 from mfem/spde_small_fix
Fix in the SPDE documentation - a factor of 2
2026-07-14 12:19:15 -07:00
Veselin Dobrev 80e40ace14 Merge pull request #5372 from mfem/bugfix-project
bugfix for coefficient project to quadrature function
2026-07-14 12:18:12 -07:00
Veselin Dobrev f5d71a2798 Merge pull request #5394 from mfem/fix_find_SuiteSparse
fix for finding SuiteSparse with the latest PETSc
2026-07-14 12:16:33 -07:00
Veselin Dobrev 9981355ba2 Merge pull request #5230 from mfem/lorentz-device
Lorentz with particles on device
2026-07-14 12:14:48 -07:00
John Pennycook 1416665dc3 Add support for cuDSS 0.8.0
cuDSS 0.8.0 includes some API breaking changes, as described here:
https://docs.nvidia.com/cuda/cudss/migration_guide.html

This commit adds support for the new 0.8.0 API, guarded by #ifdef
macros to ensure compatibility for previous 0.7.x builds.
2026-07-13 02:44:16 -07:00
Will Pazner 10868379f8 Add skip check output 2026-07-09 17:55:11 -07:00
Will Pazner 8c25b2f8c3 Add skip-branch-history option 2026-07-09 17:50:25 -07:00
Sohail Reddy f0d4d84a91 Bug fix for type uint = unsigned in, defined in gslib's internal header 2026-07-09 16:08:44 -07:00
Nuno Nobre d7c781a6f9 Fix output of H(curl) vars in 1d 2026-07-09 01:49:26 +01:00
Ketan Mittal c9b2ed7a65 Merge branch 'lorentz-device' of https://github.com/mfem/mfem into lorentz-device 2026-07-07 14:14:41 -07:00
Ketan Mittal 9eaa3cdf0a merge with master and resolve conflicts 2026-07-07 14:14:25 -07:00
Ketan MittalSeth Wattscopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Tzanio Kolev
17d1afc3b7 Option to specify increase in axis-aligned bounding box size for surface meshes with FindPointsGSLIB (#5259)
* initial commit - working alg

* Add new source files to CMakeLists.txt

* fix bdr tol when min bb size is specified

* support for triangles in surface mesh capability. tested for mixed meshes as well

* empty partition fix and uninitialized values for surface mesh

* fix variable naming and getboundingboxmesh on device

* change semantics of bounding box input for surface meshes

* make style

* documentation and clean up

* fix device access

* update serial miniapp and rename some variables

* minor

* add unit tests for surface meshes

* consolidate shared machinery in a helper file

* rename bb_t

* rename some functions and clean up

* minor

* simplify includes

* remove unused parameter and improve documentation

* restore whitespace

* Make gslib local helpers static

* reviewer comments

* fix edge initialization

* add the new kernel helper in CMakeLists.txt

* manage life of crystal router object in FindPointsGSLIB

* get rid of unnecessary MFEM_DEVICE_SYNC

---------

Co-authored-by: Seth Watts <watts24@llnl.gov>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Tzanio Kolev <tzanio@llnl.gov>
2026-07-07 13:35:30 -07:00
Tzanio Kolev 0eaa3b521a Merge branch 'master' into spde_small_fix 2026-07-07 13:31:32 -07:00
Tzanio Kolev 46d05563d9 Merge pull request #5379 from mfem/speed-up-gitignore-job
Optimize gitignore job
2026-07-07 13:05:55 -07:00
Tzanio Kolev 27c1ea66a4 Merge pull request #5380 from mfem/add-workflow-path-filters
Add path filters to non-required workflows
2026-07-07 13:04:03 -07:00
Tzanio Kolev 7b69944246 Merge branch 'master' into lorentz-device 2026-07-07 11:56:03 -07:00
blaz fa13c848c9 fix in the documentation - a factor of 2 2026-07-07 00:13:18 -07:00
Veselin Dobrev 8d09be7080 Merge pull request #5330 from mfem/hcurl_domain_lf
Add device assembly support for H(curl) VectorFEDomainLFIntegrator
2026-07-06 14:53:56 -07:00
Veselin Dobrev 7f71dacae6 Merge pull request #5388 from mfem/nbeams/fix-gko-hypre-type
Fix gko_hypre_* types creation
2026-07-06 14:52:14 -07:00
Tzanio Kolev bab838ffd2 Merge branch 'master' into checkpointing 2026-07-05 21:37:04 -07:00
blaz ef38472a73 small fix 2026-07-04 22:07:43 -07:00
blaz 37778f15d9 added makefile 2026-07-04 21:59:29 -07:00
blaz 5548b5d7c3 consolidated headers 2026-07-04 20:59:22 -07:00
blaz 7cc7f6920e fic 2026-07-04 20:38:13 -07:00
blaz 735672ee1a fixes 2026-07-04 20:34:09 -07:00
blaz c93dc09e93 style 2026-07-04 20:17:54 -07:00
blaz 8998c2381f fix 2026-07-04 20:12:33 -07:00
blaz 04fd73728d rm tst_* 2026-07-04 20:10:13 -07:00
blaz 7408f3c4be fixes 2026-07-04 20:08:49 -07:00
blaz ddada83674 fix 2026-07-04 20:02:51 -07:00
blaz 7091425fd2 fixed InMemoryCheckpointStorage 2026-07-04 19:59:00 -07:00
blaz 1d63176d4b new names of the files 2026-07-04 19:38:00 -07:00
blaz ac2e40f928 fixes 2026-07-04 19:15:08 -07:00
bslazarov a7b74155f7 fix for finding SuiteSparse with the latest PETSc 2026-07-04 18:40:59 -07:00
John Camier 0c20eef8fe Merge branch 'master' into lorentz-device 2026-07-03 19:04:50 +02:00
John Camier 842514bde2 Merge branch 'master' into bugfix-project 2026-07-03 19:03:33 +02:00
John Camier ee498a19f7 Merge branch 'master' into add-workflow-path-filters 2026-07-03 19:03:11 +02:00
John Camier bd5f9d80b4 Merge branch 'master' into speed-up-gitignore-job 2026-07-02 18:06:03 +02:00
Veselin Dobrev 6ee3bbde89 Merge pull request #5346 from nmnobre/hypremat
Preemptively delete rownnz if ownership flags set to -1
2026-07-01 12:18:03 -07:00
Veselin Dobrev 92f4fe3bd0 Merge pull request #5383 from mfem/raja-stream-fix
Raja stream fix
2026-07-01 10:02:00 -07:00
nbeams 0171b4b02d Only set gko_hypre_* types when building with MPI 2026-06-30 21:20:25 +00:00
Tara Drwenski 43de095a2d Decrease ccache max size to 1G 2026-06-30 13:18:51 -07:00
Tara Drwenski b25f438922 Delete a PR's caches when it is closed 2026-06-30 10:42:48 -07:00
Tara Drwenski 49310d0260 Increase ccache max size to 2G 2026-06-30 09:39:18 -07:00
Tara Drwenski a9bc59fdd5 Add ccache sloppiness and basedir for direct-mode hits 2026-06-30 09:38:28 -07:00
Tara Drwenski 17fa9b844c Shorten comment 2026-06-30 08:51:51 -07:00
John Camier 68e3a929c2 Merge branch 'master' into lorentz-device 2026-06-30 17:51:21 +02:00
Tara Drwenski 821a899d50 Add ccache to GitHub CI builds 2026-06-29 17:11:06 -07:00
John Camier 06d18956f8 Merge branch 'master' into bugfix-project 2026-06-27 20:14:54 +02:00
John Camier 60c2ac77d1 Merge branch 'master' into raja-stream-fix 2026-06-27 20:04:17 +02:00
John Camier ee23534091 Merge branch 'master' into speed-up-gitignore-job 2026-06-27 20:03:58 +02:00
John Camier c0da3d6aa9 Merge branch 'master' into add-workflow-path-filters 2026-06-27 20:01:48 +02:00
Andrew Ho fef38a9fd2 RAJA resources appear to be relatively lightweight, just construct it when needed 2026-06-26 14:45:43 -07:00
Tzanio Kolev 01aa047be1 Merge pull request #5373 from mfem/fix-project-bdr-types
[BUG] Fixed type narrowing in ProjectBdrCoefficientNormal unit test
2026-06-26 12:11:27 -07:00
Tzanio Kolev 8c68e8402f Merge branch 'master' into speed-up-gitignore-job 2026-06-26 11:44:38 -07:00
Tzanio Kolev a24e3f6dfb Merge pull request #5365 from mfem/fix-cmake-parallel
Fix CMake parallel build issue
2026-06-26 11:34:26 -07:00
Tzanio Kolev 950f406803 Merge pull request #5377 from mfem/fix-specializations
Fix AddSpecialization header includes
2026-06-26 11:33:24 -07:00
Tzanio Kolev ade7aedfc6 Merge pull request #5126 from mfem/support-shared-build-with-fetching
Support shared MFEM build when fetching TPLs
2026-06-26 11:32:12 -07:00
Andrew Ho 79bca13634 Don't insist on RAJA/CAMP always using default stream and create an internal resource with the default stream 2026-06-25 21:59:07 -07:00
John Camier 5048b1a219 Merge branch 'master' into lorentz-device 2026-06-25 07:47:47 +02:00
John Camier b84988d5f1 Merge branch 'master' into bugfix-project 2026-06-25 07:44:37 +02:00
John Camier 55aa12823c Merge branch 'master' into fix-specializations 2026-06-25 07:44:01 +02:00
Andrew Ho 80555fa132 added documentation 2026-06-24 20:45:13 -07:00
Tara Drwenski 9a98c2be01 Revert "Test: Comment out something from gitignore to test gitignore job"
This reverts commit 16f9cb63a1.
2026-06-24 14:12:34 -07:00
Tzanio Kolev 268231dd09 Merge branch 'master' into hcurl_domain_lf 2026-06-24 12:47:14 -07:00
Tara Drwenski 10880e5ad2 Add path filters to non-required workflows 2026-06-24 11:10:07 -07:00
Tara Drwenski 3df2f14eb9 Merge branch 'master' into speed-up-gitignore-job 2026-06-24 11:06:31 -07:00
Tzanio Kolev 21f1404580 Merge branch 'master' into support-shared-build-with-fetching 2026-06-24 11:03:58 -07:00
Tzanio Kolev 63a38bed84 Merge branch 'master' into hypremat 2026-06-24 11:03:55 -07:00
Tara Drwenski 16f9cb63a1 Test: Comment out something from gitignore to test gitignore job 2026-06-24 10:23:58 -07:00
Tara Drwenski 58da879f06 Remove mfem-analysis from badges in the contributing guide 2026-06-24 10:23:58 -07:00
Tara Drwenski 09b9e1c775 Style fix: use YES instead of true to match current style 2026-06-24 10:23:58 -07:00
Tara Drwenski 1ac86956d1 Move gitignore check from own workflow to builds-and-tests to avoid rebuilding 2026-06-24 08:32:37 -07:00
Andrew Ho 48622d3f1e need to include simplices headers in order for AddSpecialization to work 2026-06-23 16:12:38 -07:00
Jan Nikl 9cf0b8cb08 Fixed type narrowing in ProjectBdrCoefficientNormal unit test. 2026-06-23 10:22:52 -07:00
Andrew Ho 4843835f98 bugfix where coefficient projection assumes quadrature function is valid on host 2026-06-23 09:59:19 -07:00
Ketan Mittal 161630bf12 fix edge initialization for surface kernels 2026-06-22 16:11:56 -07:00
Nuno NobreandJan Nikl 03c99b8dfe Apply minor rephrasing suggestions
Co-authored-by: Jan Nikl <nikl1@llnl.gov>
2026-06-22 18:08:23 +01:00
Ketan Mittal b65e7ee791 Merge branch 'lorentz-device' of https://github.com/mfem/mfem into lorentz-device 2026-06-22 09:29:16 -07:00
Ketan Mittal 473084c8c5 nvcc fix for host/device lambdas 2026-06-22 09:28:55 -07:00
Ketan Mittal 2f0bbc6fc4 Merge branch 'master' into lorentz-device 2026-06-22 08:55:49 -07:00
Ketan Mittal c7cba857af fix tag access in Get/SetParticle 2026-06-19 11:00:15 -07:00
Ketan Mittal 85a16c2e43 Merge branch 'lorentz-device' of https://github.com/mfem/mfem into lorentz-device 2026-06-19 09:51:48 -07:00
Ketan Mittal 3c4d103982 do compact transfer between host-device during redistribute 2026-06-19 09:51:24 -07:00
John Camier 38ed1e049b Merge branch 'master' into lorentz-device 2026-06-17 05:58:47 -07:00
Andrew Ho ac7fa02927 Merge branch 'master' into support-shared-build-with-fetching 2026-06-15 11:59:04 -07:00
Ketan Mittal eeca0b4cd5 reviewer comments 2026-06-14 19:52:40 -07:00
Victor A. P. Magri ad18d18be1 Fix CMake parallel build issue with auto-fetched hypre 2026-06-13 00:10:33 -04:00
Ketan Mittal eb4fa33a7b Merge branch 'master' of https://github.com/mfem/mfem into lorentz-device 2026-06-10 15:50:13 -07:00
Andrew Ho 42d7d20e43 Merge branch 'master' into hcurl_domain_lf 2026-06-10 15:27:57 -07:00
Nuno Nobre 75f8ca8cd4 Switch to GetHypreMemoryLocation() 2026-06-10 18:33:14 +01:00
Nuno Nobre 36e915f226 Guard hypre_CSRMatrixMemoryLocation w/ hypre version check 2026-06-10 18:11:40 +01:00
Nuno Nobre f236d70a19 Avoid calling HypreParMatrix::Write() again and clearing ptrs 2026-06-10 13:58:58 +01:00
Nuno Nobre 85e33c6645 Use hypre_CSRMatrix{I,J,Data,OwnsData} and update explainer comment 2026-06-10 13:22:46 +01:00
Veselin Dobrev 7b62f035a5 Add a fix for the issue -- alternative to the solution in PR #5346. 2026-06-10 13:14:28 +01:00
Nuno Nobre 81fb02389f Revert "Preemptively delete rownnz if ownership flags set to -1"
This reverts commit 7a6313d725.
2026-06-10 11:35:16 +01:00
Nuno Nobre 23666fd4d8 Fix missing #ifdef MFEM_USE_MPI in new unit test 2026-06-10 11:25:58 +01:00
Nuno Nobre f3a60e2f08 Merge branch 'master' into hypremat 2026-06-10 11:25:00 +01:00
John Camier 2f21794999 Merge branch 'master' into hypremat 2026-06-09 06:45:29 -07:00
Andrew Ho 50b8f67bd1 Merge branch 'master' into hcurl_domain_lf 2026-06-08 12:41:22 -07:00
Veselin Dobrev db7da59b03 Add a test that reproduces the issue described in PR #5200 2026-06-07 19:30:38 +01:00
John Camier 28194a6736 Merge branch 'master' into lorentz-device 2026-06-06 16:37:35 -07:00
Nuno Nobre 7a6313d725 Preemptively delete rownnz if ownership flags set to -1 2026-05-26 17:24:58 +01:00
Mittal, Ketan 84bbead832 move lambdas to static functions for nvcc 2026-05-26 09:09:20 -07:00
Mittal, Ketan dbb751d7fb Merge branch 'master' of https://github.com/mfem/mfem into lorentz-device 2026-05-25 15:33:29 -07:00
blaz 3fbc6287ec fix for LONG 2026-05-21 22:13:01 -07:00
Boyan LazarovandCopilot Autofix powered by AI 8d77bb554b Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-21 19:42:48 -07:00
Boyan LazarovandCopilot Autofix powered by AI 01d60de126 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-21 19:41:43 -07:00
Tzanio Kolev d2d6524a71 Merge branch 'master' into checkpointing 2026-05-21 16:46:04 -07:00
Mittal, Ketan 58e6c4fc6d merge with master and resolve conflicts 2026-05-20 11:21:29 -07:00
Andrew Ho 670dfb2d98 fix compiler warning about missing enum cases 2026-05-15 12:23:24 -07:00
Andrew Ho 4167f0027c combine H(div) and H(curl) linear form extension tests 2026-05-15 12:14:13 -07:00
Andrew Ho b59ccd206d specify only for 3D H(curl) 2026-05-15 11:45:54 -07:00
Andrew Ho 76b033ad36 accidentally duplicated file 2026-05-15 01:19:54 -07:00
Andrew Ho 0cd0a98198 Add device assembly support for H(curl) VectorFEDomainLFIntegrator. 2026-05-15 01:08:22 -07:00
Ketan Mittal 411a361ebf Merge branch 'master' into lorentz-device 2026-05-09 20:22:29 -07:00
Ketan Mittal 0ee57ac12d Merge branch 'master' into lorentz-device 2026-05-05 22:53:21 -07:00
John Camier dee971c308 Merge branch 'master' into lorentz-device 2026-05-05 06:21:51 -07:00
Mittal, Ketan bb12710561 Merge branch 'lorentz-device' of https://github.com/mfem/mfem into lorentz-device 2026-05-04 14:04:25 -07:00
Mittal, Ketan f3276a0d5d address co-pilot comments 2026-05-04 14:04:11 -07:00
Tzanio Kolev 426b5dc6cd Merge branch 'master' into lorentz-device 2026-05-02 13:00:07 -07:00
Mittal, Ketan 0dd81462c0 use forall_switch instead of MFEM_FORALL 2026-04-29 15:29:46 -07:00
Mittal, Ketan 25514d6e8e merge with master and resolve conflicts 2026-04-29 15:02:32 -07:00
Mittal, Ketan 9f01e61a57 add unit test for redistribution of particle data when it is on device 2026-04-29 14:58:30 -07:00
Mittal, Ketan 90fdd7e762 Merge branch 'master' of https://github.com/mfem/mfem into lorentz-device 2026-04-27 14:00:49 -07:00
Mittal, Ketan cfa6594977 minor 2026-04-27 14:00:47 -07:00
AnnevanGils 02822e620a Merge branch 'master' into complexgridfunction_Lp_error 2026-03-24 10:49:26 +01:00
Anne 25a26b5e6f fix variable names shadowing members 2026-03-19 12:29:06 +01:00
Anne d6aacb95cf fix typo in documentation 2026-03-19 12:07:53 +01:00
Anne 0466624b21 apply astyle format 2026-03-19 12:03:54 +01:00
Anne 42e2d0b9a4 Add Lp error implementation to ComplexGridFunction using the modulus of complex values. Add interface ComputeMaxError for special case p = infinity. 2026-03-18 11:17:08 +01:00
Ketan Mittal 08bf7f991b Merge branch 'master' into lorentz-device 2026-03-17 19:01:33 -07:00
Ketan Mittal 0503cbd41c Merge branch 'master' into lorentz-device 2026-03-11 10:57:58 -07:00
Ketan Mittal 74d9671ec8 Merge branch 'master' into lorentz-device 2026-03-07 16:14:45 -08:00
Mittal, Ketan 74f64934e8 Merge branch 'master' of https://github.com/mfem/mfem into lorentz-device 2026-03-05 11:10:05 -08:00
Mittal, Ketan 8ff5affe45 Merge branch 'lorentz-device' of https://github.com/mfem/mfem into lorentz-device 2026-03-05 11:09:59 -08:00
Mittal, Ketan 2e7a6d745c cosmetic 2026-03-05 11:09:50 -08:00
Chris Vogl aa4f1bc8e4 Removed conditional and combined setting of initial GSLIB flags (from @nmnobre) 2026-02-27 12:03:06 -08:00
Chris VoglandNuno Nobre fcc4b2dade Switch to using OPTFLAGS for METIS fetching (from @nmnobre)
Co-authored-by: Nuno Nobre <nuno.nobre@stfc.ac.uk>
2026-02-26 15:31:16 -08:00
Chris VoglandNuno Nobre 3c137f36cb Improved consistency across TPL fetching (from @nmnobre)
Co-authored-by: Nuno Nobre <nuno.nobre@stfc.ac.uk>
2026-02-26 15:27:10 -08:00
Chris VoglandNuno Nobre 9211b97eb0 Fixed typo in GSLIB_FLAGS name (from @nmnobre)
Co-authored-by: Nuno Nobre <nuno.nobre@stfc.ac.uk>
2026-02-26 15:21:57 -08:00
Ketan Mittal 9cb3c1e1a6 Merge branch 'master' into lorentz-device 2026-02-26 13:02:01 -08:00
Mittal, Ketan 0f449eb906 Merge branch 'lorentz-device' of https://github.com/mfem/mfem into lorentz-device 2026-02-23 15:53:08 -08:00
Mittal, Ketan cdca79060b print when data is on device 2026-02-23 15:52:56 -08:00
Ketan Mittal 3505a5a354 Merge branch 'master' into lorentz-device 2026-02-20 09:31:38 -08:00
Mittal, Ketan d62ffe149a style 2026-02-17 08:49:04 -08:00
Mittal, Ketan fd36e1b177 Merge branch 'findpts-device-data-movement' of https://github.com/mfem/mfem into lorentz-device 2026-02-11 09:41:08 -08:00
Mittal, Ketan d390f9a7d1 remove timers 2026-02-11 09:41:02 -08:00
Mittal, Ketan 128f650dd3 documentation 2026-02-11 08:52:31 -08:00
Mittal, Ketan 79733572d7 initial commit 2026-02-10 09:23:02 -08:00
Mittal, Ketan 6b5d1e55de Merge branch 'findpts-device-data-movement' of https://github.com/mfem/mfem into lorentz-device 2026-02-10 09:19:07 -08:00
Mittal, Ketan 6b5eb2f92f minor 2026-02-10 09:17:06 -08:00
Mittal, Ketan 49c346233e minor clean up 2026-02-10 09:13:57 -08:00
Mittal, Ketan e81352715e Merge branch 'findpts-device-data-movement' of https://github.com/mfem/mfem 2026-02-09 13:32:17 -08:00
Ketan Mittal 159f1873d6 Merge branch 'master' into particle-device 2026-02-09 13:26:35 -08:00
Mittal, Ketan fa07a503dd minor 2026-02-09 13:26:08 -08:00
Mittal, Ketan d65fcc5d8c minor comment and split line 2026-02-09 09:56:14 -08:00
Mittal, Ketan 1696197f54 merge with master 2026-02-09 09:41:52 -08:00
Mittal, Ketan 19089ac132 merge on host instead of device 2026-02-09 09:37:31 -08:00
blaz b728d1d0ba options 2026-02-04 21:27:32 -08:00
blaz 818de95357 adaptive scalar example 2026-02-04 21:26:06 -08:00
blaz 7d2b76db78 the actual move 2026-02-03 23:34:32 -08:00
blaz 49461c5b49 test revolve 2026-02-03 23:30:50 -08:00
blaz 91f416de8a moved to linalg 2026-02-03 23:29:49 -08:00
Mittal, Ketan 2b3657c76b Merge branch 'master' of https://github.com/mfem/mfem into particle-device 2026-01-26 09:32:18 -08:00
Mittal, Ketan 63d45eb194 initial commit 2026-01-26 09:32:05 -08:00
blaz 5ec1b7ade8 style 2026-01-25 22:03:35 -08:00
blaz 8e2478f964 Checkpointing examples 2026-01-25 19:45:26 -08:00
blaz 06c485bb3e updates 2026-01-21 11:03:43 -08:00
bslazarov 1cd27605f0 memory transfer to Device for q-functions 2026-01-20 14:43:06 -08:00
blaz 547fd02911 anisotrpic elasticity structure 2026-01-20 09:57:29 -08:00
blaz aa92498033 explicit linear elasticity 2026-01-20 09:51:15 -08:00
blaz 024576620a initial REVOLVE 2026-01-16 11:29:25 -08:00
blaz 5b670bda3f file and segement storage 2026-01-15 22:59:32 -08:00
blaz 6b0e135bdb rename files 2026-01-15 22:18:33 -08:00
blaz 204b9df2d6 checkpinting into file - multiple files - one file per snapshot 2026-01-15 22:16:31 -08:00
blaz 8b49379f87 fixed slot in-memory or file storage for checkpointing 2026-01-15 22:15:09 -08:00
blaz d32a00fb2f CMake list 2026-01-15 19:57:10 -08:00
blaz 59c9ca70e4 initial version 2026-01-15 19:49:02 -08:00
Mittal, Ketan 9e5b93a532 initial commit with working prototype 2026-01-15 18:30:23 -08:00
Chris Vogl e5111df7c6 rename fetched GSLIB option variable to be consistent with fetched METIS 2025-11-24 16:20:53 -08:00
Chris Vogl 4fe80816d2 added fPIC flag option for fetched METIS, also includes optimization flags as done with fetched GSLIB 2025-11-24 16:20:16 -08:00
Chris Vogl 0839f915b4 added position independent code option for fetched hypre 2025-11-24 16:19:10 -08:00
Chris Vogl 23b4a5a5b8 style cleanup of GSLIB fetching to match other fetching code 2025-11-24 16:17:42 -08:00
79 changed files with 8917 additions and 2417 deletions
+2 -6
View File
@@ -29,16 +29,12 @@ Runs a number of static repository-level sanity checks.
- `branch-history` guards against accidental commits of large files using the `--history` option of the `config/githooks/pre-push` script.
## `mfem-analysis.yml` (`build-analysis`)
Checks if the code builds and satisfies minimal requirements.
- `gitignore` builds hypre, METIS, and MFEM using `mfem/github-actions/build-hypre`, `mfem/github-actions/build-metis`, and `mfem/github-actions/build-mfem` and checks for correct `.gitignore` settings by running the `tests/scripts/gitignore` script.
## `builds-and-tests.yml`
Runs a matrix of builds and tests runs with different compilers, OS, mfem/hypre settings, etc. Also processes and upload Codecov reports.
One matrix job runs `tests/scripts/gitignore` after `make test-noclean` to check generated artifacts against `.gitignore`.
Uses the following GitHub Actions from <https://github.com/mfem/github-actions>:
- `mfem/github-actions/build-hypre`
+70 -2
View File
@@ -111,6 +111,7 @@ jobs:
build-system: make
hypre-target: int64
precision: fp64
gitignore-check: YES
- os: ubuntu-latest
target: opt
codecov: NO
@@ -141,6 +142,10 @@ jobs:
continue-on-error: ${{ matrix.enzyme && true || false }}
# Enable ccache for all jobs except Windows (would need sccache).
env:
USE_CCACHE: ${{ matrix.os != 'windows-latest' }}
steps:
# Fix 'No space left on device' errors for Ubuntu builds.
- name: Run Actions Cleaner
@@ -289,6 +294,52 @@ jobs:
echo "OMPI_CC=$LLVM_PREFIX/bin/clang" >> $GITHUB_ENV
echo "OMPI_CXX=$LLVM_PREFIX/bin/clang++" >> $GITHUB_ENV
# Restore the compiler cache (ccache). The key embeds the run id, so new
# runs save a fresh snapshot; the restore-keys prefix warm-starts from the
# most recent prior run (incl. the base branch for PRs).
- name: cache ccache
if: ${{ env.USE_CCACHE == 'true' }}
uses: actions/cache@v5
with:
path: .ccache
key: ccache-${{ matrix.os }}-${{ matrix.build-system }}-${{ matrix.target }}-${{ matrix.mpi }}-${{ matrix.hypre-target }}-${{ matrix.precision }}${{ matrix.enzyme && '-enzyme' || '' }}-${{ github.run_id }}
restore-keys: |
ccache-${{ matrix.os }}-${{ matrix.build-system }}-${{ matrix.target }}-${{ matrix.mpi }}-${{ matrix.hypre-target }}-${{ matrix.precision }}${{ matrix.enzyme && '-enzyme' || '' }}-
# Configure ccache and select how it is injected into the MFEM build:
# - make: set CXX="ccache g++"; for MPI, OMPI_CXX="ccache g++" so mpicxx
# runs ccache around g++ (not ccache around the mpicxx wrapper).
# - cmake: set CMAKE_<LANG>_COMPILER_LAUNCHER=ccache.
# - enzyme: wrap the brew clang++ via OMPI_CXX.
# The chosen options are passed through build-mfem's 'config-options'
# input (see the build step below).
- name: configure ccache
if: ${{ env.USE_CCACHE == 'true' }}
run: |
command -v ccache >/dev/null 2>&1 || {
if [[ "${{ runner.os }}" == "Linux" ]]; then
sudo apt-get update && sudo apt-get install -y ccache
else
brew install ccache
fi
}
echo "CCACHE_DIR=${{ github.workspace }}/.ccache" >> $GITHUB_ENV
echo "CCACHE_MAXSIZE=1G" >> $GITHUB_ENV
echo "CCACHE_COMPILERCHECK=content" >> $GITHUB_ENV
# Ignore header timestamps (restamped by each checkout) so direct mode hits.
echo "CCACHE_SLOPPINESS=include_file_mtime,include_file_ctime,time_macros" >> $GITHUB_ENV
# Hash absolute paths relative to the workspace.
echo "CCACHE_BASEDIR=${{ github.workspace }}" >> $GITHUB_ENV
if [[ "${{ matrix.enzyme }}" == "true" ]]; then
echo "OMPI_CXX=ccache $LLVM_PREFIX/bin/clang++" >> $GITHUB_ENV
elif [[ "${{ matrix.build-system }}" == "cmake" ]]; then
echo 'CCACHE_CONFIG_OPTS=-DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache' >> $GITHUB_ENV
else
echo "OMPI_CXX=ccache g++" >> $GITHUB_ENV
echo 'CCACHE_CONFIG_OPTS=CXX="ccache g++" MPICXX="mpicxx"' >> $GITHUB_ENV
fi
shell: bash
# MFEM build and test
- name: build
uses: mfem/github-actions/build-mfem@v2.7
@@ -304,9 +355,14 @@ jobs:
metis-dir: ${{ env.METIS_TOP_DIR }}
mfem-dir: ${{ env.MFEM_TOP_DIR }}
precision: ${{ matrix.precision }}
config-options: ${{ matrix.config-opts }}
config-options: ${{ matrix.config-opts }} ${{ env.CCACHE_CONFIG_OPTS }}
library-only: ${{ matrix.target == 'dbg' && matrix.os != 'ubuntu-latest' }}
- name: ccache stats
if: ${{ env.USE_CCACHE == 'true' }}
run: ccache -s
shell: bash
# Run checks (and only checks) on debug targets
- name: checks
if: matrix.build-system == 'make' && matrix.target == 'dbg'
@@ -317,7 +373,13 @@ jobs:
- name: tests
if: matrix.build-system == 'make' && (matrix.target == 'opt' || matrix.os == 'ubuntu-latest')
run: |
cd ${{ env.MFEM_TOP_DIR }} && make test
cd ${{ env.MFEM_TOP_DIR }}
if [[ "${{ matrix.gitignore-check }}" == "YES" ]]; then
make test-noclean
else
make test
fi
shell: bash
- name: cmake checks
if: matrix.build-system == 'cmake' && matrix.target == 'dbg'
@@ -369,3 +431,9 @@ jobs:
directories: "fem general linalg mesh"
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: gitignore
if: matrix.gitignore-check == 'YES'
run: |
cd ${{ env.MFEM_TOP_DIR }}/tests/scripts
./runtest gitignore
+42
View File
@@ -0,0 +1,42 @@
# 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.
---
# A closed PR's caches can never be restored again, so delete them to free
# space against the 10 GB per-repo cache limit.
name: Cleanup PR caches
on:
pull_request:
types: [closed]
permissions:
actions: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Delete caches for the closed PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PR_REF: refs/pull/${{ github.event.pull_request.number }}/merge
run: |
echo "Deleting caches for $PR_REF"
while :; do
ids=$(gh cache list --ref "$PR_REF" --limit 100 --json id --jq '.[].id')
[ -n "$ids" ] || break
echo "$ids" | while read -r id; do
[ -n "$id" ] || continue
echo "Deleting cache $id"
gh cache delete "$id" || echo " (already gone)"
done
done
+10
View File
@@ -14,9 +14,19 @@ name: "Static Analysis"
on:
push:
branches: ["master", "next"]
paths-ignore: &docs-only-paths
- "**/*.md"
- "doc/**"
- ".binder/**"
- "CITATION.cff"
- "LICENSE"
- "NOTICE"
- "CHANGELOG"
- "INSTALL"
pull_request:
# The branches below must be a subset of the branches above
branches: ["master"]
paths-ignore: *docs-only-paths
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
-102
View File
@@ -1,102 +0,0 @@
# Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
# at the Lawrence Livermore National Laboratory. All Rights reserved. See files
# LICENSE and NOTICE for details. LLNL-CODE-806117.
#
# This file is part of the MFEM library. For more information and source code
# availability visit https://mfem.org.
#
# MFEM is free software; you can redistribute it and/or modify it under the
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
name: "Build Analysis"
permissions:
actions: write
on:
push:
branches:
- master
- next
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
HYPRE_ARCHIVE: v2.19.0.tar.gz
HYPRE_TOP_DIR: hypre-2.19.0
METIS_ARCHIVE: metis-4.0.3.tar.gz
METIS_TOP_DIR: metis-4.0.3
COVERAGE_ENV: mfem-coverage
MFEM_ACTIONS_VERSION: v2.7
jobs:
gitignore:
runs-on: ubuntu-latest
steps:
- name: checkout MFEM
uses: actions/checkout@v6
with:
path: mfem
- name: Get MPI (Linux)
run: |
sudo apt-get install openmpi-bin libopenmpi-dev
export OMPI_MCA_rmaps_base_oversubscribe=1
- name: Cache Hypre Install
id: hypre-cache
uses: actions/cache@v5
with:
path: ${{ env.HYPRE_TOP_DIR }}
key: ${{ runner.os }}-ompi-build-${{ env.HYPRE_TOP_DIR }}-int32-fp64-${{ env.MFEM_ACTIONS_VERSION }}
- name: Get Hypre
if: steps.hypre-cache.outputs.cache-hit != 'true'
uses: mfem/github-actions/build-hypre@v2.7
with:
archive: ${{ env.HYPRE_ARCHIVE }}
dir: ${{ env.HYPRE_TOP_DIR }}
target: int32
precision: fp64
- name: Cache Metis Install
id: metis-cache
uses: actions/cache@v5
with:
path: ${{ env.METIS_TOP_DIR }}
key: ${{ runner.os }}-build-${{ env.METIS_TOP_DIR }}-${{ env.MFEM_ACTIONS_VERSION }}
- name: Install Metis
if: steps.metis-cache.outputs.cache-hit != 'true'
uses: mfem/github-actions/build-metis@v2.7
with:
archive: ${{ env.METIS_ARCHIVE }}
dir: ${{ env.METIS_TOP_DIR }}
# MFEM build and test
- name: build-mfem
uses: mfem/github-actions/build-mfem@v2.7
with:
os: ${{ runner.os }}
target: opt
codecov: NO
mpi: par
build-system: make
hypre-dir: ${{ env.HYPRE_TOP_DIR }}
metis-dir: ${{ env.METIS_TOP_DIR }}
mfem-dir: mfem
- name: test (no clean)
run: |
cd mfem && make test-noclean
- name: gitignore
run: |
cd mfem/tests/scripts
./runtest gitignore
+33 -4
View File
@@ -13,6 +13,7 @@ name: "Checks"
permissions:
actions: write
pull-requests: read
on:
push:
@@ -29,6 +30,11 @@ concurrency:
# by checking if the workflow trigger is 'push' ("github.event_name == 'push'")
# and if we are in a fork ("github.event.pull_request.head.repo.full_name !=
# github.repository").
#
# The logic for the branch-history check is slightly different, since that check
# also inspects the PR's labels to allow for overriding failures. In this case,
# we run on all 'pull_request' triggers, but only run for 'push' triggers that
# do not correspond to any open PRs.
jobs:
file-headers-check:
@@ -128,10 +134,7 @@ jobs:
branch-history:
if: |
github.ref != 'refs/heads/next' &&
github.ref != 'refs/heads/master' &&
(github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name != github.repository)
github.ref != 'refs/heads/next' && github.ref != 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- name: checkout mfem
@@ -139,7 +142,27 @@ jobs:
with:
fetch-depth: 0
- name: check for pull request
id: check_pr
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ github.token }}
run: |
pr_exists=$(gh pr list --repo "$GITHUB_REPOSITORY" \
--head "$GITHUB_REF_NAME" \
--state open \
--json number \
--jq 'length > 0')
echo "pr_exists=$pr_exists" >> "$GITHUB_OUTPUT"
- name: branch-history
id: branch_history
if: |
(github.event_name == 'pull_request' ||
github.event_name == 'workflow_dispatch' ||
steps.check_pr.outputs.pr_exists == 'false')
continue-on-error: ${{ contains(github.event.pull_request.labels.*.name,
'branch-history-override') }}
run: |
# We override origin to make sure we point to the main repo.
# This is to have consistent test results on PRs from forks.
@@ -147,3 +170,9 @@ jobs:
git remote add origin https://github.com/mfem/mfem.git
git checkout -b gh-actions-branch-history
./config/githooks/pre-push --history
- name: report branch-history override
if: steps.branch_history.outcome == 'failure'
run: |
echo "::warning::branch-history check failed, but the" \
"'branch-history-override' label is set."
+10
View File
@@ -17,7 +17,17 @@ permissions:
on:
push:
branches: ["master", "next"]
paths-ignore: &docs-only-paths
- "**/*.md"
- "doc/**"
- ".binder/**"
- "CITATION.cff"
- "LICENSE"
- "NOTICE"
- "CHANGELOG"
- "INSTALL"
pull_request:
paths-ignore: *docs-only-paths
workflow_dispatch:
concurrency:
+6
View File
@@ -15,6 +15,10 @@ Version 4.9.1 (development)
Discretization improvements
---------------------------
- Improved FindPointsGSLIB surface mesh capability with support for simplices
and an option to specify axis-aligned bounding box padding for near-surface
point queries.
- Added GPU-enabled partial assembly for simplicial Bernstein H1 basis based on
ragged tensor algorithms (see DOI: 10.1137/11082539X) for mass and diffusion
integrators.
@@ -66,6 +70,8 @@ Linear and nonlinear solvers
GPU computing
-------------
- Added device assembly support for 3D H(curl) VectorFEDomainLFIntegrator.
- Added NVIDIA cuDSS library interface. Implementation examples have been
added to ex1 and ex1p. See https://developer.nvidia.com/cudss for more
details. Supported versions >= 0.6.0.
-1
View File
@@ -6,7 +6,6 @@
<a href="https://github.com/mfem/mfem/blob/master/LICENSE"><img alt="License" src="https://img.shields.io/badge/License-BSD-blue.svg"></a>
<a href="https://github.com/mfem/mfem/releases/latest"><img alt="GitHub release" src="https://img.shields.io/github/v/release/mfem/mfem"></a>
<a href="https://github.com/mfem/mfem/actions/workflows/repo-check.yml?query=branch%3Amaster"><img alt="Repo check" src="https://github.com/mfem/mfem/actions/workflows/repo-check.yml/badge.svg?branch=master"></a>
<a href="https://github.com/mfem/mfem/actions/workflows/mfem-analysis.yml?query=branch%3Amaster"><img alt="Build Analysis" src="https://github.com/mfem/mfem/actions/workflows/mfem-analysis.yml/badge.svg?branch=master"></a>
<a href="https://github.com/mfem/mfem/actions/workflows/builds-and-tests.yml?query=branch%3Amaster"><img alt="Builds and Tests" src="https://github.com/mfem/mfem/actions/workflows/builds-and-tests.yml/badge.svg?branch=master"></a>
<a href="https://ci.appveyor.com/project/mfem/mfem"><img alt="Build Status" src="https://ci.appveyor.com/api/projects/status/19non9sqm6msi2wy?svg=true"></a>
<a href="https://docs.mfem.org/html/index.html"><img alt="Documentation" src="https://img.shields.io/badge/code-documented-brightgreen.svg"></a>
+8 -10
View File
@@ -18,19 +18,17 @@
if (MFEM_FETCH_GSLIB OR MFEM_FETCH_TPLS)
enable_language(C)
string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_TYPE)
set(GSLIB_FETCH_VERSION 1.0.9)
set(GSLIB_C_FLAGS ${CMAKE_C_FLAGS_${BUILD_TYPE}})
if (CMAKE_C_FLAGS)
set(GSLIB_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE}}")
endif()
if (BUILD_SHARED_LIBS)
set(GSLIB_C_FLAGS "${GSLIB_C_FLAGS} -fPIC")
endif()
add_library(GSLIB STATIC IMPORTED)
# set options (technically flags because GSLIB does not use cmake)
string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_TYPE)
set(GSLIB_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE}}")
if (BUILD_SHARED_LIBS)
set(GSLIB_FLAGS "${GSLIB_FLAGS} -fPIC")
endif()
# define external project and create future include directory so it is present
# to pass CMake checks at end of MFEM configuration step
message(STATUS "Will fetch GSLIB ${GSLIB_FETCH_VERSION} to be built with ${GSLIB_C_FLAGS}")
message(STATUS "Will fetch GSLIB ${GSLIB_FETCH_VERSION} to be built with ${GSLIB_FLAGS}")
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/gslib)
include(ExternalProject)
ExternalProject_Add(gslib
@@ -40,7 +38,7 @@ if (MFEM_FETCH_GSLIB OR MFEM_FETCH_TPLS)
UPDATE_DISCONNECTED TRUE
PREFIX ${PREFIX}
CONFIGURE_COMMAND ""
BUILD_COMMAND cd ${PREFIX}/src/gslib && $(MAKE) clean && $(MAKE) DESTDIR=${PREFIX} MPI=$<BOOL:${MFEM_USE_MPI}> "CFLAGS= ${GSLIB_C_FLAGS}"
BUILD_COMMAND cd ${PREFIX}/src/gslib && $(MAKE) clean && $(MAKE) DESTDIR=${PREFIX} MPI=$<BOOL:${MFEM_USE_MPI}> "CFLAGS=${GSLIB_FLAGS}"
INSTALL_COMMAND "")
file(MAKE_DIRECTORY ${PREFIX}/include)
# set imported library target properties
+3 -1
View File
@@ -44,6 +44,9 @@ if (MFEM_FETCH_HYPRE OR MFEM_FETCH_TPLS)
# set options and associated dependencies
set(HYPRE_CMAKE_OPTIONS "")
list(APPEND HYPRE_CMAKE_OPTIONS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
if (BUILD_SHARED_LIBS)
list(APPEND HYPRE_CMAKE_OPTIONS -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON)
endif()
# collect all HYPRE_ENABLE variables and pass them to hypre, assuming they are BOOL.
get_cmake_property(all_vars VARIABLES)
foreach(var ${all_vars})
@@ -95,7 +98,6 @@ if (MFEM_FETCH_HYPRE OR MFEM_FETCH_TPLS)
UPDATE_DISCONNECTED TRUE
SOURCE_SUBDIR src
PREFIX ${HYPRE_INSTALL}
BUILD_COMMAND ${CMAKE_COMMAND} --build . -- -j${CMAKE_BUILD_PARALLEL_LEVEL}
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${HYPRE_INSTALL} -DCMAKE_INSTALL_LIBDIR:PATH=lib ${HYPRE_CMAKE_OPTIONS})
file(MAKE_DIRECTORY ${HYPRE_INSTALL}/include)
# set imported library target properties
+10 -2
View File
@@ -19,10 +19,18 @@
# - METIS_VERSION_5 (cache variable)
if (MFEM_FETCH_METIS OR MFEM_FETCH_TPLS)
enable_language(C)
set(METIS_FETCH_VERSION 4.0.3)
add_library(METIS STATIC IMPORTED)
# set options (technically flags because METIS does not use cmake)
set(METIS_FLAGS "-Wno-implicit-int -Wno-incompatible-pointer-types")
string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_TYPE)
set(METIS_FLAGS "${METIS_FLAGS} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE}}")
if (BUILD_SHARED_LIBS)
set(METIS_FLAGS "${METIS_FLAGS} -fPIC")
endif()
# define external project
message(STATUS "Will fetch METIS ${METIS_FETCH_VERSION} to be built with default options")
message(STATUS "Will fetch METIS ${METIS_FETCH_VERSION} to be built with ${METIS_FLAGS}")
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/metis)
include(ExternalProject)
ExternalProject_Add(metis
@@ -32,7 +40,7 @@ if (MFEM_FETCH_METIS OR MFEM_FETCH_TPLS)
UPDATE_DISCONNECTED TRUE
PREFIX ${PREFIX}
CONFIGURE_COMMAND tar -xzf ../metis/metis-${METIS_FETCH_VERSION}-mac.tgz --strip=1
BUILD_COMMAND $(MAKE) COPTIONS=-Wno-incompatible-pointer-types
BUILD_COMMAND $(MAKE) clean && $(MAKE) "OPTFLAGS=${METIS_FLAGS}"
INSTALL_COMMAND mkdir -p ${PREFIX}/lib && cp libmetis.a ${PREFIX}/lib/)
# set imported library target properties
add_dependencies(METIS metis)
+9 -9
View File
@@ -22,15 +22,15 @@ include(MfemCmakeUtilities)
mfem_find_package(SuiteSparse SuiteSparse SuiteSparse_DIR "" "" "" ""
"Paths to headers required by SuiteSparse."
"Libraries required by SuiteSparse."
ADD_COMPONENT "UMFPACK" "include;suitesparse" umfpack.h "lib" umfpack
ADD_COMPONENT "KLU" "include;suitesparse" klu.h "lib" klu
ADD_COMPONENT "AMD" "include;suitesparse" amd.h "lib" amd
ADD_COMPONENT "BTF" "include;suitesparse" btf.h "lib" btf
ADD_COMPONENT "CHOLMOD" "include;suitesparse" cholmod.h "lib" cholmod
ADD_COMPONENT "COLAMD" "include;suitesparse" colamd.h "lib" colamd
ADD_COMPONENT "CAMD" "include;suitesparse" camd.h "lib" camd
ADD_COMPONENT "CCOLAMD" "include;suitesparse" ccolamd.h "lib" ccolamd
ADD_COMPONENT "config" "include;suitesparse" SuiteSparse_config.h "lib"
ADD_COMPONENT "UMFPACK" "include;include/suitesparse;suitesparse" umfpack.h "lib" umfpack
ADD_COMPONENT "KLU" "include;include/suitesparse;suitesparse" klu.h "lib" klu
ADD_COMPONENT "AMD" "include;include/suitesparse;suitesparse" amd.h "lib" amd
ADD_COMPONENT "BTF" "include;include/suitesparse;suitesparse" btf.h "lib" btf
ADD_COMPONENT "CHOLMOD" "include;include/suitesparse;suitesparse" cholmod.h "lib" cholmod
ADD_COMPONENT "COLAMD" "include;include/suitesparse;suitesparse" colamd.h "lib" colamd
ADD_COMPONENT "CAMD" "include;include/suitesparse;suitesparse" camd.h "lib" camd
ADD_COMPONENT "CCOLAMD" "include;include/suitesparse;suitesparse" ccolamd.h "lib" ccolamd
ADD_COMPONENT "config" "include;include/suitesparse;suitesparse" SuiteSparse_config.h "lib"
suitesparseconfig)
if (SuiteSparse_FOUND AND METIS_VERSION_5)
+5
View File
@@ -39,3 +39,8 @@ when a picture was added for documentation.
If that is the case, make sure the failure is indeed justified, and rerun the
push command with the `--no-verify` option. This will skip the hooks, allowing
you to push those changes.
The `branch-history` check is run automatically through GitHub Actions. If a
branch is known to have a large number of changes that are legitimate, the
check can be overridden by setting the label 'branch-history-override' on the
pull request.
+2 -1
View File
@@ -133,7 +133,7 @@ set(SRCS
tmop/assemble/diag2.cpp
tmop/assemble/grad2_limit.cpp
tmop/assemble/grad2.cpp
tmop/assemble/diag3_limit.cpp
tmop/assemble/diag3_limit.cpp
tmop/assemble/diag3.cpp
tmop/assemble/grad3_limit.cpp
tmop/assemble/grad3.cpp
@@ -311,6 +311,7 @@ set(HDRS
tmop_tools.hpp
tmop_amr.hpp
gslib.hpp
gslib/gslib_kernel_helpers.hpp
transfer.hpp
hyperbolic.hpp
integrator.hpp
+6
View File
@@ -54,6 +54,8 @@ void Coefficient::Project(QuadratureFunction &qf)
QuadratureSpaceBase &qspace = *qf.GetSpace();
const int ne = qspace.GetNE();
Vector values;
// GetValues makes a reference, but we need it to be valid on Host
qf.HostWrite();
for (int iel = 0; iel < ne; ++iel)
{
qf.GetValues(iel, values);
@@ -327,6 +329,8 @@ void VectorCoefficient::Project(QuadratureFunction &qf)
const int ne = qspace.GetNE();
DenseMatrix values;
Vector col;
// GetValues makes a reference, but we need it to be valid on Host
qf.HostWrite();
for (int iel = 0; iel < ne; ++iel)
{
qf.GetValues(iel, values);
@@ -695,6 +699,8 @@ void MatrixCoefficient::Project(QuadratureFunction &qf, bool transpose)
QuadratureSpaceBase &qspace = *qf.GetSpace();
const int ne = qspace.GetNE();
DenseMatrix values, matrix;
// GetValues makes a reference, but we need it to be valid on Host
qf.HostWrite();
for (int iel = 0; iel < ne; ++iel)
{
qf.GetValues(iel, values);
+75
View File
@@ -237,6 +237,81 @@ ComplexGridFunction::ProjectBdrCoefficientTangent(VectorCoefficient
gfi->SyncAliasMemory(*this);
}
real_t
ComplexGridFunction::ComputeLpError(const real_t p,
Coefficient &exsolr,
Coefficient &exsoli,
Coefficient *weight,
const IntegrationRule *irs[],
const Array<int> *elems) const
{
real_t error = 0.0;
const FiniteElement *fe;
ElementTransformation *T;
Vector valsr;
Vector valsi;
const GridFunction& gf_r = real();
const GridFunction& gf_i = imag();
for (int i = 0; i < fes->GetNE(); i++)
{
if (elems != NULL && (*elems)[i] == 0) { continue; }
fe = fes->GetFE(i);
const IntegrationRule *ir;
if (irs)
{
ir = irs[fe->GetGeomType()];
}
else
{
int intorder = 2*fe->GetOrder() + 3;
ir = &(IntRules.Get(fe->GetGeomType(), intorder));
}
real_t elem_error = 0.0;
gf_r.GetValues(i, *ir, valsr);
gf_i.GetValues(i, *ir, valsi);
T = fes->GetElementTransformation(i);
for (int j = 0; j < ir->GetNPoints(); j++)
{
const IntegrationPoint &ip = ir->IntPoint(j);
T->SetIntPoint(&ip);
real_t diffr = valsr(j) - exsolr.Eval(*T, ip);
real_t diffi = valsi(j) - exsoli.Eval(*T, ip);
real_t diff = hypot(diffr, diffi);
if (p < infinity())
{
diff = pow(diff, p);
if (weight)
{
diff *= weight->Eval(*T, ip);
}
elem_error += ip.weight * T->Weight() * diff;
}
else
{
if (weight)
{
diff *= weight->Eval(*T, ip);
}
error = std::max(error, diff);
}
}
if (p < infinity())
{
// negative quadrature weights may cause the error to be negative
error += fabs(elem_error);
}
}
if (p < infinity())
{
error = pow(error, 1./p);
}
return error;
}
void ComplexGridFunction::Save(std::ostream &os) const
{
os << "ComplexGridFunction\n";
+69
View File
@@ -166,6 +166,75 @@ public:
return sqrt(err_r * err_r + err_i * err_i);
}
/// @brief Returns Max|u_ex - u_h| error for complex-valued H1 or L2 elements
///
/// Compute the $L_\infty$ error across the entire domain.
///
/// @param[in] exsolr Coefficient object reproducing the real part of the
/// anticipated values of the scalar field, Re(u_ex).
/// @param[in] exsoli Coefficient object reproducing the imaginary part of
/// the anticipated values of the scalar field, Im(u_ex).
/// @param[in] irs Optional pointer to an array of custom integration
/// rules e.g. higher order than the default rules. If
/// present the array will be indexed by
/// Geometry::Type.
///
/// @note Uses ComputeLpError internally. See the ComputeLpError
/// documentation for generalizations of this error computation.
///
/// @note If an array of integration rules is provided through @a irs, be
/// sure to include valid rules for each element type that may occur
/// in the list of elements.
///
virtual real_t ComputeMaxError(Coefficient &exsolr,
Coefficient &exsoli,
const IntegrationRule *irs[] = NULL) const
{
return ComputeLpError(infinity(), exsolr, exsoli, NULL, irs);
}
/// @brief Returns ||u_ex - u_h||_Lp for complex-valued H1 or L2 elements
///
/// Computes:
/// $$(\sum_{elems} \int_{elem} w \, |u_{ex} - u_h|^p)^{1/p}$$
/// Where:
/// $$|u_{ex} - u_h| = \sqrt{Re(u_{ex} - u_h)^2 + Im(u_{ex} - u_h)^2}$$
///
/// @param[in] p Real value indicating the exponent of the $L^p$ norm.
/// To avoid domain errors p should have a positive value,
/// either finite or infinite.
/// @param[in] exsolr Coefficient object reproducing the real part of the
/// anticipated values of the scalar field, Re(u_ex).
/// @param[in] exsoli Coefficient object reproducing the imaginary part of
/// the anticipated values of the scalar field, Im(u_ex).
/// @param[in] weight Optional pointer to a Coefficient object reproducing
/// a weighting function, w.
/// @param[in] irs Optional pointer to an array of custom integration
/// rules e.g. higher order than the default rules. If
/// present the array will be indexed by Geometry::Type.
/// @param[in] elems Optional pointer to a marker array, with a length
/// equal to the number of local elements, indicating
/// which elements to integrate over. Only those elements
/// corresponding to non-zero entries in @a elems will
/// contribute to the computed L2 error.
///
/// @note If an array of integration rules is provided through @a irs, be
/// sure to include valid rules for each element type that may occur
/// in the list of elements.
///
/// @note Quadratures with negative weights (as in some simplex integration
/// rules in MFEM) can produce negative integrals even with
/// non-negative integrands. To avoid returning negative errors this
/// function uses the absolute values of the element-wise integrals.
/// This may lead to results which are not entirely consistent with
/// such integration rules.
virtual real_t ComputeLpError(const real_t p,
Coefficient &exsolr,
Coefficient &exsoli,
Coefficient *weight = NULL,
const IntegrationRule *irs[] = NULL,
const Array<int> *elems = NULL) const;
/// Save the ComplexGridFunction to an output stream.
virtual void Save(std::ostream &out) const;
+3 -1
View File
@@ -1181,12 +1181,14 @@ void ParaViewDataCollection::SaveGFieldVTU(std::ostream &os, int ref_,
DenseMatrix vval, pmat;
std::vector<char> buf;
int vec_dim = it->second->VectorDim();
int map_type = it->second->FESpace()->GetTypicalFE()->GetMapType();
os << "<DataArray type=\"" << GetDataTypeString()
<< "\" Name=\"" << it->first
<< "\" NumberOfComponents=\"" << vec_dim << "\" "
<< VTKComponentLabels(vec_dim) << " "
<< "format=\"" << GetDataFormatString() << "\" >" << '\n';
if (vec_dim == 1)
if (vec_dim == 1 && (map_type == FiniteElement::VALUE ||
map_type == FiniteElement::INTEGRAL))
{
for (int i = 0; i < mesh->GetNE(); i++)
{
+1234 -726
View File
File diff suppressed because it is too large Load Diff
+166 -51
View File
@@ -12,6 +12,9 @@
#ifndef MFEM_GSLIB
#define MFEM_GSLIB
#include <map>
#include <vector>
#include "../config/config.hpp"
#ifdef MFEM_USE_MPI
#include "pgridfunc.hpp"
@@ -119,6 +122,11 @@ protected:
// IntegrationRules for simplex->Quad/Hex and to project to p_max in-case of
// p-refinement.
Array<IntegrationRule *> ir_split;
/// Integration rules built at the field polynomial order (only for surface
/// meshes when mesh order is not the same as gridfunction order).
Array<IntegrationRule *> ir_split_sol;
/// Order at which #ir_split_sol was built; -1 means not built.
int ir_split_sol_order = -1;
Array<FiniteElementSpace *> fes_rst_map; //FESpaces to map Quad/Hex->Simplex
Array<GridFunction *> gf_rst_map; // GridFunctions to map Quad/Hex->Simplex
FiniteElementCollection *fec_map_lin;
@@ -134,6 +142,8 @@ protected:
AvgType avgtype; // average type used for L2 functions
Array<int> split_element_map;
Array<int> split_element_index;
// Geometry::Type (as int) of the original element for each split quad.
Array<int> split_element_geom;
int NE_split_total; // total number of elements after mesh splitting
int mesh_points_cnt; // number of mesh nodes
// Tolerance to ignore points found beyond the mesh boundary.
@@ -141,6 +151,12 @@ protected:
double bdr_tol;
// Use CPU functions for Mesh/GridFunction on device for gslib1.0.7
bool gpu_to_cpu_fallback = false;
// Check if a point is inside the oriented bounding box of an
// element before the Newton iteration.
// Note: only used in MFEM implementation (not in gslib) which currently
// supports GPU kernels for area meshes in 2D, volume meshes in 3D,
// and surface meshes in 1D/2D/3D.
bool obb_check = true;
// Device specific data used for FindPoints
struct DEV_STRUCT
@@ -162,11 +178,16 @@ protected:
mutable double surf_dist_tol;
} DEV;
/// Use GSLIB for communication and interpolation
// Helper function to setup and free gslib's crystal router.
void SetupCrystal(); // Called inside Setup and SetupSurf_base
void FreeCrystal(); // Called inside FreeData
/// Use GSLIB for communication and interpolation. Updates field_out on
/// host.
virtual void InterpolateH1(const GridFunction &field_in, Vector &field_out,
const int field_out_ordering);
/// Uses GSLIB Crystal Router for communication followed by MFEM's
/// interpolation functions
/// interpolation functions. Updates field_out on host.
virtual void InterpolateGeneral(const GridFunction &field_in,
Vector &field_out,
const int field_out_ordering);
@@ -181,12 +202,26 @@ protected:
IntegrationRule *irule,
int order);
/** @brief Build integration rules at the given @a order for each split mesh
* and store them in @a ir_out. Requires that \ref SetupSplitMeshes has
* already been called. */
virtual void SetupIntegrationRules(const int order,
Array<IntegrationRule *> &ir_out);
/** @brief Helper function that calls \ref SetupSplitMeshes and
* \ref SetupIntegrationRuleForSplitMesh. */
* \ref SetupIntegrationRules. */
virtual void SetupSplitMeshesAndIntegrationRules(const int order);
/// Get GridFunction value at the points expected by GSLIB.
virtual void GetNodalValues(const GridFunction *gf_in, Vector &node_vals) const;
/** @brief Get GridFunction value at the points expected by GSLIB.
* @param[in] gf_in Grid function to evaluate.
* @param[out] node_vals Output values.
* @param[in] ir_in If non-null, use these rules instead of #ir_split.
* @param[in] by_element If true, output has element-major layout
* [nel][vdim][ndofs]; otherwise component-major
* layout [vdim][total_pts]. */
virtual void GetNodalValues(const GridFunction *gf_in, Vector &node_vals,
const Array<IntegrationRule *> *ir_in = nullptr,
bool by_element = false) const;
/** @brief Map {r,s,t} coordinates from [-1,1] to [0,1] for MFEM. For
* simplices, find the original element number (that was split into
@@ -291,29 +326,60 @@ protected:
void findptsedge_setup_2(DEV_STRUCT &devs,
const double *const elx[2],
const unsigned n,
const uint nel,
const unsigned int nel,
const unsigned m,
const double bbox_tol,
const uint local_hash_size,
const uint global_hash_size);
const double bbox_rel_size_inc,
const unsigned int local_hash_size,
const unsigned int global_hash_size,
const Vector *aabb_sz_inc);
/// Preprocess 3D surface mesh needed for FindPoints.
void findptssurf_setup_3(DEV_STRUCT &devs,
const double *const elx[3],
const unsigned n,
const uint nel,
const unsigned int nel,
const unsigned m,
const double bbox_tol,
const uint local_hash_size,
const uint global_hash_size,
const int rD);
const double bbox_rel_size_inc,
const unsigned int local_hash_size,
const unsigned int global_hash_size,
const int rD,
const Vector *aabb_sz_inc);
/** @brief Shared implementation for the public surface-setup methods.
*
* @details Initializes the surface-search data structures, builds the
* split-element representation expected by gslib, and constructs the
* element bounding boxes used by the MFEM surface kernels.
*
* If @a aabb_sz_inc is null, the setup stores the default oriented
* bounding boxes and uses @a bbox_rel_size_inc as their relative size
* increase factor.
*
* If @a aabb_sz_inc is non-null, the setup stores axis-aligned bounding
* boxes only, applies the requested absolute AABB expansion in each
* physical direction, and adjusts the tolerance @a bdr_tol so points
* found in the expanded region are classified as border points.
*
* @param[in] m Input surface mesh.
* @param[in] bbox_rel_size_inc Relative size increase applied when
* expanding each element bounding box during
* setup.
* @param[in] aabb_sz_inc Optional total absolute AABB expansion
* applied to the stored axis-aligned
* bounding boxes after construction.
* @param[in] newt_tol Newton tolerance for the point-search
* kernels.
*/
void SetupSurf_Base(Mesh &m,
const double bbox_rel_size_inc,
const Vector *aabb_sz_inc,
const double newt_tol);
public:
/// Serial constructor
FindPointsGSLIB();
/// Serial constructor + setup with given Mesh (see \ref Setup)
FindPointsGSLIB(Mesh &mesh_in, const double bb_t = 0.1,
FindPointsGSLIB(Mesh &mesh_in, const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
@@ -322,7 +388,7 @@ public:
FindPointsGSLIB(MPI_Comm comm_);
/// Constructor + setup with given ParMesh (see \ref Setup)
FindPointsGSLIB(ParMesh &mesh_in, const double bb_t = 0.1,
FindPointsGSLIB(ParMesh &mesh_in, const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
#endif
@@ -338,23 +404,59 @@ public:
Note: not tested with periodic (L2).
Note: the input mesh \p m must have Nodes set.
@param[in] m Input mesh.
@param[in] bb_t (Optional) Relative size of bounding box around
each element.
@param[in] newt_tol (Optional) Newton tolerance for the gslib
search methods.
@param[in] npt_max (Optional) Number of points for simultaneous
iteration. This alters performance and
memory footprint.
@param[in] m Input mesh.
@param[in] bbox_rel_size_inc (Optional) Relative size increase applied
when expanding each element bounding box.
@param[in] newt_tol (Optional) Newton tolerance for the gslib
search methods.
@param[in] npt_max (Optional) Number of points for
simultaneous iteration. This alters
performance and memory footprint.
*/
void Setup(Mesh &m, const double bb_t = 0.1, const double newt_tol = 1.0e-12,
void Setup(Mesh &m, const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
/// Preprocess the surface mesh to compute data for FindPoints.
void SetupSurf(Mesh &m,
const double bb_t = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12);
/** @brief Preprocess the surface mesh to compute data for FindPoints using
* absolute AABB expansion.
*
* @details This method computes only axis-aligned bounding boxes and
* increases their total length by a user-specified amount in each
* physical direction. The absolute AABB expansion is applied
* symmetrically to the lower and upper bounds.
*
* The size of @a aabb_sz_inc determines how the expansion values are
* interpreted:
* - `1`: one expansion value used in every direction for every element
* - `NElements`: one expansion value per element, reused in x/y/z
* directions
* - `SpaceDim`: one expansion value per physical direction, reused for
* every element
* - `NElements*SpaceDim`: one expansion value per element and direction,
* ordered as `(dx1,dy1,dz1, ... dxN,dyN,dzN)`
*
* This method disables the oriented bounding-box precheck because the
* stored boxes are modified only in their axis-aligned representation.
*
* @param[in] m Input surface mesh.
* @param[in] aabb_sz_inc Total absolute AABB expansion applied in
* each physical direction to the stored
* axis-aligned bounding boxes.
* @param[in] newt_tol Newton tolerance for the point-search
* kernels.
*
* @note We disable the oriented bounding box check with this setup.
* @a bdr_tol is also adjusted so that all points in the AABBs can
* be found.
*/
void SetupSurfWithAABBExpansion(Mesh &m, const Vector &aabb_sz_inc,
const double newt_tol = 1.0e-12);
/** @brief Searches positions given in physical space by \p point_pos.
@@ -401,7 +503,8 @@ public:
/// Setup FindPoints and search positions
void FindPoints(Mesh &m, const Vector &point_pos,
const int point_pos_ordering = Ordering::byNODES,
const double bb_t = 0.1, const double newt_tol = 1.0e-12,
const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
/** @brief Interpolation of field values at prescribed reference space
@@ -413,7 +516,11 @@ public:
mesh that was given to Setup().
@param[out] field_out Interpolated values. For points that are not found
the value is set to #default_interp_value.
The output ordering is determined from field_in.*/
The output ordering is determined from field_in.
@note: field_out is moved to device if field_in is on device. Otherwise,
field_out memory allocation is not changed.
*/
virtual void Interpolate(const GridFunction &field_in, Vector &field_out);
/// Interpolation of field values, with output ordering specification.
@@ -468,7 +575,12 @@ public:
* @details When using FindPoints, gslib may return points as found on the
* boundary even when they are slightly outside the domain. This tolerance
* is used to filter such points based on the distance^2 value and mark them
* as not found.*/
* as not found.
*
* @note When the SetupSurfWithAABBExpansion method is used for surface
* meshes, this tolerance is automatically computed based on the size of
* expanded AABBs. Using this method will override that computed tolerance.
* */
virtual void SetDistanceToleranceForPointsFoundOnBoundary(double bdr_tol_)
{
bdr_tol = bdr_tol_;
@@ -603,25 +715,28 @@ public:
Note: not tested with periodic meshes (L2).
Note: the input mesh \p m must have Nodes set.
@param[in] m Input mesh.
@param[in] meshid A unique # for each overlapping mesh. This id is
used to make sure that points being searched are not
looked for in the mesh that they belong to.
@param[in] gfmax (Optional) GridFunction in H1 that is used as a
discriminator when one point is located in multiple
meshes. The mesh that maximizes gfmax is chosen.
For example, using the distance field based on the
overlapping boundaries is helpful for convergence
during Schwarz iterations.
@param[in] bb_t (Optional) Relative size of bounding box around
each element.
@param[in] newt_tol (Optional) Newton tolerance for the gslib
search methods.
@param[in] npt_max (Optional) Number of points for simultaneous
iteration. This alters performance and
memory footprint.*/
void Setup(Mesh &m, const int meshid, GridFunction *gfmax = NULL,
const double bb_t = 0.1, const double newt_tol = 1.0e-12,
@param[in] m Input mesh.
@param[in] meshid A unique # for each overlapping mesh.
This id is used to make sure that points
being searched are not looked for in the
mesh that they belong to.
@param[in] gfmax (Optional) GridFunction in H1 that is used
as a discriminator when one point is
located in multiple meshes. The mesh that
maximizes gfmax is chosen. For example,
using the distance field based on the
overlapping boundaries is helpful for
convergence during Schwarz iterations.
@param[in] bbox_rel_size_inc (Optional) Relative size increase applied
when expanding each element bounding box.
@param[in] newt_tol (Optional) Newton tolerance for the gslib
search methods.
@param[in] npt_max (Optional) Number of points for
simultaneous iteration. This alters
performance and memory footprint.*/
void Setup(Mesh &m, const int meshid, GridFunction *gfmax = nullptr,
const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
/** Searches positions given in physical space by \p point_pos. All output
@@ -677,7 +792,7 @@ class GSOPGSLIB
protected:
struct gslib::crystal *cr; // gslib's internal data
struct gslib::comm *gsl_comm; // gslib's internal data
struct gslib::gs_data *gsl_data = NULL;
struct gslib::gs_data *gsl_data = nullptr;
int num_ids;
public:
+64 -170
View File
@@ -11,7 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "../../linalg/kernels.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -27,8 +27,6 @@
#pragma GCC diagnostic pop
#endif
#include <climits>
namespace mfem
{
#if GSLIB_RELEASE_VERSION >= 10009
@@ -54,127 +52,14 @@ struct findptsElementGPT_t
double x[DIM], jac[DIM * DIM], hes[4];
};
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[DIM], A[DIM * DIM];
dbl_range_t x[DIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[DIM];
double fac[DIM];
unsigned int *offset;
int max;
};
// Eval the ith Lagrange interpolant and its first derivative at x.
// Note: lCoeff stores pre-computed coefficients for fast evaluation.
static MFEM_HOST_DEVICE inline void lag_eval_first_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2 * (x - z[j]);
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN+i] = 2.0 * lCoeff[i] * u1;
}
// Eval the ith Lagrange interpolant and its first and second derivative at x.
// Note: lCoeff stores pre-computed coefficients for fast evaluation.
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2 * (x - z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN+i] = 2.0 * lCoeff[i] * u1;
p0[2*pN+i] = 8.0 * lCoeff[i] * u2;
}
// Axis-aligned bounding box test.
static MFEM_HOST_DEVICE inline double AABB_test(const obbox_t *const b,
const double x[2])
{
double test = 1;
for (int d = 0; d < 2; ++d)
{
double b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
test = test < 0 ? test : b_d;
}
return test;
}
// Axis-aligned bounding box test followed by oriented bounding-box test.
static MFEM_HOST_DEVICE inline double bbox_test(const obbox_t *const b,
const double x[2])
{
const double bxyz = AABB_test(b, x);
if (bxyz < 0)
{
return bxyz;
}
else
{
double dxyz[2];
for (int d = 0; d < 2; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
double test = 1;
for (int d = 0; d < 2; ++d)
{
double rst = 0;
for (int e = 0; e < 2; ++e)
{
rst += b->A[d * 2 + e] * dxyz[e];
}
double brst = (rst + 1) * (1 - rst);
test = test < 0 ? test : brst;
}
return test;
}
}
// Element index corresponding to hash mesh that the point is located in.
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[2])
{
const int n = p->hash_n;
int sum = 0;
for (int d = 2 - 1; d >= 0; --d)
{
sum *= n;
int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i < 0 ? 0 : (n - 1 < i ? n - 1 : i);
}
return sum;
}
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<DIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<DIM>;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_first_der;
using gslib::lag_eval_second_der;
/*Solve Ax=y. A is row-major */
static MFEM_HOST_DEVICE inline void lin_solve_2(double x[2], const double A[4],
@@ -185,12 +70,6 @@ static MFEM_HOST_DEVICE inline void lin_solve_2(double x[2], const double A[4],
x[1] = idet*(A[0]*y[1] - A[2]*y[0]);
}
/* L2 norm squared. */
static MFEM_HOST_DEVICE inline double l2norm2(const double x[2])
{
return x[0] * x[0] + x[1] * x[1];
}
/* the bit structure of flags is CSSRR
the C bit --- 1<<4 --- is set when the point is converged
RR is 0 = 00b if r is unconstrained,
@@ -352,7 +231,7 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *res,
const findptsElementPoint_t *p,
const double tol)
{
const double dist2 = l2norm2(resid);
const double dist2 = l2norm2<2>(resid);
const double decr = p->dist2 - dist2;
const double pred = p->dist2p;
for (int d = 0; d < 2; ++d)
@@ -695,25 +574,25 @@ static MFEM_HOST_DEVICE double tensor_ig2_j(double *g_partials,
}
template<int T_D1D = 0>
static void FindPointsLocal2D_Kernel(const int npt,
const double tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
static void FindPointsLocal2DKernel(const int npt,
const double tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
{
const int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
const int D1D = T_D1D ? T_D1D : pN;
@@ -1175,30 +1054,45 @@ void FindPointsGSLIB::FindPointsLocal2(const Vector &point_pos,
switch (DEV.dof1d)
{
case 2:
return FindPointsLocal2D_Kernel<2>(
npt, DEV.newt_tol, pp, point_pos_ordering, pgslm, NE_split_total, pwt,
pbb, DEV.lh_nx, plhm, plhf, plho, pcode, pelem, pref, pdist,
pgll1d, plc);
FindPointsLocal2DKernel<2>(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 3:
return FindPointsLocal2D_Kernel<3>(
npt, DEV.newt_tol, pp, point_pos_ordering, pgslm, NE_split_total, pwt,
pbb, DEV.lh_nx, plhm, plhf, plho, pcode, pelem, pref, pdist,
pgll1d, plc);
FindPointsLocal2DKernel<3>(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 4:
return FindPointsLocal2D_Kernel<4>(
npt, DEV.newt_tol, pp, point_pos_ordering, pgslm, NE_split_total, pwt,
pbb, DEV.lh_nx, plhm, plhf, plho, pcode, pelem, pref, pdist,
pgll1d, plc);
FindPointsLocal2DKernel<4>(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 5:
return FindPointsLocal2D_Kernel<5>(
npt, DEV.newt_tol, pp, point_pos_ordering, pgslm, NE_split_total, pwt,
pbb, DEV.lh_nx, plhm, plhf, plho, pcode, pelem, pref, pdist,
pgll1d, plc);
FindPointsLocal2DKernel<5>(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
default:
return FindPointsLocal2D_Kernel(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx,
plhm, plhf, plho, pcode, pelem,
pref, pdist, pgll1d, plc, DEV.dof1d);
FindPointsLocal2DKernel(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc, DEV.dof1d);
break;
}
}
#undef DIM2
+29 -157
View File
@@ -11,9 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "../../linalg/kernels.hpp"
#include <climits>
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -59,128 +57,15 @@ struct findptsElemPt
double x[DIM], jac[DIM * DIM], hes[18];
};
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[DIM], A[DIM * DIM];
dbl_range_t x[DIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[DIM];
double fac[DIM];
unsigned int *offset;
// int max;
};
// Eval the ith Lagrange interpolant and its first derivative at x.
// Note: lCoeff stores pre-computed coefficients for fast evaluation.
static MFEM_HOST_DEVICE inline void lag_eval_first_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2*(x-z[j]);
u1 = d_j*u1+u0;
u0 = d_j*u0;
}
}
p0[i] = lCoeff[i]*u0;
p0[pN+i] = 2.0*lCoeff[i]*u1;
}
// Eval the ith Lagrange interpolant and its first and second derivative at x.
// Note: lCoeff stores pre-computed coefficients for fast evaluation.
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2*(x-z[j]);
u2 = d_j*u2+u1;
u1 = d_j*u1+u0;
u0 = d_j*u0;
}
}
p0[i] = lCoeff[i]*u0;
p0[pN+i] = 2.0*lCoeff[i]*u1;
p0[2*pN+i] = 8.0*lCoeff[i]*u2;
}
// Axis-aligned bounding box test.
static MFEM_HOST_DEVICE inline double AABB_test(const obbox_t *const b,
const double x[3])
{
double b_d;
for (int d = 0; d < 3; ++d)
{
b_d = (x[d]-b->x[d].min)*(b->x[d].max-x[d]);
if (b_d < 0) { return b_d; }
}
return b_d;
}
// Axis-aligned bounding box test followed by oriented bounding-box test.
static MFEM_HOST_DEVICE inline double bbox_test(const obbox_t *const b,
const double x[3])
{
const double bxyz = AABB_test(b, x);
if (bxyz < 0)
{
return bxyz;
}
else
{
double dxyz[3];
for (int d = 0; d < 3; ++d)
{
dxyz[d] = x[d]-b->c0[d];
}
double test = 1;
for (int d = 0; d < 3; ++d)
{
double rst = 0;
for (int e = 0; e < 3; ++e)
{
rst += b->A[d*3+e]*dxyz[e];
}
double brst = (rst+1)*(1-rst);
test = test < 0 ? test : brst;
}
return test;
}
}
// Element index corresponding to hash mesh that the point is located in.
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[3])
{
const int n = p->hash_n;
int sum = 0;
for (int d = 3-1; d >= 0; --d)
{
sum *= n;
int i = (int)floor((x[d]-p->bnd[d].min)*p->fac[d]);
sum += i < 0 ? 0 : (n-1 < i ? n-1 : i);
}
return sum;
}
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<DIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<DIM>;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_first_der;
using gslib::lag_eval_second_der;
using gslib::lin_solve_sym_2;
// Solve Ax=y. A is row-major.
static MFEM_HOST_DEVICE inline void lin_solve_3(double x[3], const double A[9],
@@ -199,22 +84,6 @@ static MFEM_HOST_DEVICE inline void lin_solve_3(double x[3], const double A[9],
x[2] = idet*(inv6*y[0]+inv7*y[1]+inv8*y[2]);
}
// Solve Ax=y. A is a symmetric 2x2 matrix.
static MFEM_HOST_DEVICE inline void lin_solve_sym_2(double x[2],
const double A[3],
const double y[2])
{
const double idet = 1 / (A[0]*A[2]-A[1]*A[1]);
x[0] = idet*(A[2]*y[0]-A[1]*y[1]);
x[1] = idet*(A[0]*y[1]-A[1]*y[0]);
}
// L2 norm.
static MFEM_HOST_DEVICE inline double l2norm2(const double x[3])
{
return x[0]*x[0]+x[1]*x[1]+x[2]*x[2];
}
/* the bit structure of flags is CTTSSRR
the C bit --- 1<<6 --- is set when the point is converged
RR is 0 = 00b if r is unconstrained,
@@ -459,7 +328,7 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsPt *res,
const findptsPt *p,
const double tol)
{
const double dist2 = l2norm2(resid);
const double dist2 = l2norm2<3>(resid);
const double decr = p->dist2-dist2;
const double pred = p->dist2p;
for (int d = 0; d < 3; ++d)
@@ -1809,33 +1678,36 @@ void FindPointsGSLIB::FindPointsLocal3(const Vector &point_pos,
{
case 2:
FindPointsLocal3DKernel<2>(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx, plhm,
plhf, plho, pcode, pelem, pref, pdist, pgll1d,
plc);
pgslm, NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc);
break;
case 3:
FindPointsLocal3DKernel<3>(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx, plhm,
plhf, plho, pcode, pelem, pref, pdist, pgll1d,
plc);
pgslm, NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc);
break;
case 4:
FindPointsLocal3DKernel<4>(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx, plhm,
plhf, plho, pcode, pelem, pref, pdist, pgll1d,
plc);
pgslm, NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc);
break;
case 5:
FindPointsLocal3DKernel<5>(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx, plhm,
plhf, plho, pcode, pelem, pref, pdist, pgll1d,
plc);
pgslm, NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc);
break;
default:
FindPointsLocal3DKernel(npt, DEV.newt_tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc,
FindPointsLocal3DKernel(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc,
DEV.dof1d);
break;
}
}
#undef pMax
+107 -176
View File
@@ -11,6 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -52,113 +53,14 @@ struct findptsElementGPT_t
double x[sDIM], jac[sDIM*rDIM], hes[sDIM*rDIM];
};
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[sDIM], A[sDIM*sDIM];
dbl_range_t x[sDIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[sDIM];
double fac[sDIM];
unsigned int *offset;
};
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2 * (x-z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
double *p1 = p0 + pN, *p2 = p0 + 2 * pN;
p0[i] = lCoeff[i] * u0;
p1[i] = 2.0 * lCoeff[i] * u1;
p2[i] = 8.0 * lCoeff[i] * u2;
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double obbox_axis_test(const obbox_t *const b,
const double x[sDIM])
{
double b_d;
for (int d=0; d<sDIM; ++d)
{
b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
if (b_d < 0) // if outside in any dimension
{
return b_d;
}
}
return b_d; // only positive if inside
}
/* positive when given point is possibly inside given obbox b */
static MFEM_HOST_DEVICE inline double obbox_test(const obbox_t *const b,
const double x[sDIM])
{
const double bxyz = obbox_axis_test(b,x);
if (bxyz<0) // test if point is in AABB
{
return bxyz;
}
else // test OBB only if inside AABB
{
double dxyz[sDIM];
for (int d=0; d<sDIM; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
double test = 1;
for (int d=0; d<sDIM; ++d)
{
double rst = 0;
for (int e=0; e<sDIM; ++e)
{
rst += b->A[d*2 + e] * dxyz[e];
}
double brst = (rst+1)*(1-rst);
test = test<0 ? test : brst;
}
return test;
}
}
/* Hash index in the hash table to the elements that possibly contain the point x */
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[2])
{
const int n = p->hash_n;
int sum = 0;
for (int d=sDIM-1; d>=0; --d)
{
sum *= n;
int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i<0 ? 0 : (n-1 < i ? n-1 : i);
}
return sum;
}
static MFEM_HOST_DEVICE inline double l2norm2(const double x[2])
{
return x[0] * x[0] + x[1] * x[1];
}
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<sDIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<sDIM>;
using gslib::AABB_test;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_second_der;
/* the bit structure of flags is CRR
the C bit --- 1<<2 --- is set when the point is converged
@@ -187,29 +89,29 @@ static MFEM_HOST_DEVICE inline int point_index(const int x)
/* check reduction in objective against prediction, and adjust
trust region radius (p->tr) accordingly;
may reject the prior step, returning 1; otherwise returns 0
sets out->dist2, out->index, out->x, out->oldr in any event,
leaving out->r, out->dr, out->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
sets out_pt->dist2, out_pt->index, out_pt->x, out_pt->oldr in any event,
leaving out_pt->r, out_pt->dr, out_pt->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
const double resid[2],
const findptsElementPoint_t *p,
const double tol)
{
const double dist2 = l2norm2(resid);
const double dist2 = l2norm2<2>(resid);
const double decr = p->dist2 - dist2;
const double pred = p->dist2p;
out->x[0] = p->x[0];
out->x[1] = p->x[1];
out->oldr = p->r;
out->dist2 = dist2;
out_pt->x[0] = p->x[0];
out_pt->x[1] = p->x[1];
out_pt->oldr = p->r;
out_pt->dist2 = dist2;
if (decr >= 0.01*pred)
{
if (decr >= 0.9*pred) // very good iteration
{
out->tr = p->tr*2;
out_pt->tr = p->tr*2;
}
else // somewhat good iteration
{
out->tr = p->tr;
out_pt->tr = p->tr;
}
return false;
}
@@ -220,21 +122,21 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
"very good iteration" --- this doubles the trust radius,
which is why we divide by 4 below */
double v0 = fabs(p->r - p->oldr);
out->tr = v0/4.0;
out->dist2 = p->dist2;
out->r = p->oldr;
out->flags = p->flags>>3;
out->dist2p = -HUGE_VAL;
out_pt->tr = v0/4.0;
out_pt->dist2 = p->dist2;
out_pt->r = p->oldr;
out_pt->flags = p->flags>>3;
out_pt->dist2p = -HUGE_VAL;
if (pred < dist2*tol)
{
out->flags |= CONVERGED_FLAG;
out_pt->flags |= CONVERGED_FLAG;
}
return true;
}
}
static MFEM_HOST_DEVICE inline void newton_edge( findptsElementPoint_t *const
out,
out_pt,
const double jac[2],
const double rhess,
const double resid[2],
@@ -304,9 +206,9 @@ newton_edge_fin:
{
new_flags |= CONVERGED_FLAG;
}
out->r = newr;
out->dist2p = -v;
out->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<3);
out_pt->r = newr;
out_pt->dist2p = -v;
out_pt->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<3);
}
static MFEM_HOST_DEVICE void seed_j( const double *elx[sDIM],
@@ -332,26 +234,27 @@ static MFEM_HOST_DEVICE void seed_j( const double *elx[sDIM],
}
template<int T_D1D = 0>
static void FindPointsEdgeLocal2D_Kernel( const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0 )
static void FindPointsEdgeLocal2DKernel( const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const bool obb_check,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0 )
{
const int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
const int D1D = T_D1D ? T_D1D : pN;
@@ -412,22 +315,34 @@ static void FindPointsEdgeLocal2D_Kernel( const int npt,
{
const unsigned int el = *elp;
const int n_box_ents = obb_check ? (3*sDIM + sDIM2) : (2*sDIM);
bool pass_bb = true;
obbox_t box;
int n_box_ents = 3*sDIM + sDIM2;
for (int idx = 0; idx < sDIM; ++idx)
if (obb_check)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
for (int idx = 0; idx < sDIM; ++idx)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
pass_bb = (bbox_test(&box, x_i) >= 0);
}
else
{
for (int d = 0; d < sDIM; ++d)
{
box.x[d].min = boxinfo[n_box_ents*el + d];
box.x[d].max = boxinfo[n_box_ents*el + sDIM + d];
}
pass_bb = (AABB_test(&box, x_i) >= 0);
}
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
if (obbox_test(&box,x_i)>=0)
if (pass_bb)
{
//------------ findpts_local ------------------
{
@@ -516,11 +431,14 @@ static void FindPointsEdgeLocal2D_Kernel( const int npt,
double *hess = jac + sDIM*rDIM;
findptsElementGEdge_t edge;
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
}
MFEM_FOREACH_THREAD(j,x,D1D)
{
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
edge.x[d][j] = elx[d][j];
}
}
@@ -681,28 +599,41 @@ void FindPointsGSLIB::FindPointsEdgeLocal2( const Vector &point_pos,
auto pgll1d = DEV.gll1d.ReadWrite(use_dev);
auto plc = DEV.lagcoeff.Read(use_dev);
double dist2tol = DEV.surf_dist_tol;
const bool obb_chk = obb_check;
switch (DEV.dof1d)
{
case 2:
return FindPointsEdgeLocal2D_Kernel<2>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsEdgeLocal2DKernel<2>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 3:
return FindPointsEdgeLocal2D_Kernel<3>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsEdgeLocal2DKernel<3>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 4:
return FindPointsEdgeLocal2D_Kernel<4>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsEdgeLocal2DKernel<4>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
default:
return FindPointsEdgeLocal2D_Kernel(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc, DEV.dof1d);
FindPointsEdgeLocal2DKernel(npt, DEV.newt_tol, dist2tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc, DEV.dof1d);
break;
}
}
#undef sDIM
+109 -181
View File
@@ -11,6 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -54,117 +55,14 @@ struct findptsElementGPT_t
double x[sDIM], jac[sDIM], hes[sDIM*(1+1)];
};
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[sDIM], A[sDIM*sDIM];
dbl_range_t x[sDIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[sDIM];
double fac[sDIM];
unsigned int *offset;
};
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j=0; j<pN; ++j)
{
if (i!=j)
{
double d_j = 2 * (x-z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
double *p1 = p0 + pN, *p2 = p0 + 2 * pN;
p0[i] = lCoeff[i] * u0;
p1[i] = 2.0 * lCoeff[i] * u1;
p2[i] = 8.0 * lCoeff[i] * u2;
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double obbox_axis_test(const obbox_t *const b,
const double x[sDIM])
{
double b_d;
for (int d=0; d<sDIM; ++d)
{
b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
if (b_d < 0) // if outside in any dimension
{
return b_d;
}
}
return b_d; // only positive if inside in all dimensions
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double obbox_test(const obbox_t *const b,
const double x[sDIM])
{
const double bxyz = obbox_axis_test(b, x);
if (bxyz<0)
{
return bxyz;
}
else
{
double dxyz[3];
// dxyz: distance of the point from the center of the OBB
for (int d=0; d<sDIM; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
// transform dxyz to the local coordinate system of the OBB,
// and check if the point is inside the OBB [-1,1]^sDIM
double test = 1;
for (int d=0; d<sDIM; ++d)
{
double rst = 0;
for (int e=0; e<sDIM; ++e)
{
rst += b->A[d*sDIM + e] * dxyz[e];
}
double brst = (rst+1)*(1-rst);
test = test<0 ? test : brst;
}
return test;
}
}
/* Hash index in the hash table to the elements that possibly contain the point x */
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[sDIM])
{
const int n = p->hash_n;
int sum = 0;
for (int d=sDIM-1; d>=0; --d)
{
sum *= n;
int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i<0 ? 0 : (n-1 < i ? n-1 : i);
}
return sum;
}
static MFEM_HOST_DEVICE inline double norm2(const double x[sDIM])
{
return ( x[0]*x[0] + x[1]*x[1] + x[2]*x[2] );
}
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<sDIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<sDIM>;
using gslib::AABB_test;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_second_der;
/* the bit structure of flags is CRR
the C bit --- 1<<2 --- is set when the point is converged
@@ -175,47 +73,46 @@ static MFEM_HOST_DEVICE inline double norm2(const double x[sDIM])
#define CONVERGED_FLAG (1u<<2)
#define FLAG_MASK 0x07u
/* returns the number of constrained reference coordinates, max 2
/* returns the number of constrained reference coordinates, max 1
*/
static MFEM_HOST_DEVICE inline int num_constrained(const int flags)
{
const int y = (flags | flags>>1);
return (y & 1u) + (y>>2 & 1u);
return ((flags | flags>>1) & 1u);
}
static MFEM_HOST_DEVICE inline int point_index(const int x)
{
return ((x>>1)&1u) | ((x>>2)&2u);
return ((x>>1)&1u);
}
/* check reduction in objective against prediction, and adjust
trust region radius (p->tr) accordingly;
may reject the prior step, returning 1; otherwise returns 0
sets out->dist2, out->index, out->x, out->oldr in any event,
leaving out->r, out->dr, out->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
sets out_pt->dist2, out_pt->index, out_pt->x, out_pt->oldr in any event,
leaving out_pt->r, out_pt->dr, out_pt->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
const double resid[3],
const findptsElementPoint_t *p,
const double tol)
{
const double dist2 = norm2(resid);
const double dist2 = l2norm2<sDIM>(resid);
const double decr = p->dist2 - dist2;
const double pred = p->dist2p;
for (int d=0; d<sDIM; ++d)
{
out->x[d] = p->x[d];
out_pt->x[d] = p->x[d];
}
out->oldr = p->r;
out->dist2 = dist2;
out_pt->oldr = p->r;
out_pt->dist2 = dist2;
if (decr>=0.01*pred)
{
if (decr>=0.9*pred) // very good iteration
{
out->tr = 2*p->tr;
out_pt->tr = 2*p->tr;
}
else // good iteration
{
out->tr = p->tr;
out_pt->tr = p->tr;
}
return false;
}
@@ -226,21 +123,21 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
"very good iteration" --- this doubles the trust radius,
which is why we divide by 4 below */
double v0 = fabs(p->r - p->oldr);
out->tr = v0/4.0;
out->dist2 = p->dist2;
out->r = p->oldr;
out->flags = p->flags>>3;
out->dist2p = -HUGE_VAL;
out_pt->tr = v0/4.0;
out_pt->dist2 = p->dist2;
out_pt->r = p->oldr;
out_pt->flags = p->flags>>3;
out_pt->dist2p = -HUGE_VAL;
if (pred<dist2*tol)
{
out->flags |= CONVERGED_FLAG;
out_pt->flags |= CONVERGED_FLAG;
}
return true;
}
}
static MFEM_HOST_DEVICE inline void newton_edge(findptsElementPoint_t *const
out,
out_pt,
const double jac[sDIM*rDIM],
const double rhes,
const double resid[sDIM],
@@ -314,9 +211,9 @@ newton_edge_fin:
{
new_flags |= CONVERGED_FLAG;
}
out->r = nr;
out->dist2p = -v;
out->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<3);
out_pt->r = nr;
out_pt->dist2p = -v;
out_pt->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<3);
#undef EVAL
}
@@ -338,31 +235,32 @@ static MFEM_HOST_DEVICE void seed_j(const double *elx[sDIM],
{
dx[d] = x[d] - elx[d][ir];
}
dist2[ir] = norm2(dx);;
dist2[ir] = l2norm2(dx);
r[ir] = z[ir];
}
template<int T_D1D = 0>
static void FindPointsEdgeLocal3D_Kernel(const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
static void FindPointsEdgeLocal3DKernel(const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const bool obb_check,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
{
const int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
const int D1D = T_D1D ? T_D1D : pN;
@@ -419,21 +317,35 @@ static void FindPointsEdgeLocal3D_Kernel(const int npt,
for (; elp!=ele; ++elp)
{
const unsigned int el = *elp;
const int n_box_ents = obb_check ? (3*sDIM + sDIM2) : (2*sDIM);
bool pass_bb = true;
obbox_t box;
int n_box_ents = 3*sDIM + sDIM2;
for (int idx = 0; idx < sDIM; ++idx)
if (obb_check)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
for (int idx = 0; idx < sDIM; ++idx)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
pass_bb = (bbox_test(&box, x_i) >= 0);
}
for (int idx = 0; idx < sDIM2; ++idx)
else
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
for (int d = 0; d < sDIM; ++d)
{
box.x[d].min = boxinfo[n_box_ents*el + d];
box.x[d].max = boxinfo[n_box_ents*el + sDIM + d];
}
pass_bb = (AABB_test(&box, x_i) >= 0);
}
if (obbox_test(&box, x_i)>=0)
if (pass_bb)
{
//// findpts_local ////
{
@@ -521,11 +433,14 @@ static void FindPointsEdgeLocal3D_Kernel(const int npt,
double *hess = jac + sDIM*rDIM;
findptsElementGEdge_t edge;
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
}
MFEM_FOREACH_THREAD(j,x,D1D)
{
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
edge.x[d][j] = elx[d][j];
}
}
@@ -688,28 +603,41 @@ void FindPointsGSLIB::FindPointsEdgeLocal3(const Vector &point_pos,
auto pgll1d = DEV.gll1d.ReadWrite(use_dev);
auto plc = DEV.lagcoeff.Read(use_dev);
double dist2tol = DEV.surf_dist_tol;
const bool obb_chk = obb_check;
switch (DEV.dof1d)
{
case 2:
return FindPointsEdgeLocal3D_Kernel<2>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsEdgeLocal3DKernel<2>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 3:
return FindPointsEdgeLocal3D_Kernel<3>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsEdgeLocal3DKernel<3>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 4:
return FindPointsEdgeLocal3D_Kernel<4>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsEdgeLocal3DKernel<4>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
default:
return FindPointsEdgeLocal3D_Kernel(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc, DEV.dof1d);
FindPointsEdgeLocal3DKernel(npt, DEV.newt_tol, dist2tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc, DEV.dof1d);
break;
}
}
#undef rDIM2
+131 -206
View File
@@ -11,6 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
@@ -51,124 +52,15 @@ struct findptsElementGPT_t
double x[sDIM], jac[sDIM*rDIM], hes[sDIM*(rDIM+1)];
};
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[sDIM], A[sDIM*sDIM];
dbl_range_t x[sDIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[sDIM];
double fac[sDIM];
unsigned int *offset;
};
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2 * (x - z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN+i] = 2.0 * lCoeff[i] * u1;
p0[2*pN+i] = 8.0 * lCoeff[i] * u2;
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double AABB_test(const obbox_t *const b,
const double x[sDIM])
{
double b_d;
for (int d=0; d<sDIM; ++d)
{
b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
if (b_d < 0) // if outside in any dimension
{
return b_d;
}
}
return b_d; // only positive if inside in all dimensions
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double bbox_test(const obbox_t *const b,
const double x[sDIM])
{
const double bxyz = AABB_test(b, x);
if (bxyz<0)
{
return bxyz;
}
else
{
double dxyz[3];
// dxyz: distance of the point from the center of the OBB
for (int d=0; d<sDIM; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
// tranform dxyz to the local coordinate system of the OBB,
// and check if the point is inside the OBB [-1,1]^sDIM
double test = 1;
for (int d=0; d<sDIM; ++d)
{
double rst = 0;
for (int e=0; e<sDIM; ++e)
{
rst += b->A[d*sDIM + e] * dxyz[e];
}
double brst = (rst+1)*(1-rst);
test = test<0 ? test : brst;
}
return test;
}
}
/* Hash index in the hash table to the elements that possibly contain the point x */
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[sDIM])
{
const int n = p->hash_n;
int sum = 0;
for (int d=sDIM-1; d>=0; --d)
{
sum *= n;
int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i<0 ? 0 : (n-1 < i ? n-1 : i);
}
return sum;
}
static MFEM_HOST_DEVICE inline void lin_solve_sym_2(double x[2],
const double A[3],
const double y[2])
{
const double idet = 1 / (A[0] * A[2] - A[1] * A[1]);
x[0] = idet * (A[2] * y[0] - A[1] * y[1]);
x[1] = idet * (A[0] * y[1] - A[1] * y[0]);
}
static MFEM_HOST_DEVICE inline double l2norm2(const double x[sDIM])
{
return ( x[0]*x[0] + x[1]*x[1] + x[2]*x[2]);
}
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<sDIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<sDIM>;
using gslib::AABB_test;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_second_der;
using gslib::lin_solve_sym_2;
/* the bit structure of flags is CSSRR
the C bit --- 1<<4 --- is set when the point is converged
@@ -219,18 +111,10 @@ static MFEM_HOST_DEVICE inline int point_index(const int x)
return ((x>>1)&1u) | ((x>>2)&2u);
}
static MFEM_HOST_DEVICE inline findptsElementGEdge_t
static MFEM_HOST_DEVICE inline void
get_edge(const double *elx[3], const double *wtend, int ei,
double *workspace, int &side_init, int jidx, int pN)
int &side_init, int jidx, int pN, findptsElementGEdge_t &edge)
{
findptsElementGEdge_t edge;
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = workspace + d*pN;
edge.dxdn[d] = workspace + sDIM*pN + d*pN;
edge.d2xdn[d] = workspace + 2*sDIM*pN + d*pN;
}
// given edge index, compute normal and tangential directions
const int dn = ei>>1, //0 for rmin/rmax, 1 for smin/smax
de = plus_1_mod_2(dn); // 1 for rmin/rmax, 0 for smin/smax
@@ -256,7 +140,6 @@ get_edge(const double *elx[3], const double *wtend, int ei,
edge.d2xdn[dd][jj] = sums_k[1];
#undef ELX
}
return edge;
}
static MFEM_HOST_DEVICE inline findptsElementGPT_t get_pt(const double *elx[3],
@@ -312,34 +195,34 @@ static MFEM_HOST_DEVICE inline findptsElementGPT_t get_pt(const double *elx[3],
/* check reduction in objective against prediction, and adjust
trust region radius (p->tr) accordingly;
may reject the prior step, returning 1; otherwise returns 0
sets out->dist2, out->index, out->x, out->oldr in any event,
leaving out->r, out->dr, out->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
sets out_pt->dist2, out_pt->index, out_pt->x, out_pt->oldr in any event,
leaving out_pt->r, out_pt->dr, out_pt->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
const double resid[3],
const findptsElementPoint_t *p,
const double tol)
{
const double dist2 = l2norm2(resid);
const double dist2 = l2norm2<sDIM>(resid);
const double decr = p->dist2 - dist2;
const double pred = p->dist2p;
for (int d=0; d<sDIM; ++d)
{
out->x[d] = p->x[d];
out_pt->x[d] = p->x[d];
}
for (int d=0; d<rDIM; ++d)
{
out->oldr[d] = p->r[d];
out_pt->oldr[d] = p->r[d];
}
out->dist2 = dist2;
out_pt->dist2 = dist2;
if (decr>=0.01*pred)
{
if (decr>=0.9*pred) // very good iteration
{
out->tr = 2*p->tr;
out_pt->tr = 2*p->tr;
}
else // good iteration
{
out->tr = p->tr;
out_pt->tr = p->tr;
}
return false;
}
@@ -351,17 +234,17 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
which is why we divide by 4 below */
double v0 = fabs(p->r[0] - p->oldr[0]),
v1 = fabs(p->r[1] - p->oldr[1]);
out->tr = ( v0>v1 ? v0 : v1 )/4;
out->dist2 = p->dist2;
out->flags = p->flags >> 5;
out->dist2p = -HUGE_VAL;
out_pt->tr = ( v0>v1 ? v0 : v1 )/4;
out_pt->dist2 = p->dist2;
out_pt->flags = p->flags >> 5;
out_pt->dist2p = -HUGE_VAL;
for (int d=0; d<rDIM; ++d)
{
out->r[d] = p->oldr[d];
out_pt->r[d] = p->oldr[d];
}
if (pred<dist2*tol)
{
out->flags |= CONVERGED_FLAG;
out_pt->flags |= CONVERGED_FLAG;
}
return true;
}
@@ -369,7 +252,7 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
/* minimize ||resid - jac * dr||_2, with |dr| <= tr, |r0+dr|<=1
(exact solution of trust region problem) */
static MFEM_HOST_DEVICE void newton_face( findptsElementPoint_t *const out,
static MFEM_HOST_DEVICE void newton_face( findptsElementPoint_t *const out_pt,
const double jac[sDIM*rDIM],
const double rhes[3],
const double resid[sDIM],
@@ -540,19 +423,19 @@ newton_face_constrained:
}
newton_face_fin:
out->dist2p = -2*v;
out_pt->dist2p = -2*v;
dr[0] = r[0] - p->r[0];
dr[1] = r[1] - p->r[1];
if ( fabs(dr[0])+fabs(dr[1]) < tol)
{
new_flags |= CONVERGED_FLAG;
}
out->r[0] = r[0], out->r[1] = r[1];
out->flags = new_flags | ((p->flags & FLAG_MASK)<<5);
out_pt->r[0] = r[0], out_pt->r[1] = r[1];
out_pt->flags = new_flags | ((p->flags & FLAG_MASK)<<5);
}
static MFEM_HOST_DEVICE inline void newton_edge(findptsElementPoint_t *const
out,
out_pt,
const double jac[sDIM*rDIM],
const double rhes,
const double resid[sDIM],
@@ -637,10 +520,10 @@ newton_edge_fin:
{
new_flags |= CONVERGED_FLAG;
}
out->r[de] = nr;
out->r[dn] = p->r[dn];
out->dist2p = -v;
out->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<5);
out_pt->r[de] = nr;
out_pt->r[dn] = p->r[dn];
out_pt->dist2p = -v;
out_pt->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<5);
#undef EVAL
}
@@ -676,26 +559,27 @@ static MFEM_HOST_DEVICE void seed_j(const double *elx[sDIM],
// global memory access of element coordinates.
// Are the structs being stored in "local memory" or registers?
template<int T_D1D = 0>
static void FindPointsSurfLocal3D_Kernel(const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
static void FindPointsSurfLocal3DKernel(const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const bool obb_check,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
{
const int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
const int D1D = T_D1D ? T_D1D : pN;
@@ -753,22 +637,36 @@ static void FindPointsSurfLocal3D_Kernel(const int npt,
{
const unsigned int el = *elp;
// construct obbox on the fly
const int n_box_ents = obb_check ? (3*sDIM + sDIM2) : (2*sDIM);
bool pass_bb = true;
obbox_t box;
int n_box_ents = 3*sDIM + sDIM2;
for (int idx = 0; idx < sDIM; ++idx)
if (obb_check)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
// construct obbox on the fly
for (int idx = 0; idx < sDIM; ++idx)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
pass_bb = (bbox_test(&box, x_i) >= 0);
}
else
{
for (int d = 0; d < sDIM; ++d)
{
box.x[d].min = boxinfo[n_box_ents*el + d];
box.x[d].max = boxinfo[n_box_ents*el + sDIM + d];
}
pass_bb = (AABB_test(&box, x_i) >= 0);
}
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
if (bbox_test(&box, x_i) < 0) { continue; }
if (!pass_bb) { continue; }
//// findpts_local ////
{
@@ -968,13 +866,19 @@ static void FindPointsSurfLocal3D_Kernel(const int npt,
double *hes_T = jac + sDIM*rDIM;
double *hes = hes_T + hes_count*sDIM;
findptsElementGEdge_t edge;
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
edge.dxdn[d] = constraint_workspace + d*D1D
+ sDIM*D1D;
edge.d2xdn[d] = constraint_workspace + d*D1D
+ 2*sDIM*D1D;
}
MFEM_FOREACH_THREAD(j,x,D1D*sDIM)
{
// utilized first D1D threads
edge = get_edge(elx, wtend, ei,
constraint_workspace, edge_init, j,
D1D);
// One thread per physical component and edge DOF.
get_edge(elx, wtend, ei, edge_init, j, D1D, edge);
}
MFEM_SYNC_THREAD;
@@ -1045,7 +949,15 @@ static void FindPointsSurfLocal3D_Kernel(const int npt,
steep *= tmp->r[dn];
if (steep<0)
{
newton_face( fpt,jac,hes,resid,tmp->flags&CONVERGED_FLAG,tmp,tol);
double face_hes[3] =
{
dn == 0 ? hes[2] : hes[0],
hes[1],
dn == 0 ? hes[0] : hes[2]
};
newton_face(fpt, jac, face_hes, resid,
tmp->flags & CONVERGED_FLAG,
tmp, tol);
}
else
{
@@ -1211,29 +1123,42 @@ void FindPointsGSLIB::FindPointsSurfLocal3(const Vector &point_pos,
auto pgll1d = DEV.gll1d.ReadWrite(use_dev);
auto plc = DEV.lagcoeff.Read(use_dev);
double dist2tol = DEV.surf_dist_tol;
const bool obb_chk = obb_check;
switch (DEV.dof1d)
{
case 2:
return FindPointsSurfLocal3D_Kernel<2>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsSurfLocal3DKernel<2>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 3:
return FindPointsSurfLocal3D_Kernel<3>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsSurfLocal3DKernel<3>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
case 4:
return FindPointsSurfLocal3D_Kernel<4>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
FindPointsSurfLocal3DKernel<4>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
default:
return FindPointsSurfLocal3D_Kernel(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc, DEV.dof1d);
FindPointsSurfLocal3DKernel(npt, DEV.newt_tol, dist2tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc, DEV.dof1d);
break;
}
}
+190
View File
@@ -0,0 +1,190 @@
#ifndef MFEM_GSLIB_KERNEL_HELPERS_HPP
#define MFEM_GSLIB_KERNEL_HELPERS_HPP
#include "../../config/config.hpp"
#include <cmath>
namespace mfem
{
namespace gslib
{
struct dbl_range_t
{
double min, max;
};
template <int SDIM>
struct obbox_t
{
double c0[SDIM], A[SDIM * SDIM];
dbl_range_t x[SDIM];
};
template <int SDIM>
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[SDIM];
double fac[SDIM];
unsigned int *offset;
};
// Eval the ith Lagrange interpolant at x.
MFEM_HOST_DEVICE inline void lagrange_eval(double *p0, double x,
int i, int p_Nq,
double *z, double *lagrangeCoeff)
{
double p_i = (1 << (p_Nq - 1));
for (int j = 0; j < p_Nq; ++j)
{
const double d_j = x - z[j];
p_i *= j == i ? 1 : d_j;
}
p0[i] = lagrangeCoeff[i] * p_i;
}
// Eval the ith Lagrange interpolant and its first derivative at x.
MFEM_HOST_DEVICE inline void lag_eval_first_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
const double d_j = 2 * (x - z[j]);
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN + i] = 2.0 * lCoeff[i] * u1;
}
// Eval the ith Lagrange interpolant and its first and second derivative at x.
MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
const double d_j = 2 * (x - z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN + i] = 2.0 * lCoeff[i] * u1;
p0[2 * pN + i] = 8.0 * lCoeff[i] * u2;
}
// Solve Ax=y where A is a symmetric 2x2 matrix packed as {a00, a01, a11}.
MFEM_HOST_DEVICE inline void lin_solve_sym_2(double x[2],
const double A[3],
const double y[2])
{
const double idet = 1 / (A[0] * A[2] - A[1] * A[1]);
x[0] = idet * (A[2] * y[0] - A[1] * y[1]);
x[1] = idet * (A[0] * y[1] - A[1] * y[0]);
}
// Positive when the point is inside the axis-aligned bounding box.
template <int SDIM>
MFEM_HOST_DEVICE inline double AABB_test(const obbox_t<SDIM> *const b,
const double (&x)[SDIM])
{
double test = 1.0;
for (int d = 0; d < SDIM; ++d)
{
const double b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
test = test < 0.0 ? test : b_d;
}
return test;
}
// Positive when the point is inside the oriented bounding box.
template <int SDIM>
MFEM_HOST_DEVICE inline double bbox_test(const obbox_t<SDIM> *const b,
const double (&x)[SDIM])
{
const double bxyz = AABB_test(b, x);
if (bxyz < 0.0)
{
return bxyz;
}
double dxyz[SDIM];
for (int d = 0; d < SDIM; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
double test = 1.0;
for (int d = 0; d < SDIM; ++d)
{
double rst = 0.0;
for (int e = 0; e < SDIM; ++e)
{
rst += b->A[d * SDIM + e] * dxyz[e];
}
const double brst = (rst + 1.0) * (1.0 - rst);
test = test < 0.0 ? test : brst;
}
return test;
}
// Hash index in the hash table for the point x.
template <int SDIM>
MFEM_HOST_DEVICE inline int hash_index(
const findptsLocalHashData_t<SDIM> *const p,
const double (&x)[SDIM])
{
const int n = p->hash_n;
int sum = 0;
for (int d = SDIM - 1; d >= 0; --d)
{
sum *= n;
const int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i < 0 ? 0 : (n - 1 < i ? n - 1 : i);
}
return sum;
}
// Squared Euclidean norm.
template <int SDIM>
MFEM_HOST_DEVICE inline double l2norm2(const double (&x)[SDIM])
{
double sum = 0.0;
for (int d = 0; d < SDIM; ++d)
{
sum += x[d] * x[d];
}
return sum;
}
template <int SDIM>
MFEM_HOST_DEVICE inline double l2norm2(const double *x)
{
double sum = 0.0;
for (int d = 0; d < SDIM; ++d)
{
sum += x[d] * x[d];
}
return sum;
}
} // namespace gslib
} // namespace mfem
#endif
+22 -27
View File
@@ -11,7 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "../../linalg/kernels.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -33,17 +33,7 @@ namespace mfem
#define CODE_BORDER 1
#define CODE_NOT_FOUND 2
static MFEM_HOST_DEVICE void lagrange_eval(double *p0, double x,
int i, int p_Nq,
double *z, double *lagrangeCoeff)
{
double p_i = (1 << (p_Nq - 1));
for (int j=0; j<p_Nq; ++j)
{
p_i *= j==i ? 1 : x-z[j];
}
p0[i] = lagrangeCoeff[i] * p_i;
}
using gslib::lagrange_eval;
template<int T_D1D = 0>
static void InterpolateLocal1DKernel(const double *const gf_in,
@@ -123,21 +113,26 @@ void FindPointsGSLIB::InterpolateLocal1( const Vector &field_in,
auto plcf = DEV.lagcoeff_sol.ReadWrite(use_dev);
switch (dof1Dsol)
{
case 2: return InterpolateLocal1DKernel<2>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 3: return InterpolateLocal1DKernel<3>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 4: return InterpolateLocal1DKernel<4>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 5: return InterpolateLocal1DKernel<5>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
default: return InterpolateLocal1DKernel(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf, dof1Dsol);
case 2:
InterpolateLocal1DKernel<2>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 3:
InterpolateLocal1DKernel<3>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 4:
InterpolateLocal1DKernel<4>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 5:
InterpolateLocal1DKernel<5>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
default:
InterpolateLocal1DKernel(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf, dof1Dsol);
break;
}
}
#undef CODE_INTERNAL
+22 -27
View File
@@ -11,6 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -32,18 +33,7 @@ namespace mfem
#define CODE_BORDER 1
#define CODE_NOT_FOUND 2
static MFEM_HOST_DEVICE void lagrange_eval(double *p0, double x,
int i, int p_Nq,
double *z, double *lagrangeCoeff)
{
double p_i = (1 << (p_Nq - 1));
for (int j = 0; j < p_Nq; ++j)
{
double d_j = x - z[j];
p_i *= j == i ? 1 : d_j;
}
p0[i] = lagrangeCoeff[i] * p_i;
}
using gslib::lagrange_eval;
template<int T_D1D = 0>
static void InterpolateLocal2DKernel(const double *const gf_in,
@@ -132,21 +122,26 @@ void FindPointsGSLIB::InterpolateLocal2(const Vector &field_in,
auto plcf = DEV.lagcoeff_sol.ReadWrite(use_dev);
switch (dof1Dsol)
{
case 2: return InterpolateLocal2DKernel<2>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 3: return InterpolateLocal2DKernel<3>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 4: return InterpolateLocal2DKernel<4>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 5: return InterpolateLocal2DKernel<5>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
default: return InterpolateLocal2DKernel(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf, dof1Dsol);
case 2:
InterpolateLocal2DKernel<2>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 3:
InterpolateLocal2DKernel<3>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 4:
InterpolateLocal2DKernel<4>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 5:
InterpolateLocal2DKernel<5>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
default:
InterpolateLocal2DKernel(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf, dof1Dsol);
break;
}
}
+22 -27
View File
@@ -11,6 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -32,18 +33,7 @@ namespace mfem
#define CODE_BORDER 1
#define CODE_NOT_FOUND 2
static MFEM_HOST_DEVICE void lagrange_eval(double *p0, double x,
int i, int p_Nq,
double *z, double *lagrangeCoeff)
{
double p_i = (1 << (p_Nq - 1));
for (int j = 0; j < p_Nq; ++j)
{
double d_j = x - z[j];
p_i *= j == i ? 1 : d_j;
}
p0[i] = lagrangeCoeff[i] * p_i;
}
using gslib::lagrange_eval;
template<int T_D1D = 0>
static void InterpolateLocal3DKernel(const double *const gf_in,
@@ -135,21 +125,26 @@ void FindPointsGSLIB::InterpolateLocal3(const Vector &field_in,
auto plcf = DEV.lagcoeff_sol.ReadWrite(use_dev);
switch (dof1Dsol)
{
case 2: return InterpolateLocal3DKernel<2>(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 3: return InterpolateLocal3DKernel<3>(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 4: return InterpolateLocal3DKernel<4>(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 5: return InterpolateLocal3DKernel<5>(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf);
default: return InterpolateLocal3DKernel(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf, dof1Dsol);
case 2:
InterpolateLocal3DKernel<2>(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 3:
InterpolateLocal3DKernel<3>(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 4:
InterpolateLocal3DKernel<4>(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 5:
InterpolateLocal3DKernel<5>(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
default:
InterpolateLocal3DKernel(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf, dof1Dsol);
break;
}
}
@@ -19,6 +19,8 @@
#include "../../linalg/vector.hpp"
#include "../bilininteg.hpp"
#include "bilininteg_diffusion_pa_simplices.hpp"
namespace mfem
{
+2
View File
@@ -19,6 +19,8 @@
#include "../../linalg/vector.hpp"
#include "../bilininteg.hpp"
#include "bilininteg_mass_pa_simplices.hpp"
namespace mfem
{
+500
View File
@@ -307,6 +307,506 @@ DomainLFIntegrator::AssembleKernels::Kernel()
MFEM_ABORT("");
}
template <int T_D1D = 0, int T_Q1D = 0>
static void HdivDLFAssemble2D(const int ne, const Array<int> &markers,
const Vector &jac, const Array<real_t> &weights,
const Array<real_t> &testBO,
const Array<real_t> &testBC, const Vector &coeff,
Vector &y, const int d, const int q)
{
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HDIV_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HDIV_MAX_Q1D,
"Problem size too large.");
MFEM_VERIFY(y.Size() == 2 * (d - 1) * d * ne, "");
constexpr int vdim = 2;
const auto F = coeff.Read();
const auto M = markers.Read();
const auto BO = Reshape(testBO.Read(), q, d-1);
const auto BC = Reshape(testBC.Read(), q, d);
const auto J = Reshape(jac.Read(), q, q, vdim, vdim, ne);
const auto W = Reshape(weights.Read(), q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1) : Reshape(F,vdim,q,q,ne);
auto Y = y.ReadWrite();
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE (int e)
{
constexpr int vdim = 2;
if (M[e] == 0) { return; } // ignore
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HDIV_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HDIV_MAX_D1D;
MFEM_SHARED real_t sBot[Q*D];
MFEM_SHARED real_t sBct[Q*D];
MFEM_SHARED real_t sQQ[vdim*Q*Q];
MFEM_SHARED real_t sQD[vdim*Q*D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d-1, q);
kernels::internal::LoadB<D,Q>(d-1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D,Q>(d, q, BC, sBct);
const DeviceCube QQ(sQQ, q, q, vdim);
const DeviceCube QD(sQD, q, d, vdim);
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const real_t cst_val_0 = C(0,0,0,0);
const real_t cst_val_1 = C(1,0,0,0);
MFEM_FOREACH_THREAD(y,y,q)
{
MFEM_FOREACH_THREAD(x,x,q)
{
const real_t J0 = J(x,y,0,vd,e);
const real_t J1 = J(x,y,1,vd,e);
const real_t C0 = cst ? cst_val_0 : C(0,x,y,e);
const real_t C1 = cst ? cst_val_1 : C(1,x,y,e);
QQ(x,y,vd) = W(x,y)*(J0*C0 + J1*C1);
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
DeviceMatrix Btx = (vd == 0) ? Bct : Bot;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t qd = 0.0;
for (int qx = 0; qx < q; ++qx)
{
qd += QQ(qx,qy,vd) * Btx(dx,qx);
}
QD(dx,qy,vd) = qd;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
DeviceMatrix Bty = (vd == 1) ? Bct : Bot;
DeviceTensor<4> Yxy(Y, nx, ny, vdim, ne);
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t dd = 0.0;
for (int qy = 0; qy < q; ++qy)
{
dd += QD(dx,qy,vd) * Bty(dy,qy);
}
Yxy(dx,dy,vd,e) += dd;
}
}
}
MFEM_SYNC_THREAD;
});
}
template <int T_D1D = 0, int T_Q1D = 0>
static void HdivDLFAssemble3D(const int ne, const Array<int> &markers,
const Vector &jac, const Array<real_t> &weights,
const Array<real_t> &testBO,
const Array<real_t> &testBC, const Vector &coeff,
Vector &y, const int d, const int q)
{
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HDIV_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HDIV_MAX_Q1D,
"Problem size too large.");
MFEM_VERIFY(y.Size() == 3 * (d - 1) * (d - 1) * d * ne, "y wrong length");
constexpr int vdim = 3;
const auto F = coeff.Read();
const auto M = markers.Read();
const auto BO = Reshape(testBO.Read(), q, d-1);
const auto BC = Reshape(testBC.Read(), q, d);
const auto J = Reshape(jac.Read(), q, q, q, vdim, vdim, ne);
const auto W = Reshape(weights.Read(), q, q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1,1) : Reshape(F,vdim,q,q,q,ne);
auto Y = y.ReadWrite();
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE (int e)
{
constexpr int vdim = 3;
if (M[e] == 0) { return; } // ignore
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HDIV_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HDIV_MAX_D1D;
MFEM_SHARED real_t sBot[Q*D];
MFEM_SHARED real_t sBct[Q*D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d-1, q);
kernels::internal::LoadB<D,Q>(d-1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D,Q>(d, q, BC, sBct);
MFEM_SHARED real_t sm0[vdim*Q*Q*Q];
MFEM_SHARED real_t sm1[vdim*Q*Q*Q];
DeviceTensor<4> QQQ(sm1, q, q, q, vdim);
DeviceTensor<4> DQQ(sm0, d, q, q, vdim);
DeviceTensor<4> DDQ(sm1, d, d, q, vdim);
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const real_t cst_val_0 = C(0,0,0,0,0);
const real_t cst_val_1 = C(1,0,0,0,0);
const real_t cst_val_2 = C(2,0,0,0,0);
MFEM_FOREACH_THREAD(y,y,q)
{
MFEM_FOREACH_THREAD(x,x,q)
{
for (int z = 0; z < q; ++z)
{
const real_t J0 = J(x,y,z,0,vd,e);
const real_t J1 = J(x,y,z,1,vd,e);
const real_t J2 = J(x,y,z,2,vd,e);
const real_t C0 = cst ? cst_val_0 : C(0,x,y,z,e);
const real_t C1 = cst ? cst_val_1 : C(1,x,y,z,e);
const real_t C2 = cst ? cst_val_2 : C(2,x,y,z,e);
QQQ(x,y,z,vd) = W(x,y,z)*(J0*C0 + J1*C1 + J2*C2);
}
}
}
}
MFEM_SYNC_THREAD;
// Apply Bt operator
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
DeviceMatrix Btx = (vd == 0) ? Bct : Bot;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { u[qz] = 0.0; }
MFEM_UNROLL(Q)
for (int qx = 0; qx < q; ++qx)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += QQQ(qx,qy,qz,vd) * Btx(dx,qx);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { DQQ(dx,qy,qz,vd) = u[qz]; }
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
DeviceMatrix Bty = (vd == 1) ? Bct : Bot;
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { u[qz] = 0.0; }
MFEM_UNROLL(Q)
for (int qy = 0; qy < q; ++qy)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += DQQ(dx,qy,qz,vd) * Bty(dy,qy);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { DDQ(dx,dy,qz,vd) = u[qz]; }
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
const int nz = (vd == 2) ? d : d-1;
DeviceTensor<5> Yxyz(Y, nx, ny, nz, vdim, ne);
DeviceMatrix Btz = (vd == 2) ? Bct : Bot;
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[D];
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz) { u[dz] = 0.0; }
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
u[dz] += DDQ(dx,dy,qz,vd) * Btz(dz,qz);
}
}
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz) { Yxyz(dx,dy,dz,vd,e) += u[dz]; }
}
}
}
MFEM_SYNC_THREAD;
});
}
/// @param ne number of elements
/// @param markers array where entry markers[e] == 0 to skip assembly over
/// element e element
/// @param jac Spatial Jacobians evaluated at all quadrature points
/// @param weights 1D quadrature weights
/// @param testBO 1D open basis test functions
/// @param testBC 1D closed basis test functions
/// @param coeff coefficient values evaluated at quadrature points, possibly
/// compressed.
/// @param d number of 1D closed dofs
/// @param q number of 1D quadrature points
/// @tparam T_D1D maximum number of dofs along any direction, or 0
/// @tparam T_Q1D maximum number of quadrature points along any direction, or 0
template <int T_D1D = 0, int T_Q1D = 0>
static void HcurlDLFAssemble3D(const int ne, const Array<int> &markers,
const Vector &jac, const Array<real_t> &weights,
const Array<real_t> &testBO,
const Array<real_t> &testBC, const Vector &coeff,
Vector &y, const int d, const int q)
{
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HCURL_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HCURL_MAX_Q1D,
"Problem size too large.");
MFEM_VERIFY(y.Size() == 3 * (d - 1) * d * d * ne, "y wrong length");
constexpr int vdim = 3;
const auto F = coeff.Read();
const auto M = markers.Read();
const auto BO = Reshape(testBO.Read(), q, d-1);
const auto BC = Reshape(testBC.Read(), q, d);
const auto J = Reshape(jac.Read(), q, q, q, vdim, vdim, ne);
const auto W = Reshape(weights.Read(), q, q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1,1) : Reshape(F,vdim,q,q,q,ne);
auto Y = y.ReadWrite();
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE(int e)
{
if (M[e] == 0)
{
// ignore
return;
}
constexpr int vdim = 3;
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HCURL_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HCURL_MAX_D1D;
MFEM_SHARED real_t sBot[Q * D];
MFEM_SHARED real_t sBct[Q * D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d - 1, q);
kernels::internal::LoadB<D, Q>(d - 1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D, Q>(d, q, BC, sBct);
MFEM_SHARED real_t sm0[vdim * Q * Q * Q];
MFEM_SHARED real_t sm1[vdim * Q * Q * Q];
DeviceTensor<4> QQQ(sm1, q, q, q, vdim);
DeviceTensor<4> DQQ(sm0, d, q, q, vdim);
DeviceTensor<4> DDQ(sm1, d, d, q, vdim);
const real_t cst_val_0 = C(0, 0, 0, 0, 0);
const real_t cst_val_1 = C(1, 0, 0, 0, 0);
const real_t cst_val_2 = C(2, 0, 0, 0, 0);
MFEM_FOREACH_THREAD(vd, z, vdim)
{
MFEM_FOREACH_THREAD(y, y, q)
{
MFEM_FOREACH_THREAD(x, x, q)
{
for (int z = 0; z < q; ++z)
{
real_t curr[3];
curr[0] = cst ? cst_val_0 : C(0, x, y, z, e);
curr[1] = cst ? cst_val_1 : C(1, x, y, z, e);
curr[2] = cst ? cst_val_2 : C(2, x, y, z, e);
const real_t J11 = J(x, y, z, 0, 0, e);
const real_t J21 = J(x, y, z, 1, 0, e);
const real_t J31 = J(x, y, z, 2, 0, e);
const real_t J12 = J(x, y, z, 0, 1, e);
const real_t J22 = J(x, y, z, 1, 1, e);
const real_t J32 = J(x, y, z, 2, 1, e);
const real_t J13 = J(x, y, z, 0, 2, e);
const real_t J23 = J(x, y, z, 1, 2, e);
const real_t J33 = J(x, y, z, 2, 2, e);
// adj(J)
const real_t A11 = (J22 * J33) - (J23 * J32);
const real_t A12 = (J32 * J13) - (J12 * J33);
const real_t A13 = (J12 * J23) - (J22 * J13);
const real_t A21 = (J31 * J23) - (J21 * J33);
const real_t A22 = (J11 * J33) - (J13 * J31);
const real_t A23 = (J21 * J13) - (J11 * J23);
const real_t A31 = (J21 * J32) - (J31 * J22);
const real_t A32 = (J31 * J12) - (J11 * J32);
const real_t A33 = (J11 * J22) - (J12 * J21);
const real_t A[9] = {A11, A12, A13, A21, A22,
A23, A31, A32, A33
};
QQQ(x, y, z, vd) = W(x, y, z) * (A[vd * vdim] * curr[0] +
A[vd * vdim + 1] * curr[1] +
A[vd * vdim + 2] * curr[2]);
}
}
}
}
MFEM_SYNC_THREAD;
// Apply Bt operator
MFEM_FOREACH_THREAD(vd, z, vdim)
{
const int nx = (vd == 0) ? d - 1 : d;
DeviceMatrix Btx = (vd == 0) ? Bot : Bct;
MFEM_FOREACH_THREAD(qy, y, q)
{
MFEM_FOREACH_THREAD(dx, x, nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] = 0.0;
}
MFEM_UNROLL(Q)
for (int qx = 0; qx < q; ++qx)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += QQQ(qx, qy, qz, vd) * Btx(dx, qx);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
DQQ(dx, qy, qz, vd) = u[qz];
}
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd, z, vdim)
{
const int nx = (vd == 0) ? d - 1 : d;
const int ny = (vd == 1) ? d - 1 : d;
DeviceMatrix Bty = (vd == 1) ? Bot : Bct;
MFEM_FOREACH_THREAD(dy, y, ny)
{
MFEM_FOREACH_THREAD(dx, x, nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] = 0.0;
}
MFEM_UNROLL(Q)
for (int qy = 0; qy < q; ++qy)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += DQQ(dx, qy, qz, vd) * Bty(dy, qy);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
DDQ(dx, dy, qz, vd) = u[qz];
}
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd, z, vdim)
{
const int nx = (vd == 0) ? d - 1 : d;
const int ny = (vd == 1) ? d - 1 : d;
const int nz = (vd == 2) ? d - 1 : d;
DeviceTensor<5> Yxyz(Y, nx, ny, nz, vdim, ne);
DeviceMatrix Btz = (vd == 2) ? Bot : Bct;
MFEM_FOREACH_THREAD(dy, y, ny)
{
MFEM_FOREACH_THREAD(dx, x, nx)
{
real_t u[D];
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
u[dz] = 0.0;
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
u[dz] += DDQ(dx, dy, qz, vd) * Btz(dz, qz);
}
}
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
Yxyz(dx, dy, dz, vd, e) += u[dz];
}
}
}
}
MFEM_SYNC_THREAD;
});
}
template <FiniteElement::DerivType TestType, int DIM, int TEST_D1D, int Q1D>
VectorFEDomainLFIntegrator::AssembleKernelType
VectorFEDomainLFIntegrator::AssembleKernels::Kernel()
{
if constexpr (TestType == FiniteElement::DIV)
{
if constexpr (DIM == 2)
{
return HdivDLFAssemble2D<TEST_D1D, Q1D>;
}
if constexpr (DIM == 3)
{
return HdivDLFAssemble3D<TEST_D1D, Q1D>;
}
}
if constexpr (TestType == FiniteElement::CURL)
{
if constexpr (DIM == 3)
{
return HcurlDLFAssemble3D<TEST_D1D, Q1D>;
}
}
MFEM_ABORT("");
}
/// \endcond DO_NOT_DOCUMENT
} // namespace mfem
+68 -301
View File
@@ -13,317 +13,76 @@
#include "../../fem/kernels.hpp"
#include "../fem.hpp"
#include "lininteg_domain_kernels.hpp"
namespace mfem
{
template<int T_D1D = 0, int T_Q1D = 0>
static void HdivDLFAssemble2D(
const int ne, const int d, const int q, const int *markers, const real_t *bo,
const real_t *bc, const real_t *j, const real_t *weights,
const Vector &coeff, real_t *y)
VectorFEDomainLFIntegrator::Kernels::Kernels()
{
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HDIV_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HDIV_MAX_Q1D,
"Problem size too large.");
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 1, 1>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 2, 2>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 3, 3>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 4, 4>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 5, 5>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 6, 6>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 7, 7>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 8, 8>();
static constexpr int vdim = 2;
const auto F = coeff.Read();
const auto M = Reshape(markers, ne);
const auto BO = Reshape(bo, q, d-1);
const auto BC = Reshape(bc, q, d);
const auto J = Reshape(j, q, q, vdim, vdim, ne);
const auto W = Reshape(weights, q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1) : Reshape(F,vdim,q,q,ne);
auto Y = Reshape(y, 2*(d-1)*d, ne);
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 1, 1>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 2, 2>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 3, 3>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 4, 4>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 5, 5>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 6, 6>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 7, 7>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 8, 8>();
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE (int e)
{
if (M(e) == 0) { return; } // ignore
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 1, 1>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 2, 2>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 3, 3>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 4, 4>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 5, 5>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 6, 6>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 7, 7>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 8, 8>();
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HDIV_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HDIV_MAX_D1D;
MFEM_SHARED real_t sBot[Q*D];
MFEM_SHARED real_t sBct[Q*D];
MFEM_SHARED real_t sQQ[vdim*Q*Q];
MFEM_SHARED real_t sQD[vdim*Q*D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d-1, q);
kernels::internal::LoadB<D,Q>(d-1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D,Q>(d, q, BC, sBct);
const DeviceCube QQ(sQQ, q, q, vdim);
const DeviceCube QD(sQD, q, d, vdim);
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const real_t cst_val_0 = C(0,0,0,0);
const real_t cst_val_1 = C(1,0,0,0);
MFEM_FOREACH_THREAD(y,y,q)
{
MFEM_FOREACH_THREAD(x,x,q)
{
const real_t J0 = J(x,y,0,vd,e);
const real_t J1 = J(x,y,1,vd,e);
const real_t C0 = cst ? cst_val_0 : C(0,x,y,e);
const real_t C1 = cst ? cst_val_1 : C(1,x,y,e);
QQ(x,y,vd) = W(x,y)*(J0*C0 + J1*C1);
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
DeviceMatrix Btx = (vd == 0) ? Bct : Bot;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t qd = 0.0;
for (int qx = 0; qx < q; ++qx)
{
qd += QQ(qx,qy,vd) * Btx(dx,qx);
}
QD(dx,qy,vd) = qd;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
DeviceMatrix Bty = (vd == 1) ? Bct : Bot;
DeviceTensor<4> Yxy(Y, nx, ny, vdim, ne);
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t dd = 0.0;
for (int qy = 0; qy < q; ++qy)
{
dd += QD(dx,qy,vd) * Bty(dy,qy);
}
Yxy(dx,dy,vd,e) += dd;
}
}
}
MFEM_SYNC_THREAD;
});
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 1, 2>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 2, 3>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 3, 4>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 4, 5>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 5, 6>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 6, 7>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 7, 8>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 8, 9>();
}
template<int T_D1D = 0, int T_Q1D = 0>
static void HdivDLFAssemble3D(
const int ne, const int d, const int q, const int *markers, const real_t *bo,
const real_t *bc, const real_t *j, const real_t *weights,
const Vector &coeff, real_t *y)
/// \cond DO_NOT_DOCUMENT
VectorFEDomainLFIntegrator::AssembleKernelType
VectorFEDomainLFIntegrator::AssembleKernels::Fallback(
FiniteElement::DerivType TestType, int DIM, int, int)
{
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HDIV_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HDIV_MAX_Q1D,
"Problem size too large.");
static constexpr int vdim = 3;
const auto F = coeff.Read();
const auto M = Reshape(markers, ne);
const auto BO = Reshape(bo, q, d-1);
const auto BC = Reshape(bc, q, d);
const auto J = Reshape(j, q, q, q, vdim, vdim, ne);
const auto W = Reshape(weights, q, q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1,1) : Reshape(F,vdim,q,q,q,ne);
auto Y = Reshape(y, 2*(d-1)*(d-1)*d, ne);
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE (int e)
if (TestType == FiniteElement::DIV)
{
if (M(e) == 0) { return; } // ignore
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HDIV_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HDIV_MAX_D1D;
MFEM_SHARED real_t sBot[Q*D];
MFEM_SHARED real_t sBct[Q*D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d-1, q);
kernels::internal::LoadB<D,Q>(d-1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D,Q>(d, q, BC, sBct);
MFEM_SHARED real_t sm0[vdim*Q*Q*Q];
MFEM_SHARED real_t sm1[vdim*Q*Q*Q];
DeviceTensor<4> QQQ(sm1, q, q, q, vdim);
DeviceTensor<4> DQQ(sm0, d, q, q, vdim);
DeviceTensor<4> DDQ(sm1, d, d, q, vdim);
MFEM_FOREACH_THREAD(vd,z,vdim)
if (DIM == 2)
{
const real_t cst_val_0 = C(0,0,0,0,0);
const real_t cst_val_1 = C(1,0,0,0,0);
const real_t cst_val_2 = C(2,0,0,0,0);
MFEM_FOREACH_THREAD(y,y,q)
{
MFEM_FOREACH_THREAD(x,x,q)
{
for (int z = 0; z < q; ++z)
{
const real_t J0 = J(x,y,z,0,vd,e);
const real_t J1 = J(x,y,z,1,vd,e);
const real_t J2 = J(x,y,z,2,vd,e);
const real_t C0 = cst ? cst_val_0 : C(0,x,y,z,e);
const real_t C1 = cst ? cst_val_1 : C(1,x,y,z,e);
const real_t C2 = cst ? cst_val_2 : C(2,x,y,z,e);
QQQ(x,y,z,vd) = W(x,y,z)*(J0*C0 + J1*C1 + J2*C2);
}
}
}
return HdivDLFAssemble2D<0, 0>;
}
MFEM_SYNC_THREAD;
// Apply Bt operator
MFEM_FOREACH_THREAD(vd,z,vdim)
if (DIM == 3)
{
const int nx = (vd == 0) ? d : d-1;
DeviceMatrix Btx = (vd == 0) ? Bct : Bot;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { u[qz] = 0.0; }
MFEM_UNROLL(Q)
for (int qx = 0; qx < q; ++qx)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += QQQ(qx,qy,qz,vd) * Btx(dx,qx);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { DQQ(dx,qy,qz,vd) = u[qz]; }
}
}
return HdivDLFAssemble3D<0, 0>;
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
DeviceMatrix Bty = (vd == 1) ? Bct : Bot;
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { u[qz] = 0.0; }
MFEM_UNROLL(Q)
for (int qy = 0; qy < q; ++qy)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += DQQ(dx,qy,qz,vd) * Bty(dy,qy);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { DDQ(dx,dy,qz,vd) = u[qz]; }
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
const int nz = (vd == 2) ? d : d-1;
DeviceTensor<5> Yxyz(Y, nx, ny, nz, vdim, ne);
DeviceMatrix Btz = (vd == 2) ? Bct : Bot;
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[D];
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz) { u[dz] = 0.0; }
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
u[dz] += DDQ(dx,dy,qz,vd) * Btz(dz,qz);
}
}
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz) { Yxyz(dx,dy,dz,vd,e) += u[dz]; }
}
}
}
MFEM_SYNC_THREAD;
});
}
static void HdivDLFAssemble(const FiniteElementSpace &fes,
const IntegrationRule *ir,
const Array<int> &markers,
const Vector &coeff,
Vector &y)
{
Mesh &mesh = *fes.GetMesh();
const int dim = mesh.Dimension();
const FiniteElement *el = fes.GetTypicalFE();
const auto *vel = dynamic_cast<const VectorTensorFiniteElement *>(el);
MFEM_VERIFY(vel != nullptr, "Must be VectorTensorFiniteElement");
const MemoryType mt = Device::GetDeviceMemoryType();
const DofToQuad &maps_o = vel->GetDofToQuadOpen(*ir, DofToQuad::TENSOR);
const DofToQuad &maps_c = vel->GetDofToQuad(*ir, DofToQuad::TENSOR);
const int d = maps_c.ndof, q = maps_c.nqpt;
constexpr int flags = GeometricFactors::JACOBIANS;
const GeometricFactors *geom = mesh.GetGeometricFactors(*ir, flags, mt);
decltype(&HdivDLFAssemble2D<>) ker =
dim == 2 ? HdivDLFAssemble2D<> : HdivDLFAssemble3D<>;
if (dim==2)
{
if (d==1 && q==1) { ker=HdivDLFAssemble2D<1,1>; }
if (d==2 && q==2) { ker=HdivDLFAssemble2D<2,2>; }
if (d==3 && q==3) { ker=HdivDLFAssemble2D<3,3>; }
if (d==4 && q==4) { ker=HdivDLFAssemble2D<4,4>; }
if (d==5 && q==5) { ker=HdivDLFAssemble2D<5,5>; }
if (d==6 && q==6) { ker=HdivDLFAssemble2D<6,6>; }
if (d==7 && q==7) { ker=HdivDLFAssemble2D<7,7>; }
if (d==8 && q==8) { ker=HdivDLFAssemble2D<8,8>; }
}
if (dim==3)
else if (TestType == FiniteElement::CURL)
{
if (d==2 && q==2) { ker=HdivDLFAssemble3D<2,2>; }
if (d==3 && q==3) { ker=HdivDLFAssemble3D<3,3>; }
if (d==4 && q==4) { ker=HdivDLFAssemble3D<4,4>; }
if (d==5 && q==5) { ker=HdivDLFAssemble3D<5,5>; }
if (d==6 && q==6) { ker=HdivDLFAssemble3D<6,6>; }
if (d==7 && q==7) { ker=HdivDLFAssemble3D<7,7>; }
if (d==8 && q==8) { ker=HdivDLFAssemble3D<8,8>; }
if (DIM == 3)
{
return HcurlDLFAssemble3D<0, 0>;
}
}
MFEM_VERIFY(ker, "No kernel ndof " << d << " nqpt " << q);
const int ne = mesh.GetNE();
const int *M = markers.Read();
const real_t *Bo = maps_o.B.Read();
const real_t *Bc = maps_c.B.Read();
const real_t *J = geom->J.Read();
const real_t *W = ir->GetWeights().Read();
real_t *Y = y.ReadWrite();
ker(ne, d, q, M, Bo, Bc, J, W, coeff, Y);
MFEM_ABORT("");
}
/// \endcond DO_NOT_DOCUMENT
void VectorFEDomainLFIntegrator::AssembleDevice(const FiniteElementSpace &fes,
const Array<int> &markers,
@@ -337,15 +96,23 @@ void VectorFEDomainLFIntegrator::AssembleDevice(const FiniteElementSpace &fes,
QuadratureSpace qs(*fes.GetMesh(), *ir);
CoefficientVector coeff(QF, qs, CoefficientStorage::COMPRESSED);
const int fe_type = fe.GetDerivType();
if (fe_type == FiniteElement::DIV)
{
HdivDLFAssemble(fes, ir, markers, coeff, b);
}
else
{
MFEM_ABORT("Not implemented.");
}
const FiniteElement::DerivType fe_type =
static_cast<FiniteElement::DerivType>(fe.GetDerivType());
Mesh &mesh = *fes.GetMesh();
const int dim = mesh.Dimension();
const FiniteElement *el = fes.GetTypicalFE();
const auto *vel = dynamic_cast<const VectorTensorFiniteElement *>(el);
MFEM_VERIFY(vel != nullptr, "Must be VectorTensorFiniteElement");
const MemoryType mt = Device::GetDeviceMemoryType();
const DofToQuad &maps_o = vel->GetDofToQuadOpen(*ir, DofToQuad::TENSOR);
const DofToQuad &maps_c = vel->GetDofToQuad(*ir, DofToQuad::TENSOR);
const int d = maps_c.ndof, q = maps_c.nqpt;
constexpr int flags = GeometricFactors::JACOBIANS;
const GeometricFactors *geom = mesh.GetGeometricFactors(*ir, flags, mt);
AssembleKernels::Run(fe_type, dim, d, q, mesh.GetNE(), markers, geom->J,
ir->GetWeights(), maps_o.B, maps_c.B, coeff, b, d, q);
}
} // namespace mfem
+7
View File
@@ -471,6 +471,13 @@ void VectorBoundaryLFIntegrator::AssembleRHSElementVect(
}
}
VectorFEDomainLFIntegrator::VectorFEDomainLFIntegrator(
VectorCoefficient &F, const IntegrationRule *ir)
: DeltaLFIntegrator(F, ir), QF(F)
{
static Kernels kernels{};
}
void VectorFEDomainLFIntegrator::AssembleRHSElementVect(
const FiniteElement &el, ElementTransformation &Tr, Vector &elvect)
{
+36 -2
View File
@@ -369,8 +369,8 @@ private:
Vector vec;
public:
VectorFEDomainLFIntegrator(VectorCoefficient &F)
: DeltaLFIntegrator(F), QF(F) { }
VectorFEDomainLFIntegrator(VectorCoefficient &F,
const IntegrationRule *ir = nullptr);
void AssembleRHSElementVect(const FiniteElement &el,
ElementTransformation &Tr,
@@ -387,6 +387,40 @@ public:
Vector &b) override;
using LinearFormIntegrator::AssembleRHSElementVect;
/// @param ne number of elements
/// @param markers array where entry markers[e] == 0 to skip assembly over
/// element e element
/// @param jac Spatial Jacobians evaluated at all quadrature points
/// @param weights 1D quadrature weights
/// @param testBO 1D open basis test functions
/// @param testBC 1D closed basis test functions
/// @param coeff coefficient values evaluated at quadrature points, possibly
/// compressed.
/// @param d number of 1D closed dofs
/// @param q number of 1D quadrature points
using AssembleKernelType = void (*)(const int NE, const Array<int> &markers,
const Vector &jac,
const Array<real_t> &weights,
const Array<real_t> &testBO,
const Array<real_t> &testBC,
const Vector &coeff, Vector &y,
const int testd1d, const int q1d);
/// parameters: test_fetype, ndims, test_d1d, q1d
MFEM_REGISTER_KERNELS(AssembleKernels, AssembleKernelType,
(FiniteElement::DerivType, int, int, int));
struct Kernels
{
Kernels();
};
template <FiniteElement::DerivType TestType, int DIM, int TEST_D1D, int Q1D>
static void AddSpecialization()
{
AssembleKernels::Specialization<TestType, DIM, TEST_D1D, Q1D>::Add();
}
};
/// $ (Q, \mathrm{curl}(v))_{\Omega} $ for Nedelec Elements
+354 -63
View File
@@ -10,6 +10,7 @@
// CONTRIBUTING.md for details.
#include "particleset.hpp"
#include "../general/forall.hpp"
#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
@@ -225,6 +226,7 @@ void ParticleSet::AddParticles(const Array<IDType> &new_ids,
}
}
// Add new ids
ids.HostReadWrite();
ids.Append(new_ids);
// Update data
@@ -244,6 +246,102 @@ void ParticleSet::AddParticles(const Array<IDType> &new_ids,
#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
/// \cond DO_NOT_DOCUMENT
// Static helper: gather selected particle-vector entries into a compact buffer.
// nvcc does not allow extended host/device lambdas in non-public members.
static void GatherParticleVectorDevice(const ParticleVector &pv,
const Array<int> &send_idxs,
Vector &send_data,
int nsend)
{
const int vdim = pv.GetVDim();
const int ordering = pv.GetOrdering();
const int num_particles = pv.GetNumParticles();
const MemoryClass device_mc = Device::GetDeviceMemoryClass();
send_data.SetSize(nsend*vdim);
real_t *d_send_data =
send_data.GetMemory().Write(device_mc, send_data.Size());
const real_t *d_src = pv.GetMemory().Read(device_mc, pv.Size());
const int *d_send_idxs = send_idxs.GetMemory().Read(device_mc, nsend);
mfem::forall(nsend, [=] MFEM_HOST_DEVICE (int i)
{
const int p = d_send_idxs[i];
const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
const int stride = (ordering == Ordering::byVDIM) ? 1 : num_particles;
for (int c = 0; c < vdim; c++)
{
d_send_data[i*vdim + c] = d_src[offset + c*stride];
}
});
}
// Static helper: gather selected tag values into a compact buffer.
// nvcc does not allow extended host/device lambdas in non-public members.
static void GatherParticleTagsDevice(const Array<int> &tag,
const Array<int> &send_idxs,
Array<int> &send_tag,
int nsend)
{
const MemoryClass device_mc = Device::GetDeviceMemoryClass();
send_tag.SetSize(nsend);
int *d_send_tag = send_tag.GetMemory().Write(device_mc, nsend);
const int *d_tag = tag.GetMemory().Read(device_mc, tag.Size());
const int *d_send_idxs = send_idxs.GetMemory().Read(device_mc, nsend);
mfem::forall(nsend, [=] MFEM_HOST_DEVICE (int i)
{
d_send_tag[i] = d_tag[d_send_idxs[i]];
});
}
// Static helper: scatter compact particle-vector entries to particle storage.
// nvcc does not allow extended host/device lambdas in non-public members.
static void ScatterParticleVectorDevice(ParticleVector &pv,
const Vector &recv_data,
const Array<int> &recv_locs,
int nrecv)
{
const int vdim = pv.GetVDim();
const int ordering = pv.GetOrdering();
const int num_particles = pv.GetNumParticles();
const MemoryClass device_mc = Device::GetDeviceMemoryClass();
const real_t *d_recv_data =
recv_data.GetMemory().Read(device_mc, recv_data.Size());
const int *d_recv_locs = recv_locs.GetMemory().Read(device_mc, nrecv);
real_t *d_dst = pv.GetMemory().ReadWrite(device_mc, pv.Size());
mfem::forall(nrecv, [=] MFEM_HOST_DEVICE (int i)
{
const int p = d_recv_locs[i];
const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
const int stride = (ordering == Ordering::byVDIM) ? 1 : num_particles;
for (int c = 0; c < vdim; c++)
{
d_dst[offset + c*stride] = d_recv_data[i*vdim + c];
}
});
}
// Static helper: scatter compact tag values to particle storage.
// nvcc does not allow extended host/device lambdas in non-public members.
static void ScatterParticleTagsDevice(Array<int> &tag,
const Array<int> &recv_tag,
const Array<int> &recv_locs,
int nrecv)
{
const MemoryClass device_mc = Device::GetDeviceMemoryClass();
const int *d_recv_tag = recv_tag.GetMemory().Read(device_mc, nrecv);
const int *d_recv_locs = recv_locs.GetMemory().Read(device_mc, nrecv);
int *d_tag = tag.GetMemory().ReadWrite(device_mc, tag.Size());
mfem::forall(nrecv, [=] MFEM_HOST_DEVICE (int i)
{
d_tag[d_recv_locs[i]] = d_recv_tag[i];
});
}
template<size_t NBytes>
void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
const Array<int> &send_idxs,
@@ -266,37 +364,108 @@ void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
array_init(parr_t, &gsl_arr, send_idxs.Size());
pdata_arr = (parr_t*) gsl_arr.ptr;
int nparticles = pset.GetNParticles();
int nsend = send_idxs.Size();
gsl_arr.n = send_idxs.Size();
const int *h_send_idxs_initial = send_idxs.HostRead();
const IDType *h_ids = pset.GetIDs().HostRead();
for (int i = 0; i < send_idxs.Size(); i++)
{
parr_t &pdata = pdata_arr[i];
pdata.id = pset.GetIDs()[send_idxs[i]];
pdata.id = h_ids[h_send_idxs_initial[i]];
}
// Copy particle data directly into pdata
size_t counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
// Pack coords and fields into the GSLIB send buffer. Device-resident data
// is first gathered into a compact device buffer so that only selected
// particles are copied back to host. Host-resident data is packed directly.
int max_vdim = pset.Coords().GetVDim();
for (int f = 0; f < pset.GetNFields(); f++)
{
int f_vdim = pset.Field(f).GetVDim();
if (f_vdim > max_vdim) { max_vdim = f_vdim; }
}
Vector send_data;
Array<int> send_tag;
if (Device::IsEnabled())
{
send_data.SetSize(nsend * max_vdim); // allocate max size over all fields
send_tag.SetSize(nsend);
}
size_t counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
{
const ParticleVector &pv = f == -1 ? pset.Coords() : pset.Field(f);
const int vdim = pv.GetVDim();
const int ordering = pv.GetOrdering();
const int num_particles = pv.GetNumParticles();
const bool use_dev = Device::IsEnabled() && pv.UseDevice();
if (use_dev)
{
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
for (int c = 0; c < pv.GetVDim(); c++)
GatherParticleVectorDevice(pv, send_idxs, send_data, nsend);
const real_t *h_send_data = send_data.HostRead();
for (int i = 0; i < nsend; i++)
{
std::memcpy(pdata.data.data() + counter, &pv(send_idxs[i], c),
sizeof(real_t));
counter += sizeof(real_t);
std::memcpy(pdata_arr[i].data.data() + counter,
h_send_data + i*vdim, vdim * sizeof(real_t));
}
}
else
{
const real_t *h_src = pv.HostRead();
const int *h_send_idxs = send_idxs.HostRead();
for (int i = 0; i < nsend; i++)
{
parr_t &pdata = pdata_arr[i];
const int p = h_send_idxs[i];
const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
const int stride = (ordering == Ordering::byVDIM) ? 1 :
num_particles;
for (int c = 0; c < vdim; c++)
{
std::memcpy(pdata.data.data() + counter + c*sizeof(real_t),
h_src + offset + c*stride, sizeof(real_t));
}
}
}
// Copy tags
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag_arr = pset.Tag(t);
std::memcpy(pdata.data.data() + counter, &tag_arr[send_idxs[i]],
sizeof(int));
counter += sizeof(int);
}
counter += vdim*sizeof(real_t);
}
int nparticles = pset.GetNParticles();
int nsend = send_idxs.Size();
// Pack tags after all real_t data. Each tag uses the same selective
// device gather path when its Array is device-resident.
for (int t = 0; t < pset.GetNTags(); t++)
{
const Array<int> &tag = pset.Tag(t);
const size_t tag_counter = counter + t*sizeof(int);
const bool use_dev = Device::IsEnabled() && tag.UseDevice();
if (use_dev)
{
GatherParticleTagsDevice(tag, send_idxs, send_tag, nsend);
const int *h_send_tag = send_tag.HostRead();
for (int i = 0; i < nsend; i++)
{
std::memcpy(pdata_arr[i].data.data() + tag_counter,
h_send_tag + i, sizeof(int));
}
}
else
{
const int *h_tag = tag.HostRead();
const int *h_send_idxs = send_idxs.HostRead();
for (int i = 0; i < nsend; i++)
{
std::memcpy(pdata_arr[i].data.data() + tag_counter,
h_tag + h_send_idxs[i], sizeof(int));
}
}
}
// Transfer particles
sarray_transfer_ext(parr_t, &gsl_arr, send_ranks.GetData(),
@@ -304,11 +473,20 @@ void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
// Make sure we have enough space for received particles
int nrecv = (int) gsl_arr.n;
Vector recv_data;
Array<int> recv_tag;
if (Device::IsEnabled())
{
recv_data.SetSize(nrecv * max_vdim);
recv_tag.SetSize(nrecv);
}
int ndelete = nsend - nrecv;
if (ndelete > 0)
{
// Remove unneeded particles
auto datap = const_cast<int*>(send_idxs.GetData());
auto datap = const_cast<int*>(send_idxs.HostRead());
Array<int> delete_idxs(datap + nrecv, ndelete);
pset.RemoveParticles(delete_idxs);
}
@@ -319,47 +497,133 @@ void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
pdata_arr = (parr_t*) gsl_arr.ptr;
// Add newly-recvd data directly to active state
// Make a list of new IDs to add
int num_new = nrecv > nsend ? nrecv - nsend : 0;
Array<IDType> new_ids(num_new);
for (int i = 0; i < num_new; i++)
{
new_ids[i] = pdata_arr[nsend + i].id;
}
// Add particles in batch
Array<int> new_indices;
if (num_new > 0)
{
pset.AddParticles(new_ids, &new_indices);
}
// Map each received packet to the local particle slot it updates.
Array<int> recv_locs(nrecv);
int *h_recv_locs = recv_locs.HostWrite();
const int *h_send_idxs_recv = send_idxs.HostRead();
for (int i = 0; i < nrecv; i++)
{
parr_t &pdata = pdata_arr[i];
IDType id = pdata.id;
int new_loc_idx;
if (i < nsend) // update existing particle
{
new_loc_idx = send_idxs[i];
pset.UpdateID(new_loc_idx, id);
h_recv_locs[i] = h_send_idxs_recv[i];
pset.UpdateID(h_recv_locs[i], pdata.id);
}
else
{
// add new particle
Array<int> idx_temp;
pset.AddParticles(Array<IDType>({id}), &idx_temp);
new_loc_idx = idx_temp[0]; // Get index of newly-added particle
h_recv_locs[i] = new_indices[i - nsend];
}
}
size_t counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
// Unpack coords and fields from GSLIB host packets. Device-resident
// destinations use a compact host buffer followed by a device scatter.
size_t recv_counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
const int vdim = pv.GetVDim();
const int ordering = pv.GetOrdering();
const int num_particles = pv.GetNumParticles();
const bool use_dev = Device::IsEnabled() && pv.UseDevice();
if (use_dev)
{
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
for (int c = 0; c < pv.GetVDim(); c++)
recv_data.SetSize(nrecv*vdim);
real_t *h_recv_data = recv_data.HostWrite();
for (int i = 0; i < nrecv; i++)
{
real_t& val = pv(new_loc_idx, c);
std::memcpy(&val, pdata.data.data() + counter, sizeof(real_t));
counter += sizeof(real_t);
std::memcpy(h_recv_data + i*vdim,
pdata_arr[i].data.data() + recv_counter,
vdim*sizeof(real_t));
}
ScatterParticleVectorDevice(pv, recv_data, recv_locs, nrecv);
}
else
{
real_t *h_dst = pv.HostReadWrite();
const int *h_recv_locs_read = recv_locs.HostRead();
for (int i = 0; i < nrecv; i++)
{
parr_t &pdata = pdata_arr[i];
const int p = h_recv_locs_read[i];
const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
const int stride = (ordering == Ordering::byVDIM) ? 1 :
num_particles;
for (int c = 0; c < vdim; c++)
{
std::memcpy(h_dst + offset + c*stride,
pdata.data.data() + recv_counter + c*sizeof(real_t),
sizeof(real_t));
}
}
}
for (int t = 0; t < pset.GetNTags(); t++)
recv_counter += vdim*sizeof(real_t);
}
// Unpack tags after all real_t data, using the same compact scatter path
// for device-resident tag arrays.
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag = pset.Tag(t);
const size_t tag_counter = recv_counter + t*sizeof(int);
const bool use_dev = Device::IsEnabled() && tag.UseDevice();
if (use_dev)
{
Array<int> &tag_arr = pset.Tag(t);
std::memcpy(&tag_arr[new_loc_idx],
pdata.data.data() + counter, sizeof(int));
counter += sizeof(int);
recv_tag.SetSize(nrecv);
int *h_recv_tag = recv_tag.HostWrite();
for (int i = 0; i < nrecv; i++)
{
std::memcpy(h_recv_tag + i,
pdata_arr[i].data.data() + tag_counter, sizeof(int));
}
ScatterParticleTagsDevice(tag, recv_tag, recv_locs, nrecv);
}
else
{
int *h_tag = tag.HostReadWrite();
const int *h_recv_locs_read = recv_locs.HostRead();
for (int i = 0; i < nrecv; i++)
{
std::memcpy(h_tag + h_recv_locs_read[i],
pdata_arr[i].data.data() + tag_counter, sizeof(int));
}
}
}
array_free(&gsl_arr);
// Restore Device validity if needed
for (int f = -1; f < pset.GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
pv.ReadWrite(pv.UseDevice());
}
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag_arr = pset.Tag(t);
if (tag_arr.UseDevice()) { tag_arr.ReadWrite(true); }
}
}
template<size_t NBytes>
@@ -526,11 +790,14 @@ ParticleSet::ParticleSet(int id_stride_, IDType id_counter_, int num_particles,
int dim, Ordering::Type coords_ordering, const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_)
const Array<const char*> &tag_names_,
bool use_device)
: id_stride(id_stride_),
id_counter(id_counter_),
coords(dim, coords_ordering)
{
if (use_device) { coords.UseDevice(true); }
// Initialize fields
for (int f = 0; f < field_vdims.Size(); f++)
{
@@ -580,21 +847,22 @@ bool ParticleSet::IsValidParticle(const Particle &p) const
}
ParticleSet::ParticleSet(int num_particles, int dim,
Ordering::Type coords_ordering)
Ordering::Type coords_ordering,
bool use_device)
: ParticleSet(1, 0, num_particles, dim, coords_ordering, Array<int>(),
Array<Ordering::Type>(), Array<const char*>(), 0,
Array<const char*>())
Array<const char*>(), use_device)
{
}
ParticleSet::ParticleSet(int num_particles, int dim,
const Array<int> &field_vdims, int num_tags,
Ordering::Type all_ordering)
Ordering::Type all_ordering, bool use_device)
: ParticleSet(1, 0, num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
GetEmptyNameArray(field_vdims.Size()), num_tags,
GetEmptyNameArray(num_tags))
GetEmptyNameArray(num_tags), use_device)
{
}
@@ -602,11 +870,11 @@ ParticleSet::ParticleSet(int num_particles, int dim,
const Array<int> &field_vdims, const Array<const
char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
Ordering::Type all_ordering)
Ordering::Type all_ordering, bool use_device)
: ParticleSet(1, 0, num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
field_names_, num_tags,
tag_names_)
tag_names_, use_device)
{
}
@@ -616,9 +884,9 @@ ParticleSet::ParticleSet(int num_particles, int dim,
const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_)
const Array<const char*> &tag_names_, bool use_device)
: ParticleSet(1, 0, num_particles, dim, coords_ordering, field_vdims,
field_orderings, field_names_, num_tags, tag_names_)
field_orderings, field_names_, num_tags, tag_names_, use_device)
{
}
@@ -627,21 +895,21 @@ ParticleSet::ParticleSet(int num_particles, int dim,
#ifdef MFEM_USE_MPI
ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
Ordering::Type coords_ordering)
Ordering::Type coords_ordering, bool use_device)
: ParticleSet(comm_, rank_num_particles, dim, coords_ordering, Array<int>(),
Array<Ordering::Type>(), Array<const char*>(), 0,
Array<const char*>())
Array<const char*>(), use_device)
{
};
ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims, int num_tags,
Ordering::Type all_ordering)
Ordering::Type all_ordering, bool use_device)
: ParticleSet(comm_, rank_num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
GetEmptyNameArray(field_vdims.Size()), num_tags,
GetEmptyNameArray(num_tags))
GetEmptyNameArray(num_tags), use_device)
{
}
@@ -650,11 +918,11 @@ ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims, const Array<const
char*> &field_names_,
int num_tags, const Array<const char*> &tag_names_,
Ordering::Type all_ordering)
Ordering::Type all_ordering, bool use_device)
: ParticleSet(comm_, rank_num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
field_names_, num_tags,
tag_names_)
tag_names_, use_device)
{
}
@@ -664,7 +932,7 @@ ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_)
const Array<const char*> &tag_names_, bool use_device)
: ParticleSet(GetSize(comm_), (IDType)GetRank(comm_),
rank_num_particles,
dim,
@@ -673,7 +941,7 @@ ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
field_orderings,
field_names_,
num_tags,
tag_names_)
tag_names_, use_device)
{
comm = comm_;
#ifdef MFEM_USE_GSLIB
@@ -705,6 +973,7 @@ int ParticleSet::AddField(int vdim, Ordering::Type field_ordering,
}
fields.emplace_back(std::make_unique<ParticleVector>(vdim, field_ordering,
GetNParticles()));
if (coords.UseDevice()) { fields.back()->UseDevice(true); }
field_names.emplace_back(field_name_str);
return GetNFields() - 1;
@@ -718,6 +987,7 @@ int ParticleSet::AddTag(const char* tag_name)
tag_name_str = GetDefaultTagName(tag_names.size());
}
tags.emplace_back(std::make_unique<Array<int>>(GetNParticles()));
if (coords.UseDevice()) { tags.back()->GetMemory().UseDevice(true); }
tag_names.emplace_back(tag_name_str);
return GetNTags() - 1;
@@ -782,7 +1052,7 @@ Particle ParticleSet::GetParticle(int i) const
for (int t = 0; t < GetNTags(); t++)
{
p.Tag(t) = Tag(t)[i];
p.Tag(t) = Tag(t).HostRead()[i];
}
return p;
@@ -790,13 +1060,21 @@ Particle ParticleSet::GetParticle(int i) const
bool ParticleSet::IsParticleRefValid() const
{
if (coords.GetOrdering() == Ordering::byNODES)
if (coords.GetOrdering() == Ordering::byNODES || coords.UseDevice())
{
return false;
}
for (int f = 0; f < GetNFields(); f++)
{
if (fields[f]->GetOrdering() == Ordering::byNODES)
if (fields[f]->GetOrdering() == Ordering::byNODES ||
fields[f]->UseDevice())
{
return false;
}
}
for (int t = 0; t < GetNTags(); t++)
{
if (tags[t]->UseDevice())
{
return false;
}
@@ -806,6 +1084,10 @@ bool ParticleSet::IsParticleRefValid() const
Particle ParticleSet::GetParticleRef(int i)
{
MFEM_ASSERT(IsParticleRefValid(),
"GetParticleRef is only valid when coordinates and fields are "
"ordered byVDIM and particle data is host-resident.");
Particle p = CreateParticle();
Coords().GetValuesRef(i, p.Coords());
@@ -839,7 +1121,7 @@ void ParticleSet::SetParticle(int i, const Particle &p)
for (int t = 0; t < GetNTags(); t++)
{
Tag(t)[i] = p.Tag(t);
Tag(t).HostReadWrite()[i] = p.Tag(t);
}
}
@@ -900,6 +1182,15 @@ void ParticleSet::PrintCSV(const char *fname, const Array<int> &field_idxs,
#ifdef MFEM_USE_MPI
int rank = GetRank(comm);
#endif // MFEM_USE_MPI
// make sure we can read tag data on host. fields and coords will be read as
// needed in the loop below, so we don't need to pre-read them here.
for (int i = 0; i < GetNTags(); i++)
{
tags[i]->HostRead();
}
ids.HostRead();
// Write particle data
for (int i = 0; i < GetNParticles(); i++)
{
ss_data << ids[i];
+49 -12
View File
@@ -211,6 +211,12 @@ public:
* byVDIM). The unique_ptrs to all the ParticleVectors are stored in the
* std::vector \ref fields.
*
* @par Device Behavior:
* When a ParticleSet is constructed with \p use_device=true, \ref coords and
* all ParticleVector fields are marked to use device memory. Fields added
* later through \ref AddField inherit the current device mode (through
* \ref coords).
*
* @par Tags:
* Tags represent integers associated with each particle. For a given tag,
* all particle data are stored in a single Array<int>. The unique_ptrs to all
@@ -369,7 +375,10 @@ protected:
* ID of a particle.
*/
void UpdateID(int local_idx, IDType new_global_id)
{ ids[local_idx] = new_global_id; }
{
ids.HostReadWrite();
ids[local_idx] = new_global_id;
}
/** @brief Create a Particle object with the same spatial dimension,
* number of fields and field vdims, and number of tags as this ParticleSet.
@@ -399,12 +408,14 @@ protected:
* @param[in] field_names_ Array of field names.
* @param[in] num_tags Number of tags to register.
* @param[in] tag_names_ Array of tag names.
* @param[in] use_device Use device memory for particle fields.
*/
ParticleSet(int id_stride_, IDType id_counter_, int num_particles, int dim,
Ordering::Type coords_ordering, const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_);
const Array<const char*> &tag_names_,
bool use_device);
public:
@@ -413,9 +424,12 @@ public:
* @param[in] num_particles Number of particles to initialize.
* @param[in] dim Particle spatial dimension.
* @param[in] coords_ordering Ordering of coordinates.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(int num_particles, int dim,
Ordering::Type coords_ordering=Ordering::byVDIM);
Ordering::Type coords_ordering=Ordering::byVDIM,
bool use_device=false);
/** @brief Construct a serial ParticleSet with specified fields and tags at
* construction.
@@ -426,9 +440,12 @@ public:
* @param[in] num_tags Number of tags to register.
* @param[in] all_ordering (Optional) Ordering of coordinates and
* field ParticleVector.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(int num_particles, int dim, const Array<int> &field_vdims,
int num_tags, Ordering::Type all_ordering=Ordering::byVDIM);
int num_tags, Ordering::Type all_ordering=Ordering::byVDIM,
bool use_device=false);
/** @brief Construct a serial ParticleSet with specified fields and tags at
* construction, with names.
@@ -441,11 +458,14 @@ public:
* @param[in] tag_names_ Array of tag names.
* @param[in] all_ordering (Optional) Ordering of coordinates and
* field ParticleVector.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(int num_particles, int dim, const Array<int> &field_vdims,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
Ordering::Type all_ordering=Ordering::byVDIM);
Ordering::Type all_ordering=Ordering::byVDIM,
bool use_device=false);
/** @brief Comprehensive serial constructor of ParticleSet.
*
@@ -457,12 +477,15 @@ public:
* @param[in] field_names_ Array of field names.
* @param[in] num_tags Number of tags to register.
* @param[in] tag_names_ Array of tag names.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(int num_particles, int dim, Ordering::Type coords_ordering,
const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_);
const Array<const char*> &tag_names_,
bool use_device=false);
#ifdef MFEM_USE_MPI
/** @brief Construct a parallel ParticleSet.
@@ -471,9 +494,12 @@ public:
* @param[in] rank_num_particles Number of particles to initialize.
* @param[in] dim Particle spatial dimension.
* @param[in] coords_ordering (Optional) Ordering of coordinates.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
Ordering::Type coords_ordering=Ordering::byVDIM);
Ordering::Type coords_ordering=Ordering::byVDIM,
bool use_device=false);
/** @brief Construct a parallel ParticleSet with specified fields and tags
* at construction.
@@ -485,10 +511,13 @@ public:
* @param[in] num_tags Number of tags to register.
* @param[in] all_ordering (Optional) Ordering of coordinates and
* field ParticleVector.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims, int num_tags,
Ordering::Type all_ordering=Ordering::byVDIM);
Ordering::Type all_ordering=Ordering::byVDIM,
bool use_device=false);
/** @brief Construct a parallel ParticleSet with specified fields and tags
* at construction, with names (for PrintCSV()).
@@ -502,12 +531,15 @@ public:
* @param[in] tag_names_ Array of tag names.
* @param[in] all_ordering (Optional) Ordering of coordinates and
* field ParticleVector.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims,
const Array<const char*> &field_names_,
int num_tags, const Array<const char*> &tag_names_,
Ordering::Type all_ordering=Ordering::byVDIM);
Ordering::Type all_ordering=Ordering::byVDIM,
bool use_device=false);
/** @brief Comprehensive parallel constructor of ParticleSet.
*
@@ -520,12 +552,15 @@ public:
* @param[in] field_names_ Array of field names.
* @param[in] num_tags Number of tags to register.
* @param[in] tag_names_ Array of tag names.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
Ordering::Type coords_ordering, const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_);
const Array<const char*> &tag_names_,
bool use_device=false);
/// Get the MPI communicator for this ParticleSet.
MPI_Comm GetComm() const { return comm; };
@@ -545,6 +580,8 @@ public:
* @param[in] field_ordering (Optional) Ordering::Type of the field.
* @param[in] field_name (Optional) Name of the field.
*
* @note New fields inherit the current device mode of \ref coords.
*
* @return Index of the newly-added field.
*/
int AddField(int vdim, Ordering::Type field_ordering=Ordering::byVDIM,
@@ -637,8 +674,8 @@ public:
/** @brief Determine if GetParticleRef is valid.
*
* If coordinates and all fields are ordered byVDIM, then returns true.
* Otherwise, false.
* Returns true when coordinates and all fields are ordered byVDIM and
* particle data is host-resident. Otherwise, false.
*/
bool IsParticleRefValid() const;
-9
View File
@@ -38,15 +38,6 @@
#define CUB_IGNORE_DEPRECATED_CPP_DIALECT
#define THRUST_IGNORE_DEPRECATED_CPP_DIALECT
// MFEM only supports using RAJA/CAMP backends in default stream mode because
// memory calls are performed outside of the RAJA ecosystem
#ifndef CAMP_USE_PLATFORM_DEFAULT_STREAM
#define CAMP_USE_PLATFORM_DEFAULT_STREAM 1
#else
#if !CAMP_USE_PLATFORM_DEFAULT_STREAM
#error "MFEM only supports RAJA/CAMP with the default platform stream."
#endif
#endif
#include "RAJA/RAJA.hpp"
#if defined(RAJA_ENABLE_CUDA) && !defined(MFEM_USE_CUDA)
#error When RAJA is built with CUDA, MFEM_USE_CUDA=YES is required
+3 -1
View File
@@ -581,7 +581,9 @@ void Device::Setup(const std::string &device_option, const int device_id)
if (Allows(Backend::CUDA)) { CudaDeviceSetup(dev, ngpu); }
if (Allows(Backend::HIP)) { HipDeviceSetup(dev, ngpu); }
if (Allows(Backend::RAJA_CUDA) || Allows(Backend::RAJA_HIP))
{ RajaDeviceSetup(dev, ngpu); }
{
RajaDeviceSetup(dev, ngpu);
}
// The check for MFEM_USE_OCCA is in the function OccaDeviceSetup().
if (Allows(Backend::OCCA_MASK)) { OccaDeviceSetup(dev); }
if (Allows(Backend::CEED_MASK))
+17
View File
@@ -16,6 +16,11 @@
#include "globals.hpp"
#include "mem_manager.hpp"
#ifdef MFEM_USE_RAJA
#include "RAJA/RAJA.hpp"
#endif
#include <memory>
#include <string>
namespace mfem
@@ -266,6 +271,18 @@ public:
static inline bool Allows(unsigned long b_mask)
{ return Get().backends & b_mask; }
#if defined(MFEM_USE_RAJA) && \
(defined(RAJA_ENABLE_CUDA) || defined(RAJA_ENABLE_HIP))
static inline auto GetRajaResource()
{
#if defined(RAJA_ENABLE_CUDA)
return RAJA::resources::Cuda::CudaFromStream(0, Get().GetId());
#elif defined(RAJA_ENABLE_HIP)
return RAJA::resources::Hip::HipFromStream(0, Get().GetId());
#endif
}
#endif
/** @brief Get the current Host MemoryType. This is the MemoryType used by
most MFEM classes when allocating memory used on the host.
*/
+30 -38
View File
@@ -317,8 +317,8 @@ template <typename DBODY>
void RajaCuWrap1D(const int N, DBODY &&d_body)
{
//true denotes asynchronous kernel
RAJA::forall<RAJA::cuda_exec<MFEM_CUDA_BLOCKS,true>>(RAJA::RangeSegment(0,N),
d_body);
RAJA::forall<RAJA::cuda_exec<MFEM_CUDA_BLOCKS, true> >(
Device::GetRajaResource(), RAJA::RangeSegment(0, N), d_body);
}
template <typename DBODY>
@@ -331,9 +331,9 @@ void RajaCuWrap2D(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_policy>
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE (LaunchContext ctx)
launch<cuda_launch_policy>(Device::GetRajaResource(),
LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<cuda_teams_x>(ctx, RangeSegment(0, G), [&] (const int n)
@@ -349,7 +349,6 @@ void RajaCuWrap2D(const int N, DBODY &&d_body,
});
});
});
MFEM_GPU_CHECK(cudaGetLastError());
@@ -365,9 +364,9 @@ void RajaCuWrap2DLaunchBounds(const int N, DBODY &&d_body, const int X,
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_bounds_policy<LB> >
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
launch<cuda_launch_bounds_policy<LB> >(
Device::GetRajaResource(), LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<cuda_teams_x>(ctx, RangeSegment(0, G), [&] (const int n)
{
@@ -390,13 +389,12 @@ void RajaCuWrap3D(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_policy>
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE (LaunchContext ctx)
launch<cuda_launch_policy>(Device::GetRajaResource(),
LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<cuda_teams_x>(ctx, RangeSegment(0, N), d_body);
});
MFEM_GPU_CHECK(cudaGetLastError());
@@ -410,12 +408,10 @@ void RajaCuWrap3DLaunchBounds(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_bounds_policy<LB> >
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<cuda_teams_x>(ctx, RangeSegment(0, N), d_body);
});
launch<cuda_launch_bounds_policy<LB> >(
Device::GetRajaResource(), LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{ loop<cuda_teams_x>(ctx, RangeSegment(0, N), d_body); });
MFEM_GPU_CHECK(cudaGetLastError());
}
@@ -484,8 +480,8 @@ template <typename DBODY>
void RajaHipWrap1D(const int N, DBODY &&d_body)
{
//true denotes asynchronous kernel
RAJA::forall<RAJA::hip_exec<MFEM_HIP_BLOCKS,true>>(RAJA::RangeSegment(0,N),
d_body);
RAJA::forall<RAJA::hip_exec<MFEM_HIP_BLOCKS,true> >(RAJA::RangeSegment(0,N),
d_body);
}
template <typename DBODY>
@@ -498,9 +494,9 @@ void RajaHipWrap2D(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_policy>
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE (LaunchContext ctx)
launch<hip_launch_policy>(Device::GetRajaResource(),
LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<hip_teams_x>(ctx, RangeSegment(0, G), [&] (const int n)
@@ -516,7 +512,6 @@ void RajaHipWrap2D(const int N, DBODY &&d_body,
});
});
});
MFEM_GPU_CHECK(hipGetLastError());
@@ -532,9 +527,9 @@ void RajaHipWrap2DLaunchBounds(const int N, DBODY &&d_body, const int X,
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_bounds_policy<LB> >
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
launch<hip_launch_bounds_policy<LB> >(
Device::GetRajaResource(), LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<hip_teams_x>(ctx, RangeSegment(0, G), [&] (const int n)
{
@@ -557,13 +552,12 @@ void RajaHipWrap3D(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_policy>
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE (LaunchContext ctx)
launch<hip_launch_policy>(Device::GetRajaResource(),
LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<hip_teams_x>(ctx, RangeSegment(0, N), d_body);
});
MFEM_GPU_CHECK(hipGetLastError());
@@ -577,12 +571,10 @@ void RajaHipWrap3DLaunchBounds(const int N, DBODY &&d_body, const int X,
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_bounds_policy<LB> >
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<hip_teams_x>(ctx, RangeSegment(0, N), d_body);
});
launch<hip_launch_bounds_policy<LB> >(
Device::GetRajaResource(), LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{ loop<hip_teams_x>(ctx, RangeSegment(0, N), d_body); });
MFEM_GPU_CHECK(hipGetLastError());
}
+24 -1
View File
@@ -12,7 +12,9 @@
#include "optparser.hpp"
#include "../linalg/vector.hpp"
#include "../general/communication.hpp"
#include <cerrno>
#include <cctype>
#include <cstdlib>
namespace mfem
{
@@ -48,6 +50,19 @@ int isValidAsInt(char * s)
return 1;
}
int isValidAsLongLong(char * s)
{
if (s == NULL || *s == '\0')
{
return 0;
}
char *end = NULL;
errno = 0;
std::strtoll(s, &end, 10);
return errno == 0 && end != s && *end == '\0';
}
int isValidAsDouble(char * s)
{
// A valid floating point number for atof using the "C" locale is formed by
@@ -200,6 +215,10 @@ void OptionsParser::Parse()
isValid = isValidAsInt(argv[i]);
*(int *)(options[j].var_ptr) = atoi(argv[i++]);
break;
case LONG:
isValid = isValidAsLongLong(argv[i]);
*(long long *)(options[j].var_ptr) = std::strtoll(argv[i++], NULL, 10);
break;
case DOUBLE:
isValid = isValidAsDouble(argv[i]);
*(real_t *)(options[j].var_ptr) = atof(argv[i++]);
@@ -279,6 +298,10 @@ void OptionsParser::WriteValue(const Option &opt, std::ostream &os)
os << *(int *)(opt.var_ptr);
break;
case LONG:
os << *(long long *)(opt.var_ptr);
break;
case DOUBLE:
os << *(real_t *)(opt.var_ptr);
break;
@@ -410,7 +433,7 @@ void OptionsParser::PrintHelp(ostream &os) const
static const char *line_sep = "";
static const char *types[] = { " <int>", " <double>", " <string>",
" <string>", "", "", " '<int>...'",
" '<double>...'"
" '<double>...'", " <long long>"
};
os << indent << "-h" << seprtr << "--help" << descr_sep
+9 -1
View File
@@ -31,7 +31,7 @@ class Vector;
class OptionsParser
{
public:
enum OptionType { INT, DOUBLE, STRING, STD_STRING, ENABLE, DISABLE, ARRAY, VECTOR };
enum OptionType { INT, DOUBLE, STRING, STD_STRING, ENABLE, DISABLE, ARRAY, VECTOR, LONG };
private:
struct Option
@@ -98,6 +98,14 @@ public:
required));
}
/// Add a long integer option and set 'var' to receive the value.
void AddOption(long long *var, const char *short_name, const char *long_name,
const char *description, bool required = false)
{
options.Append(Option(LONG, var, short_name, long_name, description,
required));
}
/// Add a double option and set 'var' to receive the value.
void AddOption(real_t *var, const char *short_name, const char *long_name,
const char *description, bool required = false)
+6
View File
@@ -76,6 +76,12 @@ list(APPEND HDRS
ttensor.hpp
tensor.hpp
vector.hpp
chpt/chpt.hpp
chpt/dynamic_checkpointing.hpp
chpt/file_checkpoint_storage.hpp
chpt/fixed_slot_checkpoint_storage.hpp
chpt/revolve_checkpointing.hpp
chpt/revolve_file_storage.hpp
)
if (MFEM_USE_MPI)
+21
View File
@@ -0,0 +1,21 @@
// 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_CHPT_HPP
#define MFEM_CHPT_HPP
#include "dynamic_checkpointing.hpp"
#include "file_checkpoint_storage.hpp"
#include "fixed_slot_checkpoint_storage.hpp"
#include "revolve_checkpointing.hpp"
#include "revolve_file_storage.hpp"
#endif // MFEM_CHPT_HPP
+309
View File
@@ -0,0 +1,309 @@
#ifndef MFEM_DYNAMIC_CHECKPOINTING_HPP
#define MFEM_DYNAMIC_CHECKPOINTING_HPP
#pragma once
#include <map>
#include <memory>
#include <limits>
#include <vector>
#include <utility>
namespace mfem
{
template <typename Snapshot>
class InMemoryCheckpointStorage
{
public:
using Handle = int;
Handle InvalidHandle() const { return -1; }
bool IsValid(const Handle &h) const { return h >= 0; }
Handle Store(Snapshot &&snap)
{
Handle id = InvalidHandle();
if (!free_.empty())
{
id = free_.back();
free_.pop_back();
MFEM_VERIFY(!slots_[id],
"InMemoryCheckpointStorage: free slot unexpectedly occupied.");
// Reuse the handle ID only; erased Snapshot objects are not reused.
slots_[id].reset(new Snapshot(std::move(snap)));
}
else
{
id = (Handle) slots_.size();
slots_.push_back(std::unique_ptr<Snapshot>(new Snapshot(std::move(snap))));
}
return id;
}
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h), "InMemoryCheckpointStorage: invalid handle.");
MFEM_VERIFY(h < (Handle) slots_.size(),
"InMemoryCheckpointStorage: handle out of range.");
MFEM_VERIFY(slots_[h].get() != nullptr,
"InMemoryCheckpointStorage: empty slot.");
f(*slots_[h]);
}
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
MFEM_VERIFY(h < (Handle) slots_.size(),
"InMemoryCheckpointStorage: handle out of range.");
slots_[h].reset();
free_.push_back(h);
h = InvalidHandle();
}
private:
std::vector<std::unique_ptr<Snapshot>> slots_;
std::vector<Handle> free_;
};
/**
* Dynamic checkpointing manager (WangMoinIaccarino 2009), with pluggable storage.
*
* Snapshot: checkpointed object (often the primal State itself).
* Storage : must provide:
* using Handle = ...
* Handle InvalidHandle() const;
* bool IsValid(const Handle&) const;
* Handle Store(Snapshot&&);
* template<class F> void Read(const Handle&, F&&) const; // calls f(const Snapshot&)
* void Erase(Handle&);
*/
template <typename Snapshot,
typename Storage = InMemoryCheckpointStorage<Snapshot>>
class DynamicCheckpointing
{
public:
using Step = long long;
using Handle = typename Storage::Handle;
struct Checkpoint
{
int level = 0;
Handle h; // InvalidHandle => placeholder
Checkpoint() = default;
Checkpoint(int lvl, const Handle &inv) : level(lvl), h(inv) {}
};
explicit DynamicCheckpointing(int s)
: s_(s),
owned_storage_(new Storage()),
storage_(owned_storage_.get())
{
MFEM_VERIFY(s_ > 0, "DynamicCheckpointing: s must be > 0.");
Reset();
}
DynamicCheckpointing(int s, Storage &external_storage)
: s_(s),
owned_storage_(nullptr),
storage_(&external_storage)
{
MFEM_VERIFY(s_ > 0, "DynamicCheckpointing: s must be > 0.");
Reset();
}
~DynamicCheckpointing() { ReleaseAll(); }
void Reset()
{
ReleaseAll();
cps_.emplace(Step(0), Checkpoint(InfLevel(), storage_->InvalidHandle()));
}
struct CheckpointInfo
{
Step step;
int level;
bool stored;
};
std::vector<CheckpointInfo> GetCheckpointInfo() const
{
std::vector<CheckpointInfo> info;
info.reserve(cps_.size());
for (const auto &kv : cps_)
{
info.push_back({kv.first, kv.second.level, storage_->IsValid(kv.second.h)});
}
return info;
}
Step GetMaxStep() const
{
MFEM_VERIFY(!cps_.empty(), "DynamicCheckpointing: checkpoint map is empty.");
return cps_.rbegin()->first;
}
template <typename State, typename PrimalStepFn, typename MakeSnapshotFn>
void ForwardStep(const Step i,
State &u_i_inout,
PrimalStepFn &&primal_step,
MakeSnapshotFn &&make_snapshot)
{
AllocateCheckpointForNextStep(i);
auto it = cps_.find(i);
if (it != cps_.end())
{
storage_->Erase(it->second.h);
Snapshot snap = make_snapshot(u_i_inout);
it->second.h = storage_->Store(std::move(snap));
}
primal_step(u_i_inout, i);
}
template <typename State,
typename AdjState,
typename PrimalStepFn,
typename AdjointStepFn,
typename MakeSnapshotFn,
typename RestoreSnapshotFn>
void BackwardStep(const Step i,
AdjState &q_ip1_inout,
State &u_work_inout,
PrimalStepFn &&primal_step,
AdjointStepFn &&adjoint_step,
MakeSnapshotFn &&make_snapshot,
RestoreSnapshotFn &&restore_snapshot)
{
// remove placeholder at i+1
const Step ph = i + 1;
auto it_ph = cps_.find(ph);
MFEM_VERIFY(it_ph != cps_.end(),
"DynamicCheckpointing: expected checkpoint at i+1 before BackwardStep.");
storage_->Erase(it_ph->second.h);
cps_.erase(it_ph);
MFEM_ASSERT(GetMaxStep() <= i,
"DynamicCheckpointing: found a checkpoint beyond current adjoint step.");
auto restore_from_handle = [&](Handle &h)
{
storage_->Read(h, [&](const Snapshot &snap)
{
restore_snapshot(snap, u_work_inout);
});
storage_->Erase(h); // retrieved => placeholder (Algorithm 4 semantics)
};
if (GetMaxStep() == i)
{
Handle h = TakeHandleMakePlaceholder(i);
restore_from_handle(h);
}
else
{
const Step k = GetMaxStep();
Handle hk = TakeHandleMakePlaceholder(k);
restore_from_handle(hk);
for (Step t = k; t < i; ++t)
{
ForwardStep(t, u_work_inout, primal_step, make_snapshot);
}
}
adjoint_step(q_ip1_inout, u_work_inout, i);
}
private:
int s_ = 0;
std::unique_ptr<Storage> owned_storage_;
Storage *storage_ = nullptr;
std::map<Step, Checkpoint> cps_;
static int InfLevel() { return std::numeric_limits<int>::max(); }
void ReleaseAll()
{
if (!storage_) { cps_.clear(); return; }
for (auto &kv : cps_) { storage_->Erase(kv.second.h); }
cps_.clear();
}
bool FindDispensableLargestStep(Step &out_step) const
{
int max_level_seen = std::numeric_limits<int>::min();
for (auto it = cps_.rbegin(); it != cps_.rend(); ++it)
{
const Step step = it->first;
const int lvl = it->second.level;
if (max_level_seen > lvl) { out_step = step; return true; }
max_level_seen = (lvl > max_level_seen) ? lvl : max_level_seen;
}
return false;
}
void AllocateCheckpointForNextStep(const Step i)
{
const Step new_step = i + 1;
MFEM_VERIFY(cps_.find(new_step) == cps_.end(),
"DynamicCheckpointing: checkpoint at i+1 already exists.");
const Handle inv = storage_->InvalidHandle();
// allow growth to s+1 entries (incl. placeholder)
if ((int)cps_.size() <= s_)
{
cps_.emplace(new_step, Checkpoint(0, inv));
return;
}
Step disp = -1;
if (FindDispensableLargestStep(disp))
{
auto it = cps_.find(disp);
MFEM_ASSERT(it != cps_.end(),
"Internal error: dispensable checkpoint not found.");
storage_->Erase(it->second.h);
cps_.erase(it);
cps_.emplace(new_step, Checkpoint(0, inv));
return;
}
auto it_i = cps_.find(i);
MFEM_VERIFY(it_i != cps_.end(),
"DynamicCheckpointing: promotion expected checkpoint at step i but none found.");
MFEM_VERIFY(i != 0,
"DynamicCheckpointing: attempted to remove step 0 checkpoint.");
const int l = it_i->second.level;
storage_->Erase(it_i->second.h);
cps_.erase(it_i);
cps_.emplace(new_step, Checkpoint(l + 1, inv));
}
Handle TakeHandleMakePlaceholder(const Step i)
{
auto it = cps_.find(i);
MFEM_VERIFY(it != cps_.end(),
"DynamicCheckpointing: TakeHandle requested a non-existent checkpoint.");
MFEM_VERIFY(storage_->IsValid(it->second.h),
"DynamicCheckpointing: TakeHandle requested a checkpoint with no snapshot.");
Handle h = std::move(it->second.h);
it->second.h = storage_->InvalidHandle();
return h;
}
};
} // namespace mfem
#endif // MFEM_DYNAMIC_CHECKPOINTING_HPP
+304
View File
@@ -0,0 +1,304 @@
#ifndef MFEM_FILE_CHECKPOINT_STORAGE_HPP
#define MFEM_FILE_CHECKPOINT_STORAGE_HPP
#pragma once
#include "../vector.hpp"
#include <cerrno>
#include <cstdint>
#include <cstdio> // std::remove, std::rename
#include <cstring>
#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <type_traits>
#if __has_include(<filesystem>)
#include <filesystem>
namespace mfem_fs = std::filesystem;
#define MFEM_HAVE_FILESYSTEM 1
#else
#define MFEM_HAVE_FILESYSTEM 0
#endif
namespace mfem
{
/**
* @brief Default binary serializer for checkpoint snapshots.
*
* Contract:
* - static void Write(std::ostream&, const Snapshot&)
* - static Snapshot Read (std::istream&)
*
* Default implementation supports trivially-copyable POD types.
* Specialization for mfem::Vector is provided below.
*
* Portability note:
* This binary format is NOT portable across endianness or differing sizeof(real_t).
* If you need portability, provide a custom SnapshotIO (e.g., text, XDR, HDF5).
*/
template <typename Snapshot, typename Enable = void>
struct DefaultCheckpointBinaryIO
{
static void Write(std::ostream &, const Snapshot &)
{
static_assert(sizeof(Snapshot) == 0,
"DefaultCheckpointBinaryIO: no implementation for this Snapshot type. "
"Provide a custom SnapshotIO with Write/Read.");
}
static Snapshot Read(std::istream &)
{
static_assert(sizeof(Snapshot) == 0,
"DefaultCheckpointBinaryIO: no implementation for this Snapshot type. "
"Provide a custom SnapshotIO with Write/Read.");
return Snapshot();
}
};
// POD / trivially-copyable types (double, int, structs of POD, etc.)
template <typename Snapshot>
struct DefaultCheckpointBinaryIO<
Snapshot,
typename std::enable_if<std::is_trivially_copyable<Snapshot>::value>::type>
{
static void Write(std::ostream &os, const Snapshot &x)
{
os.write(reinterpret_cast<const char*>(&x), sizeof(Snapshot));
MFEM_VERIFY(os.good(),
"DefaultCheckpointBinaryIO: failed to write POD snapshot.");
}
static Snapshot Read(std::istream &is)
{
Snapshot x;
is.read(reinterpret_cast<char*>(&x), sizeof(Snapshot));
MFEM_VERIFY(is.good(),
"DefaultCheckpointBinaryIO: failed to read POD snapshot.");
return x;
}
};
// Specialization for mfem::Vector
template <>
struct DefaultCheckpointBinaryIO<mfem::Vector, void>
{
static void Write(std::ostream &os, const mfem::Vector &v)
{
const std::int64_t n = (std::int64_t) v.Size();
os.write(reinterpret_cast<const char*>(&n), sizeof(n));
MFEM_VERIFY(os.good(), "VectorBinaryIO: failed to write vector size.");
if (n > 0)
{
const mfem::real_t *data = v.HostRead();
os.write(reinterpret_cast<const char*>(data),
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
MFEM_VERIFY(os.good(), "VectorBinaryIO: failed to write vector data.");
}
}
static mfem::Vector Read(std::istream &is)
{
std::int64_t n = 0;
is.read(reinterpret_cast<char*>(&n), sizeof(n));
MFEM_VERIFY(is.good(), "VectorBinaryIO: failed to read vector size.");
MFEM_VERIFY(n >= 0, "VectorBinaryIO: invalid negative vector size.");
mfem::Vector v((int)n);
if (n > 0)
{
mfem::real_t *data = v.HostWrite();
is.read(reinterpret_cast<char*>(data),
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
v.Read();
MFEM_VERIFY(is.good(), "VectorBinaryIO: failed to read vector data.");
}
return v;
}
};
/**
* @brief File-based checkpoint storage backend.
*
* Stores each snapshot in a separate file:
* <directory>/<prefix><id><extension>
*
* Handle is a monotonically-increasing integer id (reused if keep_files==false).
*
* Template parameters:
* - Snapshot : stored snapshot type
* - SnapshotIO : serializer with static Write/Read methods (see DefaultCheckpointBinaryIO)
*
* Threading:
* - Not thread-safe. Typical adjoint/checkpointing usage is single-threaded control flow.
*/
template <typename Snapshot,
typename SnapshotIO = DefaultCheckpointBinaryIO<Snapshot>>
class FileCheckpointStorage
{
public:
using Handle = std::int64_t;
/**
* @param directory directory where checkpoint files live (created if create_dir==true)
* @param prefix filename prefix (e.g. "ckpt_")
* @param extension filename extension (e.g. ".bin")
* @param create_dir create directory if missing (requires <filesystem>)
* @param keep_files if true, Erase() will NOT delete files (useful for debugging),
* and ids are NOT reused.
*/
FileCheckpointStorage(const std::string &directory,
const std::string &prefix = "ckpt_",
const std::string &extension = ".bin",
bool create_dir = true,
bool keep_files = false)
: dir_(directory),
prefix_(prefix),
ext_(extension),
keep_files_(keep_files)
{
MFEM_VERIFY(!dir_.empty(),
"FileCheckpointStorage: directory must be non-empty.");
MFEM_VERIFY(!prefix_.empty(),
"FileCheckpointStorage: prefix must be non-empty.");
MFEM_VERIFY(!ext_.empty(),
"FileCheckpointStorage: extension must be non-empty.");
if (create_dir)
{
#if MFEM_HAVE_FILESYSTEM
std::error_code ec;
mfem_fs::create_directories(mfem_fs::path(dir_), ec);
MFEM_VERIFY(!ec, "FileCheckpointStorage: failed to create directory: " << dir_);
#else
MFEM_ABORT("FileCheckpointStorage: create_dir=true requires <filesystem> support.");
#endif
}
}
Handle InvalidHandle() const { return (Handle)-1; }
bool IsValid(const Handle &h) const { return h >= 0; }
/**
* @brief Store snapshot to file, return handle.
*
* Uses atomic-ish pattern:
* - write to "<path>.tmp"
* - rename to "<path>"
*/
Handle Store(Snapshot &&snap)
{
const Handle id = AllocateId_();
const std::string path = Path_(id);
const std::string tmp = path + ".tmp";
{
std::ofstream os(tmp, std::ios::binary | std::ios::trunc);
MFEM_VERIFY(os.is_open(),
"FileCheckpointStorage: failed to open for write: " << tmp);
// Write payload
SnapshotIO::Write(os, snap);
MFEM_VERIFY(os.good(), "FileCheckpointStorage: write failed for: " << tmp);
}
// Rename tmp -> final
const int rc = std::rename(tmp.c_str(), path.c_str());
MFEM_VERIFY(rc == 0, "FileCheckpointStorage: rename failed: " << tmp << " -> "
<< path);
return id;
}
/**
* @brief Read snapshot from file and pass it to callback f(const Snapshot&).
*
* The reference passed to f is valid only during the call.
*/
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h),
"FileCheckpointStorage: Read called with invalid handle.");
const std::string path = Path_(h);
std::ifstream is(path, std::ios::binary);
MFEM_VERIFY(is.is_open(),
"FileCheckpointStorage: failed to open for read: " << path);
Snapshot snap = SnapshotIO::Read(is);
MFEM_VERIFY(is.good(), "FileCheckpointStorage: read failed for: " << path);
f(snap);
}
/**
* @brief Erase snapshot (delete file unless keep_files==true), set handle invalid.
*
* Id reuse policy:
* - if keep_files_ == false: deleted ids are reused to avoid creating huge numbers of files
* - if keep_files_ == true : ids are not reused (avoid overwriting old debug files)
*/
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
if (!keep_files_)
{
const std::string path = Path_(h);
const int rc = std::remove(path.c_str());
MFEM_VERIFY(rc == 0 || errno == ENOENT,
"FileCheckpointStorage: failed to remove file: " << path);
free_.push_back(h);
}
h = InvalidHandle();
}
private:
std::string dir_;
std::string prefix_;
std::string ext_;
bool keep_files_ = false;
mutable Handle next_id_ = 0;
mutable std::vector<Handle> free_;
Handle AllocateId_()
{
if (!keep_files_ && !free_.empty())
{
const Handle id = free_.back();
free_.pop_back();
return id;
}
return next_id_++;
}
std::string Path_(Handle id) const
{
MFEM_VERIFY(IsValid(id),
"FileCheckpointStorage: Path_ called with invalid id.");
std::ostringstream oss;
oss << prefix_ << std::setw(12) << std::setfill('0') << id << ext_;
#if MFEM_HAVE_FILESYSTEM
mfem_fs::path p = mfem_fs::path(dir_) / mfem_fs::path(oss.str());
return p.string();
#else
// Fallback: simple concatenation; assumes dir_ ends without trailing slash if needed.
return dir_ + "/" + oss.str();
#endif
}
};
} // namespace mfem
#endif // MFEM_FILE_CHECKPOINT_STORAGE_HPP
@@ -0,0 +1,408 @@
#ifndef MFEM_FIXED_SLOT_CHECKPOINT_STORAGE_HPP
#define MFEM_FIXED_SLOT_CHECKPOINT_STORAGE_HPP
#pragma once
#include "../vector.hpp"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <fstream>
#include <limits>
#include <type_traits>
namespace mfem
{
// ------------------------------------------------------------
// Packer 1: trivially-copyable snapshots (memcpy)
// ------------------------------------------------------------
template <typename Snapshot>
class TrivialFixedPacker
{
public:
TrivialFixedPacker()
{
static_assert(std::is_trivially_copyable<Snapshot>::value,
"TrivialFixedPacker requires Snapshot to be trivially copyable.");
}
std::size_t SlotBytes() const { return sizeof(Snapshot); }
void Pack(const Snapshot &snap, void *dst) const
{
std::memcpy(dst, &snap, sizeof(Snapshot));
}
void Unpack(const void *src, Snapshot &snap) const
{
std::memcpy(&snap, src, sizeof(Snapshot));
}
};
// ------------------------------------------------------------
// Packer 2: mfem::Vector of fixed length n
// (Serialized size is fixed: n*sizeof(real_t))
// ------------------------------------------------------------
class FixedVectorPacker
{
public:
explicit FixedVectorPacker(int n) : n_(n)
{
MFEM_VERIFY(n_ > 0, "FixedVectorPacker: n must be > 0.");
}
std::size_t SlotBytes() const
{
return (std::size_t)n_ * sizeof(mfem::real_t);
}
void Pack(const mfem::Vector &v, void *dst) const
{
MFEM_VERIFY(v.Size() == n_, "FixedVectorPacker: vector size mismatch.");
std::memcpy(dst, v.HostRead(), SlotBytes());
}
void Unpack(const void *src, mfem::Vector &v) const
{
v.SetSize(n_);
std::memcpy(v.HostWrite(), src, SlotBytes());
}
int Size() const { return n_; }
private:
int n_ = 0;
};
// ------------------------------------------------------------
// Fixed-slot MEMORY storage: one big RAM block
// ------------------------------------------------------------
template <typename Snapshot, typename Packer = TrivialFixedPacker<Snapshot>>
class FixedSlotMemoryCheckpointStorage
{
public:
using Handle = int;
FixedSlotMemoryCheckpointStorage(int max_slots, const Packer &packer = Packer())
: max_slots_(max_slots), packer_(packer)
{
MFEM_VERIFY(max_slots_ > 0,
"FixedSlotMemoryCheckpointStorage: max_slots must be > 0.");
slot_bytes_ = packer_.SlotBytes();
MFEM_VERIFY(slot_bytes_ > 0,
"FixedSlotMemoryCheckpointStorage: SlotBytes must be > 0.");
// Single contiguous block
bytes_.resize((std::size_t)max_slots_ * slot_bytes_);
in_use_.assign(max_slots_, 0);
free_.reserve(max_slots_);
for (int i = 0; i < max_slots_; ++i) { free_.push_back(i); }
}
Handle InvalidHandle() const { return -1; }
bool IsValid(const Handle &h) const { return h >= 0; }
int MaxSlots() const { return max_slots_; }
std::size_t SlotBytes() const { return slot_bytes_; }
Handle Store(Snapshot &&snap)
{
MFEM_VERIFY(!free_.empty(),
"FixedSlotMemoryCheckpointStorage: out of slots (increase max_slots).");
const int slot = free_.back();
free_.pop_back();
in_use_[slot] = 1;
void *dst = SlotPtr_(slot);
packer_.Pack(snap, dst);
return slot;
}
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h),
"FixedSlotMemoryCheckpointStorage: Read invalid handle.");
MFEM_VERIFY(h < max_slots_,
"FixedSlotMemoryCheckpointStorage: Read handle out of range.");
MFEM_VERIFY(in_use_[h] == 1,
"FixedSlotMemoryCheckpointStorage: Read from free slot.");
Snapshot tmp;
const void *src = SlotPtrConst_(h);
packer_.Unpack(src, tmp);
f(tmp);
}
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
MFEM_VERIFY(h < max_slots_,
"FixedSlotMemoryCheckpointStorage: Erase handle out of range.");
MFEM_VERIFY(in_use_[h] == 1,
"FixedSlotMemoryCheckpointStorage: double-free / invalid erase.");
in_use_[h] = 0;
free_.push_back(h);
h = InvalidHandle();
}
/// Optional: return all slots to the free list (does not zero memory).
void Reset()
{
free_.clear();
for (int i = 0; i < max_slots_; ++i) { in_use_[i] = 0; free_.push_back(i); }
}
private:
int max_slots_ = 0;
std::size_t slot_bytes_ = 0;
Packer packer_;
std::vector<unsigned char> bytes_; // single block
std::vector<unsigned char> in_use_;
std::vector<int> free_;
void *SlotPtr_(int slot)
{
return (void*)(&bytes_[(std::size_t)slot * slot_bytes_]);
}
const void *SlotPtrConst_(int slot) const
{
return (const void*)(&bytes_[(std::size_t)slot * slot_bytes_]);
}
};
// ------------------------------------------------------------
// Fixed-slot FILE storage: one single pre-sized file
// ------------------------------------------------------------
template <typename Snapshot, typename Packer = TrivialFixedPacker<Snapshot>>
class FixedSlotFileCheckpointStorage
{
public:
using Handle = int;
struct Header
{
char magic[8]; // "MFCKPTFS"
std::uint64_t version; // 1
std::uint64_t slot_bytes;
std::uint64_t max_slots;
std::uint64_t reserved[4]; // future use / padding
};
FixedSlotFileCheckpointStorage(const std::string &path,
int max_slots,
const Packer &packer = Packer(),
bool truncate = true,
bool flush_on_store = false)
: path_(path),
max_slots_(max_slots),
packer_(packer),
flush_on_store_(flush_on_store)
{
MFEM_VERIFY(!path_.empty(), "FixedSlotFileCheckpointStorage: empty file path.");
MFEM_VERIFY(max_slots_ > 0,
"FixedSlotFileCheckpointStorage: max_slots must be > 0.");
slot_bytes_ = packer_.SlotBytes();
MFEM_VERIFY(slot_bytes_ > 0,
"FixedSlotFileCheckpointStorage: SlotBytes must be > 0.");
Open_(truncate);
in_use_.assign(max_slots_, 0);
free_.reserve(max_slots_);
for (int i = 0; i < max_slots_; ++i) { free_.push_back(i); }
scratch_.resize(slot_bytes_);
}
~FixedSlotFileCheckpointStorage()
{
if (file_.is_open()) { file_.close(); }
}
Handle InvalidHandle() const { return -1; }
bool IsValid(const Handle &h) const { return h >= 0; }
int MaxSlots() const { return max_slots_; }
std::size_t SlotBytes() const { return slot_bytes_; }
const std::string &Path() const { return path_; }
Handle Store(Snapshot &&snap)
{
MFEM_VERIFY(!free_.empty(),
"FixedSlotFileCheckpointStorage: out of slots (increase max_slots).");
const int slot = free_.back();
free_.pop_back();
in_use_[slot] = 1;
// Pack into scratch buffer then write into fixed slot offset
packer_.Pack(snap, scratch_.data());
const std::uint64_t off = SlotOffset_(slot);
file_.seekp((std::streamoff)off, std::ios::beg);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: seekp failed.");
file_.write(reinterpret_cast<const char*>(scratch_.data()),
(std::streamsize)slot_bytes_);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: write failed.");
if (flush_on_store_) { file_.flush(); }
return slot;
}
template <typename Func>
void Read(const Handle &h, Func &&f) const
{
MFEM_VERIFY(IsValid(h), "FixedSlotFileCheckpointStorage: Read invalid handle.");
MFEM_VERIFY(h < max_slots_,
"FixedSlotFileCheckpointStorage: Read handle out of range.");
MFEM_VERIFY(in_use_[h] == 1,
"FixedSlotFileCheckpointStorage: Read from free slot.");
const std::uint64_t off = SlotOffset_(h);
file_.seekg((std::streamoff)off, std::ios::beg);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: seekg failed.");
file_.read(reinterpret_cast<char*>(scratch_.data()),
(std::streamsize)slot_bytes_);
MFEM_VERIFY(file_.good(), "FixedSlotFileCheckpointStorage: read failed.");
Snapshot tmp;
packer_.Unpack(scratch_.data(), tmp);
f(tmp);
}
void Erase(Handle &h)
{
if (!IsValid(h)) { h = InvalidHandle(); return; }
MFEM_VERIFY(h < max_slots_,
"FixedSlotFileCheckpointStorage: Erase handle out of range.");
MFEM_VERIFY(in_use_[h] == 1,
"FixedSlotFileCheckpointStorage: double-free / invalid erase.");
// No file deletion; just return slot to free list.
in_use_[h] = 0;
free_.push_back(h);
h = InvalidHandle();
}
/// Optional: return all slots to free list (file contents remain).
void Reset()
{
free_.clear();
for (int i = 0; i < max_slots_; ++i) { in_use_[i] = 0; free_.push_back(i); }
}
private:
std::string path_;
int max_slots_ = 0;
std::size_t slot_bytes_ = 0;
Packer packer_;
bool flush_on_store_ = false;
// mutable because Read() is const but needs to seek/read
mutable std::fstream file_;
mutable std::vector<unsigned char> scratch_;
std::vector<unsigned char> in_use_;
std::vector<int> free_;
static Header MakeHeader_(std::uint64_t slot_bytes, std::uint64_t max_slots)
{
Header h;
std::memset(&h, 0, sizeof(h));
h.magic[0] = 'M'; h.magic[1] = 'F'; h.magic[2] = 'C'; h.magic[3] = 'K';
h.magic[4] = 'P'; h.magic[5] = 'T'; h.magic[6] = 'F'; h.magic[7] = 'S';
h.version = 1;
h.slot_bytes = slot_bytes;
h.max_slots = max_slots;
return h;
}
void Open_(bool truncate)
{
const std::ios::openmode mode =
std::ios::binary | std::ios::in | std::ios::out | (truncate ?
std::ios::trunc : (std::ios::openmode)0);
file_.open(path_.c_str(), mode);
MFEM_VERIFY(file_.is_open(),
"FixedSlotFileCheckpointStorage: failed to open file.");
const Header expected = MakeHeader_((std::uint64_t)slot_bytes_,
(std::uint64_t)max_slots_);
if (truncate)
{
// Write header
file_.seekp(0, std::ios::beg);
file_.write(reinterpret_cast<const char*>(&expected), sizeof(expected));
MFEM_VERIFY(file_.good(),
"FixedSlotFileCheckpointStorage: header write failed.");
// Pre-size file to: header + max_slots*slot_bytes
const std::uint64_t total = (std::uint64_t)sizeof(Header)
+ (std::uint64_t)max_slots_ * (std::uint64_t)slot_bytes_;
MFEM_VERIFY(total > 0,
"FixedSlotFileCheckpointStorage: invalid total file size.");
file_.seekp((std::streamoff)(total - 1), std::ios::beg);
MFEM_VERIFY(file_.good(),
"FixedSlotFileCheckpointStorage: seekp for resize failed.");
const char zero = 0;
file_.write(&zero, 1);
MFEM_VERIFY(file_.good(),
"FixedSlotFileCheckpointStorage: resize write failed.");
file_.flush();
}
else
{
// Validate existing header
Header got;
file_.seekg(0, std::ios::beg);
file_.read(reinterpret_cast<char*>(&got), sizeof(got));
MFEM_VERIFY(file_.good(),
"FixedSlotFileCheckpointStorage: header read failed.");
MFEM_VERIFY(std::memcmp(got.magic, expected.magic, 8) == 0,
"FixedSlotFileCheckpointStorage: magic mismatch.");
MFEM_VERIFY(got.version == expected.version,
"FixedSlotFileCheckpointStorage: version mismatch.");
MFEM_VERIFY(got.slot_bytes == expected.slot_bytes,
"FixedSlotFileCheckpointStorage: slot_bytes mismatch.");
MFEM_VERIFY(got.max_slots == expected.max_slots,
"FixedSlotFileCheckpointStorage: max_slots mismatch.");
}
}
std::uint64_t SlotOffset_(int slot) const
{
return (std::uint64_t)sizeof(Header) + (std::uint64_t)slot *
(std::uint64_t)slot_bytes_;
}
};
} // namespace mfem
#endif // MFEM_FIXED_SLOT_CHECKPOINT_STORAGE_HPP
+537
View File
@@ -0,0 +1,537 @@
#ifndef MFEM_REVOLVE_CHECKPOINTING_HPP
#define MFEM_REVOLVE_CHECKPOINTING_HPP
#pragma once
#include <cstdint>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
namespace mfem
{
// -------------------------------
// Fixed-slot storage: Memory
// -------------------------------
class RevolveFixedSlotMemoryStorage
{
private:
int max_slots_ = 0;
size_t slot_bytes_ = 0;
std::vector<uint8_t> data_;
public:
RevolveFixedSlotMemoryStorage(int max_slots, size_t slot_bytes)
: max_slots_(max_slots), slot_bytes_(slot_bytes),
data_(size_t(max_slots)*slot_bytes, 0)
{
MFEM_VERIFY(max_slots_ > 0, "max_slots must be > 0");
MFEM_VERIFY(slot_bytes_ > 0, "slot_bytes must be > 0");
}
int MaxSlots() const { return max_slots_; }
size_t SlotBytes() const { return slot_bytes_; }
void Save(int slot, const uint8_t *src, size_t bytes)
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Save: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Save: bytes mismatch");
MFEM_VERIFY(src != nullptr, "Save: src is null");
std::memcpy(data_.data() + size_t(slot)*slot_bytes_, src, slot_bytes_);
}
void Load(int slot, uint8_t *dst, size_t bytes) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Load: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Load: bytes mismatch");
MFEM_VERIFY(dst != nullptr, "Load: dst is null");
std::memcpy(dst, data_.data() + size_t(slot)*slot_bytes_, slot_bytes_);
}
};
// -------------------------------
// Fixed-slot storage: Single file
// -------------------------------
class RevolveFixedSlotFileStorage
{
private:
int max_slots_ = 0;
size_t slot_bytes_ = 0;
std::string filename_;
mutable std::fstream file_;
std::streamoff Offset(int slot) const
{
return std::streamoff(size_t(slot) * slot_bytes_);
}
public:
RevolveFixedSlotFileStorage(const std::string &filename,
int max_slots,
size_t slot_bytes)
: max_slots_(max_slots), slot_bytes_(slot_bytes), filename_(filename)
{
MFEM_VERIFY(max_slots_ > 0, "max_slots must be > 0");
MFEM_VERIFY(slot_bytes_ > 0, "slot_bytes must be > 0");
MFEM_VERIFY(!filename_.empty(), "filename must not be empty");
// Create/truncate file and size it.
{
std::ofstream ofs(filename_, std::ios::binary | std::ios::trunc);
MFEM_VERIFY(ofs.good(), "Failed to create checkpoint file");
const size_t total = size_t(max_slots_) * slot_bytes_;
if (total > 0)
{
ofs.seekp(std::streamoff(total - 1));
char zero = 0;
ofs.write(&zero, 1);
}
}
file_.open(filename_, std::ios::binary | std::ios::in | std::ios::out);
MFEM_VERIFY(file_.good(), "Failed to open checkpoint file");
}
~RevolveFixedSlotFileStorage()
{
if (file_.is_open()) { file_.close(); }
}
int MaxSlots() const { return max_slots_; }
size_t SlotBytes() const { return slot_bytes_; }
void Save(int slot, const uint8_t *src, size_t bytes)
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Save: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Save: bytes mismatch");
MFEM_VERIFY(src != nullptr, "Save: src is null");
file_.seekp(Offset(slot));
MFEM_VERIFY(file_.good(), "Save: seekp failed");
file_.write(reinterpret_cast<const char*>(src), std::streamsize(slot_bytes_));
MFEM_VERIFY(file_.good(), "Save: write failed");
file_.flush();
}
void Load(int slot, uint8_t *dst, size_t bytes) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Load: slot out of range");
MFEM_VERIFY(bytes == slot_bytes_, "Load: bytes mismatch");
MFEM_VERIFY(dst != nullptr, "Load: dst is null");
file_.seekg(Offset(slot));
MFEM_VERIFY(file_.good(), "Load: seekg failed");
file_.read(reinterpret_cast<char*>(dst), std::streamsize(slot_bytes_));
MFEM_VERIFY(file_.good(), "Load: read failed");
}
};
// -------------------------------
// REVOLVE controller (actions)
// (Transcribed from revolve.c used in ADOL-C; Algorithm 799.)
// -------------------------------
enum class RevolveAction
{
advance,
takeshot,
restore,
firsturn,
youturn,
terminate
};
class RevolveController
{
private:
int snaps_ = 0;
int check_ = -1;
int capo_ = 0;
int fine_ = 0;
int turn_ = 0;
int oldfine_ = 0;
// Stack of checkpoint times, indexed by 'check_'.
std::vector<int> ch_;
public:
RevolveController() = default;
RevolveController(int snaps, int capo0, int fine0)
: snaps_(snaps), check_(-1), capo_(capo0), fine_(fine0),
turn_(0), oldfine_(fine0), ch_(snaps, 0)
{
MFEM_VERIFY(snaps_ > 0, "REVOLVE snaps must be > 0");
MFEM_VERIFY(capo_ <= fine_, "REVOLVE: capo must be <= fine");
// Match revolve.c initialization behavior.
if (check_ == -1 && capo_ < fine_)
{
turn_ = 0;
ch_[0] = capo_ - 1;
}
}
int Snaps() const { return snaps_; }
int Check() const { return check_; }
int Capo() const { return capo_; }
int Fine() const { return fine_; }
const std::vector<int>& CheckpointTimes() const { return ch_; }
RevolveAction Next()
{
MFEM_VERIFY(!(check_ < -1), "REVOLVE: check < -1");
MFEM_VERIFY(!(capo_ > fine_), "REVOLVE: capo > fine");
if ((check_ == -1) && (capo_ < fine_))
{
turn_ = 0;
ch_[0] = capo_ - 1;
}
const int diff = fine_ - capo_;
switch (diff)
{
case 0:
{
// Terminate or restore to next checkpoint on the stack.
if (check_ == -1 || capo_ == ch_[0])
{
check_ -= 1; // mirror revolve.c behavior
return RevolveAction::terminate;
}
else
{
capo_ = ch_[check_];
oldfine_ = fine_;
return RevolveAction::restore;
}
}
case 1:
{
// One adjoint step available.
fine_ -= 1;
if (check_ >= 0 && ch_[check_] == capo_) { check_ -= 1; }
if (turn_ == 0)
{
turn_ = 1;
oldfine_ = fine_;
return RevolveAction::firsturn;
}
else
{
oldfine_ = fine_;
return RevolveAction::youturn;
}
}
default:
{
// diff > 1
if (check_ == -1 || ch_[check_] != capo_)
{
// Take a new checkpoint at current capo.
check_ += 1;
MFEM_VERIFY(check_ + 1 <= snaps_, "REVOLVE: exceeded snaps");
ch_[check_] = capo_;
oldfine_ = fine_;
return RevolveAction::takeshot;
}
else
{
// Advance capo forward within (capo, fine).
// This follows the binomial logic in revolve.c.
MFEM_VERIFY(!((oldfine_ < fine_) && (snaps_ == check_ + 1)),
"REVOLVE: fine increased unexpectedly with full stack");
const int oldcapo = capo_;
const int ds = snaps_ - check_;
MFEM_VERIFY(ds >= 1, "REVOLVE: ds < 1");
int reps = 0;
long long range = 1;
while (range < (fine_ - capo_))
{
reps += 1;
// range = range*(reps+ds)/reps (integer arithmetic)
range = range * (reps + ds) / reps;
}
MFEM_VERIFY(reps >= 1, "REVOLVE: reps < 1");
// Binomial helper values (integer)
const long long bino1 = range * reps / (ds + reps);
const long long bino2 = (ds > 1) ? (bino1 * ds / (ds + reps - 1)) : 1;
const long long bino3 =
(ds == 1) ? 0 :
(ds > 2) ? (bino2 * (ds - 1) / (ds + reps - 2)) : 1;
const long long bino4 = bino2 * (reps - 1) / ds;
const long long bino5 =
(ds < 3) ? 0 :
(ds > 3) ? (bino3 * (ds - 2) / reps) : 1;
// Kowarz "new version": keep l^ as small as possible
const long long bino6 = bino1 * ds / reps;
const long long gap = fine_ - capo_;
if (gap <= bino1 + bino3)
{
capo_ += int(bino4);
}
else if (gap < bino1 + bino2)
{
capo_ = fine_ - int(bino2 + bino3);
}
else if (gap <= bino1 + bino2 + bino5)
{
capo_ += int(bino1 - bino3);
}
else
{
capo_ = fine_ - int(bino6);
}
if (capo_ == oldcapo) { capo_ = oldcapo + 1; }
oldfine_ = fine_;
return RevolveAction::advance;
}
}
}
}
};
// -------------------------------
// Fixed-step REVOLVE checkpointing
// -------------------------------
template <typename StorageT>
class FixedStepRevolveCheckpointing
{
public:
struct Shot
{
int time = 0; // state index
int slot = 0; // checkpoint slot index
};
private:
int num_steps_ = 0; // total number of primal steps (0..num_steps)
int num_checkpoints_ = 0; // REVOLVE "snaps"
size_t snapshot_bytes_ = 0;
StorageT *storage_ = nullptr;
std::vector<Shot> forward_shots_;
int forward_shot_cursor_ = 0;
// Controller state at the beginning of the reverse sweep (pre-firsturn).
RevolveController ctrl_init_;
RevolveController ctrl_;
// Two scratch buffers for (de)serialization.
std::vector<uint8_t> io_buf_;
std::vector<uint8_t> prefinal_buf_;
bool prefinal_valid_ = false;
// Reverse sweep bookkeeping.
bool reverse_started_ = false;
int u_work_time_ = -1;
public:
FixedStepRevolveCheckpointing(int num_steps,
int num_checkpoints,
size_t snapshot_bytes,
StorageT &storage)
: num_steps_(num_steps),
num_checkpoints_(num_checkpoints),
snapshot_bytes_(snapshot_bytes),
storage_(&storage),
io_buf_(snapshot_bytes, 0),
prefinal_buf_(snapshot_bytes, 0)
{
MFEM_VERIFY(num_steps_ >= 0, "num_steps must be >= 0");
MFEM_VERIFY(num_checkpoints_ > 0, "num_checkpoints must be > 0");
MFEM_VERIFY(snapshot_bytes_ > 0, "snapshot_bytes must be > 0");
MFEM_VERIFY(storage_->MaxSlots() == num_checkpoints_,
"Storage MaxSlots() must match num_checkpoints");
MFEM_VERIFY(storage_->SlotBytes() == snapshot_bytes_,
"Storage SlotBytes() must match snapshot_bytes");
BuildForwardPlanAndInitialControllerState();
Reset();
}
void Reset()
{
forward_shot_cursor_ = 0;
prefinal_valid_ = false;
reverse_started_ = false;
u_work_time_ = -1;
ctrl_ = ctrl_init_;
}
int NumSteps() const { return num_steps_; }
int NumCheckpoints() const { return num_checkpoints_; }
size_t SnapshotBytes() const { return snapshot_bytes_; }
// ForwardStep: called for i=0..num_steps-1
template <typename State, typename PrimalStep, typename MakeSnapshot>
void ForwardStep(int i,
State &u,
PrimalStep &&primal_step,
MakeSnapshot &&make_snapshot)
{
MFEM_VERIFY(0 <= i && i < num_steps_, "ForwardStep: i out of range");
// Take any planned shots at time i (before advancing).
while (forward_shot_cursor_ < (int)forward_shots_.size() &&
forward_shots_[forward_shot_cursor_].time == i)
{
const int slot = forward_shots_[forward_shot_cursor_].slot;
make_snapshot(u, io_buf_.data(), snapshot_bytes_);
storage_->Save(slot, io_buf_.data(), snapshot_bytes_);
forward_shot_cursor_++;
}
// Cache u_{num_steps-1} so reverse can start there (REVOLVE expects capo=num_steps-1).
if (i == num_steps_ - 1)
{
make_snapshot(u, prefinal_buf_.data(), snapshot_bytes_);
prefinal_valid_ = true;
}
// Advance one step.
primal_step(i, u);
}
// BackwardStep: called for i=num_steps-1..0
template <typename State, typename AdjointState,
typename PrimalStep, typename AdjointStep,
typename MakeSnapshot, typename RestoreSnapshot>
void BackwardStep(int i,
AdjointState &lambda,
State &u_work,
PrimalStep &&primal_step,
AdjointStep &&adjoint_step,
MakeSnapshot &&make_snapshot,
RestoreSnapshot &&restore_snapshot)
{
MFEM_VERIFY(0 <= i && i < num_steps_, "BackwardStep: i out of range");
if (!reverse_started_)
{
MFEM_VERIFY(prefinal_valid_ || num_steps_ == 0,
"Reverse started but prefinal state was not captured. "
"Did you run the forward loop through i=num_steps-1?");
ctrl_ = ctrl_init_;
reverse_started_ = true;
if (num_steps_ > 0)
{
// Restore u_{num_steps-1} into u_work.
restore_snapshot(u_work, prefinal_buf_.data(), snapshot_bytes_);
u_work_time_ = num_steps_ - 1;
}
}
// Execute controller actions until we perform exactly one adjoint step.
for (;;)
{
RevolveAction act = ctrl_.Next();
switch (act)
{
case RevolveAction::takeshot:
{
const int slot = ctrl_.Check();
MFEM_VERIFY(u_work_time_ == ctrl_.Capo(),
"takeshot: u_work_time must equal capo");
make_snapshot(u_work, io_buf_.data(), snapshot_bytes_);
storage_->Save(slot, io_buf_.data(), snapshot_bytes_);
break;
}
case RevolveAction::restore:
{
const int slot = ctrl_.Check();
storage_->Load(slot, io_buf_.data(), snapshot_bytes_);
restore_snapshot(u_work, io_buf_.data(), snapshot_bytes_);
u_work_time_ = ctrl_.Capo();
break;
}
case RevolveAction::advance:
{
const int target = ctrl_.Capo();
MFEM_VERIFY(u_work_time_ >= 0, "advance: u_work_time not initialized");
MFEM_VERIFY(target >= u_work_time_, "advance: target < current time");
for (int t = u_work_time_; t < target; ++t)
{
primal_step(t, u_work);
}
u_work_time_ = target;
break;
}
case RevolveAction::firsturn:
case RevolveAction::youturn:
{
// After firsturn/youturn, ctrl_.Fine() has been decremented and equals ctrl_.Capo().
const int step = ctrl_.Fine();
MFEM_VERIFY(step == ctrl_.Capo(), "youturn: fine != capo");
MFEM_VERIFY(step == u_work_time_, "youturn: u_work_time != step");
MFEM_VERIFY(step == i,
"BackwardStep called with i that doesn't match REVOLVE schedule");
// One adjoint step.
adjoint_step(step, u_work, lambda);
return;
}
case RevolveAction::terminate:
{
MFEM_ABORT("REVOLVE terminated early: BackwardStep called after completion?");
break;
}
}
}
}
private:
void BuildForwardPlanAndInitialControllerState()
{
forward_shots_.clear();
RevolveController sim(num_checkpoints_, /*capo=*/0, /*fine=*/num_steps_);
// Forward-plan phase: only TAKESHOT/ADVANCE should occur while fine-capo>1.
while (sim.Fine() - sim.Capo() > 1)
{
RevolveAction a = sim.Next();
if (a == RevolveAction::takeshot)
{
forward_shots_.push_back({sim.Capo(), sim.Check()});
}
else if (a == RevolveAction::advance)
{
// nothing to record; capo moved forward inside sim
}
else
{
MFEM_ABORT("Unexpected REVOLVE action during forward planning phase");
}
}
// At this point, sim is in the pre-firsturn state (capo = num_steps-1, fine = num_steps)
// for num_steps>=1. For num_steps<=1, fine-capo<=1 from the start.
ctrl_init_ = sim;
}
};
}
#endif //MFEM_REVOLVE_CHECKPOINTING_HPP
+362
View File
@@ -0,0 +1,362 @@
#ifndef MFEM_REVOLVE_SEPARATE_FILE_STORAGE_HPP
#define MFEM_REVOLVE_SEPARATE_FILE_STORAGE_HPP
#pragma once
#include "../vector.hpp"
#include <cstdint>
#include <cstdio> // std::remove
#include <fstream>
#include <iomanip>
#include <limits>
#include <sstream>
#include <string>
#include <vector>
#if __cplusplus >= 201703L
#include <filesystem>
#include <system_error>
#else
#if defined(_WIN32)
#include <direct.h> // _mkdir
#else
#include <sys/stat.h> // mkdir
#include <sys/types.h>
#include <errno.h>
#endif
#endif
namespace mfem
{
/**
* @brief Separate-file checkpoint storage for REVOLVE: one file per checkpoint slot.
*
* This storage is "slot indexed":
* slot = 0..max_slots-1
*
* File naming:
* <dir>/<prefix><slot as zero-padded integer><ext>
*
* Example:
* dir="ckpt", prefix="rev_", ext=".bin", max_slots=8
* -> ckpt/rev_00.bin ... ckpt/rev_07.bin
*
* Intended REVOLVE interface:
* int MaxSlots() const;
* size_t SlotBytes() const;
* void Save(int slot, const uint8_t *src, size_t bytes);
* void Load(int slot, uint8_t *dst, size_t bytes) const;
*
* Notes:
* - Slot files are pre-created and resized on construction if truncate_files=true.
* - Save() overwrites the full slot file content from offset 0.
* - Load() reads exactly SlotBytes() from offset 0.
* - If keep_files=false, destructor deletes the slot files.
* - If keep_open=true, keeps N files open (faster, but uses file descriptors).
*/
class RevolveSeparateFileCheckpointStorage
{
public:
RevolveSeparateFileCheckpointStorage(const std::string &directory,
const std::string &prefix,
const std::string &ext,
int max_slots,
std::size_t slot_bytes,
bool create_dir = true,
bool truncate_files = true,
bool keep_files = true,
bool keep_open = false,
bool flush_on_save = false)
: dir_(directory),
prefix_(prefix),
ext_(ext),
max_slots_(max_slots),
slot_bytes_(slot_bytes),
keep_files_(keep_files),
keep_open_(keep_open),
flush_on_save_(flush_on_save)
{
MFEM_VERIFY(max_slots_ > 0,
"RevolveSeparateFileCheckpointStorage: max_slots must be > 0.");
MFEM_VERIFY(slot_bytes_ > 0,
"RevolveSeparateFileCheckpointStorage: slot_bytes must be > 0.");
MFEM_VERIFY(!prefix_.empty(),
"RevolveSeparateFileCheckpointStorage: prefix must not be empty.");
if (create_dir) { EnsureDirectory_(dir_); }
// Precompute slot paths.
slot_paths_.resize((std::size_t)max_slots_);
for (int s = 0; s < max_slots_; ++s)
{
slot_paths_[(std::size_t)s] = MakeSlotPath_(s);
}
if (truncate_files)
{
PrecreateAll_();
}
else
{
// Optional light sanity check: try opening one file. (User may want to reuse existing.)
// We'll rely on Save/Load verification otherwise.
}
if (keep_open_)
{
OpenAll_();
}
}
~RevolveSeparateFileCheckpointStorage()
{
CloseAll_();
if (!keep_files_)
{
RemoveAllFiles_();
}
}
int MaxSlots() const { return max_slots_; }
std::size_t SlotBytes() const { return slot_bytes_; }
/// Return the full path for a given slot (useful for debugging).
const std::string &SlotPath(int slot) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "SlotPath: slot out of range.");
return slot_paths_[(std::size_t)slot];
}
/**
* @brief Save a checkpoint image into slot file.
*
* Requirements:
* - slot in [0, MaxSlots())
* - bytes == SlotBytes()
* - src != nullptr
*/
void Save(int slot, const std::uint8_t *src, std::size_t bytes)
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Save: slot out of range.");
MFEM_VERIFY(bytes == slot_bytes_, "Save: bytes mismatch.");
MFEM_VERIFY(src != nullptr, "Save: src is null.");
if (keep_open_)
{
std::fstream &f = files_[(std::size_t)slot];
MFEM_VERIFY(f.is_open(), "Save: file not open (keep_open).");
f.clear();
f.seekp(0, std::ios::beg);
MFEM_VERIFY(f.good(), "Save: seekp failed (keep_open).");
f.write(reinterpret_cast<const char*>(src), (std::streamsize)slot_bytes_);
MFEM_VERIFY(f.good(), "Save: write failed (keep_open).");
if (flush_on_save_) { f.flush(); }
return;
}
// Open on demand
std::fstream f(SlotPath(slot).c_str(),
std::ios::binary | std::ios::in | std::ios::out);
if (!f.is_open())
{
// If missing, create it sized correctly, then reopen.
PrecreateOne_(slot);
f.open(SlotPath(slot).c_str(), std::ios::binary | std::ios::in | std::ios::out);
}
MFEM_VERIFY(f.is_open(), "Save: failed to open slot file.");
f.seekp(0, std::ios::beg);
MFEM_VERIFY(f.good(), "Save: seekp failed.");
f.write(reinterpret_cast<const char*>(src), (std::streamsize)slot_bytes_);
MFEM_VERIFY(f.good(), "Save: write failed.");
if (flush_on_save_) { f.flush(); }
}
/**
* @brief Load a checkpoint image from slot file.
*
* Requirements:
* - slot in [0, MaxSlots())
* - bytes == SlotBytes()
* - dst != nullptr
*/
void Load(int slot, std::uint8_t *dst, std::size_t bytes) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "Load: slot out of range.");
MFEM_VERIFY(bytes == slot_bytes_, "Load: bytes mismatch.");
MFEM_VERIFY(dst != nullptr, "Load: dst is null.");
if (keep_open_)
{
std::fstream &f = files_[(std::size_t)slot];
MFEM_VERIFY(f.is_open(), "Load: file not open (keep_open).");
f.clear();
f.seekg(0, std::ios::beg);
MFEM_VERIFY(f.good(), "Load: seekg failed (keep_open).");
f.read(reinterpret_cast<char*>(dst), (std::streamsize)slot_bytes_);
MFEM_VERIFY(f.good(), "Load: read failed (keep_open).");
return;
}
std::ifstream f(SlotPath(slot).c_str(), std::ios::binary);
MFEM_VERIFY(f.is_open(), "Load: failed to open slot file.");
f.read(reinterpret_cast<char*>(dst), (std::streamsize)slot_bytes_);
MFEM_VERIFY(f.good(), "Load: read failed.");
}
private:
std::string dir_;
std::string prefix_;
std::string ext_;
int max_slots_ = 0;
std::size_t slot_bytes_ = 0;
bool keep_files_ = true;
bool keep_open_ = false;
bool flush_on_save_ = false;
std::vector<std::string> slot_paths_;
mutable std::vector<std::fstream> files_; // only used if keep_open_==true
static std::string JoinPath_(const std::string &dir, const std::string &file)
{
if (dir.empty()) { return file; }
const char last = dir.back();
if (last == '/' || last == '\\') { return dir + file; }
return dir + "/" + file;
}
int SlotDigits_() const
{
int x = max_slots_ - 1;
int d = 1;
while (x >= 10) { x /= 10; ++d; }
return d;
}
std::string MakeSlotFilename_(int slot) const
{
std::ostringstream os;
os << prefix_
<< std::setw(SlotDigits_()) << std::setfill('0') << slot
<< ext_;
return os.str();
}
std::string MakeSlotPath_(int slot) const
{
return JoinPath_(dir_, MakeSlotFilename_(slot));
}
void PrecreateOne_(int slot) const
{
MFEM_VERIFY(0 <= slot && slot < max_slots_, "PrecreateOne: slot out of range.");
// Create/truncate and set file size to slot_bytes_ by writing last byte.
std::ofstream ofs(SlotPath(slot).c_str(), std::ios::binary | std::ios::trunc);
MFEM_VERIFY(ofs.is_open(), "PrecreateOne: failed to create slot file.");
const std::uint64_t sb = (std::uint64_t)slot_bytes_;
MFEM_VERIFY(sb <= (std::uint64_t)std::numeric_limits<std::streamoff>::max(),
"PrecreateOne: slot_bytes too large for streamoff.");
if (sb > 0)
{
ofs.seekp((std::streamoff)(sb - 1), std::ios::beg);
MFEM_VERIFY(ofs.good(), "PrecreateOne: seekp failed.");
const char zero = 0;
ofs.write(&zero, 1);
MFEM_VERIFY(ofs.good(), "PrecreateOne: size write failed.");
}
}
void PrecreateAll_() const
{
for (int slot = 0; slot < max_slots_; ++slot)
{
PrecreateOne_(slot);
}
}
void OpenAll_()
{
files_.resize((std::size_t)max_slots_);
for (int slot = 0; slot < max_slots_; ++slot)
{
std::fstream &f = files_[(std::size_t)slot];
f.open(SlotPath(slot).c_str(), std::ios::binary | std::ios::in | std::ios::out);
if (!f.is_open())
{
PrecreateOne_(slot);
f.open(SlotPath(slot).c_str(), std::ios::binary | std::ios::in | std::ios::out);
}
MFEM_VERIFY(f.is_open(), "OpenAll: failed to open slot file.");
}
}
void CloseAll_()
{
if (!files_.empty())
{
for (auto &f : files_) { if (f.is_open()) { f.close(); } }
files_.clear();
}
}
void RemoveAllFiles_() const
{
for (int slot = 0; slot < max_slots_; ++slot)
{
// Ignore remove errors (e.g., already removed), but you can tighten if desired.
std::remove(SlotPath(slot).c_str());
}
}
static void EnsureDirectory_(const std::string &dir)
{
if (dir.empty()) { return; }
#if __cplusplus >= 201703L
namespace fs = std::filesystem;
std::error_code ec;
if (!fs::exists(dir, ec))
{
fs::create_directories(dir, ec);
}
MFEM_VERIFY(!ec, "EnsureDirectory: failed to create directory: " + dir);
#else
#if defined(_WIN32)
const int rc = _mkdir(dir.c_str());
if (rc != 0)
{
// If directory already exists, _mkdir fails. We accept that.
// There's no reliable portable "exists" check in pre-C++17 without more code.
}
#else
const int rc = mkdir(dir.c_str(), 0755);
if (rc != 0 && errno != EEXIST)
{
MFEM_ABORT("EnsureDirectory: failed to create directory: " + dir);
}
#endif
#endif
}
};
} // namespace mfem
#endif // MFEM_REVOLVE_SEPARATE_FILE_STORAGE_HPP
+40 -9
View File
@@ -15,10 +15,20 @@
#ifdef MFEM_USE_CUDSS
#if CUDSS_VERSION >= 800
#ifdef MFEM_USE_SINGLE
#define CUDA_REAL_T CUDA_R_32F
#define CUDSS_REAL_T CUDSS_R_32F
#else
#define CUDA_REAL_T CUDA_R_64F
#define CUDSS_REAL_T CUDSS_R_64F
#endif
#define CUDSS_INT_T CUDSS_R_32I
#else
#ifdef MFEM_USE_SINGLE
#define CUDSS_REAL_T CUDA_R_32F
#else
#define CUDSS_REAL_T CUDA_R_64F
#endif
#define CUDSS_INT_T CUDA_R_32I
#endif
// Define a cuDSS error check macro, MFEM_CUDSS_CHECK(x), where x returns/is of
@@ -65,8 +75,13 @@ CuDSSSolver::CuDSSSolver(MPI_Comm comm_) : mpi_comm(comm_)
#endif
MFEM_CUDSS_CHECK(cudssSetCommLayer(handle, comm_lib));
#if CUDSS_VERSION >= 800
MFEM_CUDSS_CHECK(cudssDataSet(handle, solverData, CUDSS_DATA_COMM_HOST,
&mpi_comm, sizeof(MPI_Comm *)));
#else
MFEM_CUDSS_CHECK(cudssDataSet(handle, solverData, CUDSS_DATA_COMM,
&mpi_comm, sizeof(MPI_Comm *)));
#endif
}
#endif // MFEM_USE_MPI
@@ -257,11 +272,19 @@ void CuDSSSolver::SetMatrixCuDSS(int *csr_offsets, int *csr_columns,
CuMemcpyDtoD(csr_offsets_d, csr_offsets, (n_loc + 1) * sizeof(int));
CuMemcpyDtoD(csr_columns_d, csr_columns, nnz * sizeof(int));
#if CUDSS_VERSION >= 800
MFEM_CUDSS_CHECK(
cudssMatrixCreateCsr(
Ac.get(), n_global, n_global, nnz, csr_offsets_d, NULL,
csr_columns_d, csr_values_d, CUDA_R_32I, CUDA_REAL_T, mat_type, mview,
CUDSS_BASE_ZERO));
csr_columns_d, csr_values_d, CUDSS_INT_T, CUDSS_INT_T, CUDSS_REAL_T,
mat_type, mview, CUDSS_BASE_ZERO));
#else
MFEM_CUDSS_CHECK(
cudssMatrixCreateCsr(
Ac.get(), n_global, n_global, nnz, csr_offsets_d, NULL,
csr_columns_d, csr_values_d, CUDSS_INT_T, CUDSS_REAL_T,
mat_type, mview, CUDSS_BASE_ZERO));
#endif
}
else // !reorder_reuse
{
@@ -269,11 +292,19 @@ void CuDSSSolver::SetMatrixCuDSS(int *csr_offsets, int *csr_columns,
{
MFEM_CUDSS_CHECK(cudssMatrixDestroy(*Ac));
}
#if CUDSS_VERSION >= 800
MFEM_CUDSS_CHECK(
cudssMatrixCreateCsr(
Ac.get(), n_global, n_global, nnz, csr_offsets, NULL, csr_columns,
csr_values_d, CUDA_R_32I, CUDA_REAL_T, mat_type, mview,
CUDSS_BASE_ZERO));
Ac.get(), n_global, n_global, nnz, csr_offsets, NULL,
csr_columns, csr_values_d, CUDSS_INT_T, CUDSS_INT_T, CUDSS_REAL_T,
mat_type, mview, CUDSS_BASE_ZERO));
#else
MFEM_CUDSS_CHECK(
cudssMatrixCreateCsr(
Ac.get(), n_global, n_global, nnz, csr_offsets, NULL,
csr_columns, csr_values_d, CUDSS_INT_T, CUDSS_REAL_T,
mat_type, mview, CUDSS_BASE_ZERO));
#endif
}
#ifdef MFEM_USE_MPI
if (Mpi::IsInitialized())
@@ -334,10 +365,10 @@ void CuDSSSolver::SetNumRHS(int nrhs_) const
}
// Create empty RHS and solution vectors
MFEM_CUDSS_CHECK(cudssMatrixCreateDn(&xc, n_global, nrhs_, n_global, NULL,
CUDA_REAL_T, CUDSS_LAYOUT_COL_MAJOR));
CUDSS_REAL_T, CUDSS_LAYOUT_COL_MAJOR));
MFEM_CUDSS_CHECK(cudssMatrixCreateDn(&yc, n_global, nrhs_, n_global, NULL,
CUDA_REAL_T, CUDSS_LAYOUT_COL_MAJOR));
CUDSS_REAL_T, CUDSS_LAYOUT_COL_MAJOR));
#ifdef MFEM_USE_MPI
MFEM_CUDSS_CHECK(cudssMatrixSetDistributionRow1d(xc, row_start, row_end));
+2
View File
@@ -39,6 +39,7 @@ namespace Ginkgo
{
template <typename T> using gko_array = gko::array<T>;
#if defined(MFEM_USE_MPI) && GINKGO_BUILD_MPI
// for inter-operability with hypre integer types
using gko_hypre_int =
std::conditional_t<sizeof(HYPRE_Int) == sizeof(std::int32_t), std::int32_t,
@@ -50,6 +51,7 @@ static_assert(!std::is_void_v<gko_hypre_int>,
"HYPRE_Int type is incompatible with Ginkgo");
static_assert(!std::is_void_v<gko_hypre_bigint>,
"HYPRE_BigInt type is incompatible with Ginkgo");
#endif
/**
* Helper class for a case where a wrapped MFEM Vector
+29 -3
View File
@@ -2872,8 +2872,8 @@ void HypreParMatrix::Destroy()
if (HypreUsingGPU() && ParCSROwner && (diagOwner < 0 || offdOwner < 0))
{
// Put the "host" or "hypre" pointers in {i,j,data} of A->{diag,offd}, so
// that they can be destroyed by hypre when hypre_ParCSRMatrixDestroy(A)
// is called below.
// that they can be destroyed by mfem_hypre_TFree_host() or hypre when
// hypre_ParCSRMatrixDestroy(A) is called below, respectively.
// Check that if both diagOwner and offdOwner are negative then they have
// the same value.
@@ -2882,7 +2882,33 @@ void HypreParMatrix::Destroy()
MemoryClass mc = (diagOwner == -1 || offdOwner == -1) ?
Device::GetHostMemoryClass() : GetHypreMemoryClass();
Write(mc, diagOwner < 0, offdOwner <0);
Write(mc, diagOwner < 0, offdOwner < 0);
if (diagOwner == -1)
{
// Note: mfem_hypre_TFree_host() sets the pointer to NULL.
mfem_hypre_TFree_host(hypre_CSRMatrixI(A->diag));
if (hypre_CSRMatrixOwnsData(A->diag))
{
mfem_hypre_TFree_host(hypre_CSRMatrixJ(A->diag));
mfem_hypre_TFree_host(hypre_CSRMatrixData(A->diag));
}
#if MFEM_HYPRE_VERSION >= 21800
hypre_CSRMatrixMemoryLocation(A->diag) = GetHypreMemoryLocation();
#endif
}
if (offdOwner == -1)
{
// Note: mfem_hypre_TFree_host() sets the pointer to NULL.
mfem_hypre_TFree_host(hypre_CSRMatrixI(A->offd));
if (hypre_CSRMatrixOwnsData(A->offd))
{
mfem_hypre_TFree_host(hypre_CSRMatrixJ(A->offd));
mfem_hypre_TFree_host(hypre_CSRMatrixData(A->offd));
}
#if MFEM_HYPRE_VERSION >= 21800
hypre_CSRMatrixMemoryLocation(A->offd) = GetHypreMemoryLocation();
#endif
}
}
#endif
+5 -4
View File
@@ -432,10 +432,11 @@ private:
// and A->col_map_offd.
// The possible values for diagOwner are:
// -1: no special treatment of A->diag (default)
// when hypre is built with CUDA support, A->diag owns the "host"
// pointers (according to A->diag->owns_data)
// -2: used when hypre is built with CUDA support, A->diag owns the "hypre"
// pointers (according to A->diag->owns_data)
// when hypre is using GPU, A->diag owns the "host" pointers (according
// to A->diag->owns_data); these host pointers are freed by MFEM using
// hypre's host deallocation macros
// -2: used when hypre is using GPU, A->diag owns the "hypre" pointers
// (according to A->diag->owns_data)
// 0: prevent hypre from destroying A->diag->{i,j,data}
// 1: same as 0, plus own the "host" A->diag->{i,j}
// 2: same as 0, plus own the "host" A->diag->data
+1
View File
@@ -40,6 +40,7 @@
#include "filteredsolver.hpp"
#include "ordering.hpp"
#include "particlevector.hpp"
#include "chpt/chpt.hpp"
#ifdef MFEM_USE_AMGX
#include "amgxsolver.hpp"
+112 -39
View File
@@ -10,6 +10,7 @@
// CONTRIBUTING.md for details.
#include "particlevector.hpp"
#include "../general/forall.hpp"
namespace mfem
{
@@ -46,20 +47,38 @@ void ParticleVector::GetValues(int i, Vector &nvals) const
{
nvals.SetSize(vdim);
if (ordering == Ordering::byNODES)
const bool nvals_use_dev = nvals.UseDevice();
// Use ParticleVector's device flag to minimize movement from large source
const bool use_dev = UseDevice();
const auto d_src = Read(use_dev);
auto d_dest = nvals.Write(use_dev);
const int vdim_ = vdim;
const int ordering_ = (int)ordering;
const int nv = (ordering == Ordering::byNODES) ? size / vdim : 0;
mfem::forall_switch(use_dev, vdim_, [=] MFEM_HOST_DEVICE (int c)
{
int nv = GetNumParticles();
for (int c = 0; c < vdim; c++)
if (ordering_ == Ordering::byNODES)
{
nvals[c] = Vector::operator[](i+nv*c);
d_dest[c] = d_src[i + nv*c];
}
else
{
d_dest[c] = d_src[c + vdim_*i];
}
});
// If nvals was not using device but ParticleVector is, copy back to host
if (!nvals_use_dev && use_dev)
{
nvals.HostRead();
nvals.UseDevice(false);
}
else
// If nvals was using device but ParticleVector is not, copy back to device
if (!use_dev && nvals_use_dev)
{
for (int c = 0; c < vdim; c++)
{
nvals[c] = Vector::operator[](c+vdim*i);
}
nvals.Read();
}
}
@@ -99,21 +118,27 @@ void ParticleVector::GetComponentsRef(int vd, Vector &nref)
void ParticleVector::SetValues(int i, const Vector &nvals)
{
if (ordering == Ordering::byNODES)
const bool use_dev = UseDevice(); // use ParticleVector's device flag
const auto mc = use_dev ? Device::GetDeviceMemoryClass()
: Device::GetHostMemoryClass();
auto d_dest = ReadWrite(use_dev);
const auto d_src = nvals.GetMemory().Read(mc, nvals.Size());
const int vdim_ = vdim;
const int ordering_ = (int)ordering;
const int nv = (ordering == Ordering::byNODES) ? size / vdim : 0;
mfem::forall_switch(use_dev, vdim_, [=] MFEM_HOST_DEVICE (int c)
{
int nv = GetNumParticles();
for (int c = 0; c < vdim; c++)
if (ordering_ == Ordering::byNODES)
{
Vector::operator[](i + c*nv) = nvals[c];
d_dest[i + c*nv] = d_src[c];
}
}
else
{
for (int c = 0; c < vdim; c++)
else
{
Vector::operator[](c + i*vdim) = nvals[c];
d_dest[c + i*vdim_] = d_src[c];
}
}
});
}
void ParticleVector::SetComponents(int vd, const Vector &comp)
@@ -144,6 +169,9 @@ real_t& ParticleVector::operator()(int i, int comp)
"Component index " << comp <<
" is invalid for vector dimension " << vdim);
// non-const so we make host flag valid in case user modifies data
HostReadWrite();
if (ordering == Ordering::byNODES)
{
return Vector::operator[](i + comp*GetNumParticles());
@@ -163,6 +191,8 @@ const real_t& ParticleVector::operator()(int i, int comp) const
"Component index " << comp <<
" is invalid for vector dimension " << vdim);
HostRead();
if (ordering == Ordering::byNODES)
{
return Vector::operator[](i + comp*GetNumParticles());
@@ -240,9 +270,37 @@ void ParticleVector::SetVDim(int vdim_, bool keep_data)
void ParticleVector::SetOrdering(Ordering::Type ordering_, bool keep_data)
{
if (keep_data)
if (keep_data && ordering != ordering_)
{
Ordering::Reorder(*this, vdim, ordering, ordering_);
int num_particles = GetNumParticles();
// create deep copy of old data that will be copied
Vector old_data(*this);
const bool use_dev = UseDevice();
const auto d_src = old_data.Read(use_dev);
auto d_dest = Write(use_dev);
const int vdim_ = vdim;
const int size_ = size;
if (ordering_ == Ordering::byNODES) // byVDIM -> byNODES
{
mfem::forall_switch(use_dev, size_, [=] MFEM_HOST_DEVICE (int k)
{
int i = k / vdim_; // src particle index
int d = k % vdim_; // src component index
d_dest[i + d * num_particles] = d_src[k];
});
}
else // byNODES -> byVDIM
{
mfem::forall_switch(use_dev, size_, [=] MFEM_HOST_DEVICE (int k)
{
int d = k / num_particles; // src component index
int i = k % num_particles; // src particle index
d_dest[d + i * vdim_] = d_src[k];
});
}
}
ordering = ordering_;
}
@@ -270,32 +328,47 @@ void ParticleVector::SetNumParticles(int num_vectors, bool keep_data)
if (!keep_data) { return; }
const bool use_dev = UseDevice();
auto d_dest = this->ReadWrite(use_dev);
if (ordering == Ordering::byNODES)
{
// Shift entries for byNODES
for (int c = vdim-1; c > 0; c--)
{
for (int i = old_nv-1; i >= 0; i--)
{
Vector::operator[](i+c*num_vectors) = Vector::operator[](i+c*old_nv);
}
}
// create deep copy of old data that will be copied
Vector old_slice;
old_slice.MakeRef(*this, 0, old_nv * vdim);
Vector old_copy(old_slice);
// Zero-out data now associated with new Vectors
for (int c = 0; c < vdim; c++)
const auto d_src = old_copy.Read(use_dev);
const int vdim_ = vdim;
// Shift entries for byNODES
mfem::forall_switch(use_dev, old_nv * vdim_,
[=] MFEM_HOST_DEVICE (int k)
{
for (int i = old_nv; i < num_vectors; i++)
{
Vector::operator[](i+c*num_vectors) = 0.0;
}
}
const int d = k / old_nv;
const int i = k % old_nv;
d_dest[i + d*num_vectors] = d_src[k];
});
// Zero-out new data slots
const int diff = num_vectors - old_nv;
mfem::forall_switch(use_dev, diff * vdim,
[=] MFEM_HOST_DEVICE (int k)
{
const int d = k / diff;
const int i = k % diff;
d_dest[d * num_vectors + old_nv + i] = 0.0;
});
}
else // byVDIM
{
for (int i = old_nv*vdim; i < num_vectors*vdim; i++)
const int start_idx = old_nv * vdim;
const int end_idx = num_vectors * vdim;
const int diff = end_idx - start_idx;
mfem::forall_switch(use_dev, diff, [=] MFEM_HOST_DEVICE (int i)
{
data[i] = 0.0;
}
d_dest[start_idx + i] = 0.0;
});
}
}
else // Else just remove the trailing vector data
+1
View File
@@ -811,6 +811,7 @@ MINIAPPS_SUBDIRS = dpg/util hooke/operators hooke/preconditioners \
FORMAT_FILES += $(foreach dir,$(TESTS_SUBDIRS),tests/$(dir)/*.?pp)
FORMAT_FILES += $(foreach dir,$(UNIT_TESTS_SUBDIRS),tests/unit/$(dir)/*.?pp)
FORMAT_FILES += $(foreach dir,$(MINIAPPS_SUBDIRS),miniapps/$(dir)/*.?pp)
FORMAT_FILES += linalg/chpt/*.?pp miniapps/mtop/chpt/*.?pp
FORMAT_FILES += config/cmake/config.hpp.in config/config.hpp.in mfem*.hpp
FORMAT_EXCLUDE = general/tinyxml2.cpp tests/unit/catch.hpp
FORMAT_LIST = $(filter-out $(FORMAT_EXCLUDE),$(wildcard $(FORMAT_FILES)))
+1
View File
@@ -31,6 +31,7 @@ add_subdirectory(hdiv-linear-solver)
add_subdirectory(hooke)
add_subdirectory(meshing)
add_subdirectory(mtop)
add_subdirectory(mtop/chpt)
add_subdirectory(multidomain)
add_subdirectory(nurbs)
add_subdirectory(parelag)
+4
View File
@@ -126,6 +126,7 @@ void VisualizeParticles(socketstream &sock, const char* vishost, int visport,
{
Vector pcoords;
pset.Coords().GetValues(i, pcoords);
pcoords.HostRead();
if (dim == 2)
{
Add2DPoint(pcoords, particles_mesh, psize);
@@ -139,6 +140,7 @@ void VisualizeParticles(socketstream &sock, const char* vishost, int visport,
FiniteElementSpace fes(&particles_mesh, &l2fec, 1);
GridFunction gf(&fes);
gf.HostWrite();
for (int i = 0; i < pset.GetNParticles(); i++)
{
@@ -193,6 +195,7 @@ void ParticleTrajectories::AddSegmentStart()
{
Vector pcoords;
pset.Coords().GetValues(i, pcoords);
pcoords.HostRead();
segment_meshes.front().AddVertex(pcoords);
}
}
@@ -213,6 +216,7 @@ void ParticleTrajectories::SetSegmentEnd()
{
Vector pcoords;
pset.Coords().GetValues(pidx, pcoords);
pcoords.HostRead();
segment_meshes.front().AddVertex(pcoords);
}
else // Otherwise set its end vertex == start vertex
+147 -7
View File
@@ -91,6 +91,7 @@ struct LorentzContext
int nt = 1000; // number of timesteps
int redist_interval = 5; // redistribution interval
int redist_mesh = 0; // redistribution mesh: 0: E mesh, 1: B mesh
std::string device_config = "cpu";
} ctx;
/// This class implements the Boris algorithm as described in the article
@@ -130,7 +131,7 @@ protected:
public:
Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
int nparticles, Ordering::Type pdata_ordering);
int nparticles, Ordering::Type pdata_ordering, bool use_device);
/// Find Particles in mesh corresponding to E and B fields
void FindParticles();
@@ -139,9 +140,12 @@ public:
/// right after FindParticles has been called.
void EvaluateFieldsAtParticles();
/// Advance particles one time step using Boris algorithm
/// Advance particles one time step using Boris algorithm. Host version.
void Step(real_t &t, real_t &dt);
/// Advance particles one time step using Boris algorithm. Device version.
void StepDevice(real_t &t, real_t &dt);
/// Remove lost particles and return their indices
Array<int> RemoveLostParticles();
@@ -235,6 +239,8 @@ int main(int argc, char *argv[])
args.AddOption(&vis_interval, "-vf", "--vis-interval",
"GLVis visualization update after this many timesteps. "
"0 means no visualization.");
args.AddOption(&ctx.device_config, "-d", "--device",
"Device configuration definition string.");
args.Parse();
if (!args.Good())
@@ -251,6 +257,10 @@ int main(int argc, char *argv[])
args.PrintOptions(cout);
}
Device device(ctx.device_config);
if (Mpi::Root()) { device.Print(); }
bool use_device = (ctx.device_config != "cpu") && Device::IsEnabled();
std::unique_ptr<VisItDataCollection> E_dc, B_dc;
ParGridFunction *E_gf = nullptr, *B_gf = nullptr;
Vector bb_xmin, bb_xmax;
@@ -266,6 +276,7 @@ int main(int argc, char *argv[])
return 1;
}
E_gf->ParFESpace()->GetParMesh()->GetBoundingBox(bb_xmin, bb_xmax, 2);
E_gf->UseDevice(use_device);
}
// Read B field if provided
@@ -280,6 +291,7 @@ int main(int argc, char *argv[])
}
Vector bb_xmint, bb_xmaxt;
B_gf->ParFESpace()->GetParMesh()->GetBoundingBox(bb_xmint, bb_xmaxt, 2);
B_gf->UseDevice(use_device);
if (ctx.E.coll_name != "")
{
// compute intersection of bounding boxes
@@ -302,10 +314,14 @@ int main(int argc, char *argv[])
// Initialize particles
int num_particles = ctx.npt/num_ranks +
(rank < (ctx.npt % num_ranks) ? 1 : 0);
Boris boris(MPI_COMM_WORLD, E_gf, B_gf, num_particles, ordering_type);
Boris boris(MPI_COMM_WORLD, E_gf, B_gf, num_particles, ordering_type,
use_device);
InitializeChargedParticles(boris.GetParticles(), ctx.x_min, ctx.x_max,
ctx.p_min, ctx.p_max, ctx.m, ctx.q);
Array<int> removed_idxs_dummy;
boris.FindParticles();
boris.Redistribute(ctx.redist_mesh, removed_idxs_dummy);
boris.EvaluateFieldsAtParticles();
real_t t = 0.0;
@@ -329,7 +345,14 @@ int main(int argc, char *argv[])
for (int step = 1; step <= ctx.nt; step++)
{
// Step the Boris algorithm
boris.Step(t, dt);
if (use_device)
{
boris.StepDevice(t, dt);
}
else
{
boris.Step(t, dt);
}
if (Mpi::Root())
{
mfem::out << "Step: " << step << " | Time: " << t << endl;
@@ -397,7 +420,7 @@ void Boris::ParticleStep(Particle &part, real_t &dt)
}
Boris::Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
int nparticles, Ordering::Type pdata_ordering)
int nparticles, Ordering::Type pdata_ordering, bool use_device)
: E_gf(E_gf_),
B_gf(B_gf_),
E_finder(comm),
@@ -426,6 +449,7 @@ Boris::Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
}
int dim = E_mesh ? E_mesh->SpaceDimension() : B_mesh->SpaceDimension();
MFEM_VERIFY(dim == 3, "Only 3D meshes are currently supported.");
pxB_.SetSize(dim); pm_.SetSize(dim); pp_.SetSize(dim);
@@ -435,7 +459,8 @@ Boris::Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
Array<int> field_vdims({1, 1, dim, dim, dim});
charged_particles = std::make_unique<ParticleSet>
(comm, nparticles, dim, field_vdims, 0, pdata_ordering);
(comm, nparticles, dim, field_vdims, 0, pdata_ordering,
use_device);
}
void Boris::FindParticles()
@@ -481,7 +506,6 @@ void Boris::Step(real_t &t, real_t &dt)
{
// Interpolate E and B fields onto particles
EvaluateFieldsAtParticles();
// Individually step each particle. If all ParticleSet fields are ordered
// byVDIM, we can use GetParticleRef for better performance.
if (charged_particles->IsParticleRefValid())
@@ -509,6 +533,112 @@ void Boris::Step(real_t &t, real_t &dt)
t += dt;
}
void Boris::StepDevice(real_t &t, real_t &dt)
{
// Interpolate E and B fields onto particles
EvaluateFieldsAtParticles();
const int N = charged_particles->GetNParticles();
auto &X = charged_particles->Coords();
auto &M = charged_particles->Field(MASS);
auto &Q = charged_particles->Field(CHARGE);
auto &P = charged_particles->Field(MOM);
auto &E = charged_particles->Field(EFIELD);
auto &B = charged_particles->Field(BFIELD);
const int dim = X.GetVDim();
// Capture orderings for each field to ensure correct access
const bool byVDIM_X = (X.GetOrdering() == Ordering::byVDIM);
const bool byVDIM_P = (P.GetOrdering() == Ordering::byVDIM);
const bool byVDIM_E = (E.GetOrdering() == Ordering::byVDIM);
const bool byVDIM_B = (B.GetOrdering() == Ordering::byVDIM);
auto d_x = X.ReadWrite();
auto d_m = M.Read();
auto d_q = Q.Read();
auto d_p = P.ReadWrite();
auto d_e = E.Read();
auto d_b = B.Read();
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i)
{
const real_t m = d_m[i];
const real_t q = d_q[i];
real_t x[3], p[3], e[3], b[3];
// Load data
for (int d = 0; d < dim; d++)
{
x[d] = d_x[byVDIM_X ? i * dim + d : i + d * N];
p[d] = d_p[byVDIM_P ? i * dim + d : i + d * N];
e[d] = d_e[byVDIM_E ? i * dim + d : i + d * N];
b[d] = d_b[byVDIM_B ? i * dim + d : i + d * N];
}
// Boris algorithm implementation
real_t pm[3], pxB[3], pp[3];
// Compute half of the contribution from q E
// pm = p + 0.5 * dt * q * e
for (int d = 0; d < dim; d++)
{
pm[d] = p[d] + (0.5 * dt * q) * e[d];
}
// Compute the contribution from q p x B
real_t B2 = 0.0;
for (int d = 0; d < dim; d++) { B2 += b[d] * b[d]; }
// ... along pm x B
// pxB = pm x b
pxB[0] = pm[1] * b[2] - pm[2] * b[1];
pxB[1] = pm[2] * b[0] - pm[0] * b[2];
pxB[2] = pm[0] * b[1] - pm[1] * b[0];
// pp = a1 * pxB
const real_t a1 = 4.0 * dt * q * m;
for (int d = 0; d < dim; d++) { pp[d] = a1 * pxB[d]; }
// ... along pm
// pp += a2 * pm
const real_t a2 = 4.0 * m * m - dt * dt * q * q * B2;
for (int d = 0; d < dim; d++) { pp[d] += a2 * pm[d]; }
// ... along B
real_t b_dot_pm = 0.0;
for (int d = 0; d < dim; d++) { b_dot_pm += b[d] * pm[d]; }
const real_t a3 = 2.0 * dt * dt * q * q * b_dot_pm;
// pp += a3 * b
for (int d = 0; d < dim; d++) { pp[d] += a3 * b[d]; }
// scale by common denominator
const real_t a4 = 4.0 * m * m + dt * dt * q * q * B2;
for (int d = 0; d < dim; d++) { pp[d] /= a4; }
// Update the momentum
// p = pp + 0.5 * dt * q * e
for (int d = 0; d < dim; d++)
{
p[d] = pp[d] + (0.5 * dt * q) * e[d];
}
// Update the position
// x += (dt / m) * p
// Store back to global arrays
for (int d = 0; d < dim; d++)
{
d_p[byVDIM_P ? i * dim + d : i + d * N] = p[d];
d_x[byVDIM_X ? i * dim + d : i + d * N] = x[d] + (dt / m) * p[d];
}
});
// Find updated particle locations in E and B field meshes
FindParticles();
// Update time
t += dt;
}
Array<int> Boris::RemoveLostParticles()
{
Array<int> lost_idxs;
@@ -617,6 +747,11 @@ void InitializeChargedParticles(ParticleSet &charged_particles,
ParticleVector &M = charged_particles.Field(Boris::MASS);
ParticleVector &Q = charged_particles.Field(Boris::CHARGE);
X.HostWrite();
P.HostWrite();
M.HostWrite();
Q.HostWrite();
for (int i = 0; i < charged_particles.GetNParticles(); i++)
{
for (int d = 0; d < dim; d++)
@@ -643,4 +778,9 @@ void InitializeChargedParticles(ParticleSet &charged_particles,
M(i) = m;
Q(i) = q;
}
X.Read();
P.Read();
M.Read();
Q.Read();
}
+19 -3
View File
@@ -49,6 +49,9 @@
// findpts -m ../../data/ref-square.mesh -o 2 -mo 1 -random 1 -surf
// findpts -m ../../data/ref-cube.mesh -o 2 -mo 1 -random 1 -surf
// findpts -m ../../data/square-disc-p2.mesh -o 4 -mo 2 -random 1 -surf
// Surface meshes + bounding box size increase:
// findpts -m ../../data/square-disc-p2.mesh -o 4 -mo 2 -random 1 -surf -sabs 0.1
// findpts -m ../../data/tinyzoo-3d.mesh -o 4 -mo 2 -random 1 -surf -sabs 0.1
#include "mfem.hpp"
#include "../common/mfem-common.hpp"
@@ -109,6 +112,7 @@ int main (int argc, char *argv[])
int randomization = 0;
int npt = 100;
bool surface = false;
double surf_aabb_sz_inc = 0.0;
// Parse command-line options.
OptionsParser args(argc, argv);
@@ -150,6 +154,9 @@ int main (int argc, char *argv[])
args.AddOption(&surface, "-surf", "--surface", "-no-surf",
"--no-surface",
"Extract surface mesh from volume mesh.");
args.AddOption(&surf_aabb_sz_inc, "-sabs", "--surface-aabb-size-inc",
"Absolute AABB expansion applied to surface-search "
"axis-aligned bounding boxes in FindPointsGSLIB surface meshes.");
args.Parse();
if (!args.Good())
@@ -384,8 +391,17 @@ int main (int argc, char *argv[])
// Find and Interpolate FE function values on the desired points.
Vector interp_vals(pts_cnt*vec_dim);
FindPointsGSLIB finder(*mesh);
finder.SetDistanceToleranceForPointsFoundOnBoundary(10);
FindPointsGSLIB finder;
if (surface && surf_aabb_sz_inc > 0.0)
{
Vector bb_size({surf_aabb_sz_inc});
finder.SetupSurfWithAABBExpansion(*mesh, bb_size);
}
else
{
finder.Setup(*mesh);
// finder.SetDistanceToleranceForPointsFoundOnBoundary(10);
}
finder.SetL2AvgType(FindPointsGSLIB::NONE);
finder.Interpolate(vxyz, field_vals, interp_vals, point_ordering);
Array<unsigned int> code_out = finder.GetCode();
@@ -424,7 +440,7 @@ int main (int argc, char *argv[])
<< "Searched points: " << pts_cnt
<< "\nFound points: " << found
<< "\nMax interp error: " << max_err
<< "\nMax dist (of found): " << max_dist
<< "\nMax dist^2 (of found): " << max_dist
<< "\nPoints not found: " << not_found;
if (randomization == 1)
{
+22 -6
View File
@@ -48,11 +48,14 @@
// Device runs:
// mpirun -np 2 pfindpts -m ../../data/inline-quad.mesh -o 3 -mo 2 -random 1 -d debug
// mpirun -np 2 pfindpts -m ../../data/amr-quad.mesh -rs 1 -o 4 -mo 2 -random 1 -npt 100 -d debug
// mpirun -np 2 pfindpts -m ../../data/inline-hex.mesh -o 3 -mo 2 -random 1 -d debug
// mpirun -np 2 pfindpts -m ../../data/inline-hex.mesh -o 3 -mo 2 -random 1 -d debug -ft 1
// Surface meshes:
// mpirun -np 4 pfindpts -m ../../data/square-disc-p2.mesh -o 4 -mo 2 -vis -random 1 -surf
// mpirun -np 4 pfindpts -m ../../data/star-q3.mesh -o 6 -mo 3 -vis -random 1 -surf
// mpirun -np 4 pfindpts -m ../../data/fichera-q2.mesh -o 6 -mo 3 -vis -random 1 -surf
// Surface meshes + bounding box size increase:
// mpirun -np 4 pfindpts -m ../../data/square-disc-p2.mesh -o 4 -mo 2 -vis -random 1 -surf -sabs 0.1
// mpirun -np 4 pfindpts -m ../../data/tinyzoo-3d.mesh -o 4 -mo 2 -vis -random 1 -surf -sabs 0.1
#include "mfem.hpp"
#include "../common/mfem-common.hpp"
@@ -102,6 +105,7 @@ int main (int argc, char *argv[])
int randomization = 0;
int npt = 100; //points per proc
bool surface = false;
double surf_aabb_sz_inc = 0.0;
// Parse command-line options.
OptionsParser args(argc, argv);
@@ -145,7 +149,9 @@ int main (int argc, char *argv[])
args.AddOption(&surface, "-surf", "--surface", "-no-surf",
"--no-surface",
"Extract surface mesh from volume mesh.");
args.AddOption(&surf_aabb_sz_inc, "-sabs", "--surface-aabb-size-inc",
"Absolute AABB expansion applied to surface-search "
"axis-aligned bounding boxes in FindPointsGSLIB surface meshes.");
args.Parse();
if (!args.Good())
{
@@ -343,7 +349,7 @@ int main (int argc, char *argv[])
Geometry::GetRandomPoint(geom, ip);
if (j < npt_face_per_elem)
{
ip.x = 0.0; // force point to be on the face
ip.x = 0.0; // force point to be on a face
npt_total_face++;
}
Vector pos_i(sdim);
@@ -373,8 +379,17 @@ int main (int argc, char *argv[])
// Find and Interpolate FE function values on the desired points.
Vector interp_vals(pts_cnt*vec_dim);
FindPointsGSLIB finder(pmesh);
finder.SetDistanceToleranceForPointsFoundOnBoundary(10);
FindPointsGSLIB finder;
if (surface && surf_aabb_sz_inc > 0.0)
{
Vector bb_size({surf_aabb_sz_inc});
finder.SetupSurfWithAABBExpansion(pmesh, bb_size);
}
else
{
finder.Setup(pmesh);
}
// finder.SetDistanceToleranceForPointsFoundOnBoundary(1e-10);
// Enable GPU to CPU fallback for GPUData only if you are using an older
// version of GSLIB.
// finder.SetGPUtoCPUFallback(true);
@@ -456,10 +471,11 @@ int main (int argc, char *argv[])
<< "\nPoints on faces: " << face_pts << " out of "
<< npt_total_face
<< "\nMax interp error: " << max_error
<< "\nMax dist (of found): " << max_dist
<< "\nMax dist^2 (of found): " << max_dist
<< endl;
}
delete fec;
if (randomization != 0)
+68
View File
@@ -0,0 +1,68 @@
# 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.
list(APPEND CHPT_SOURCES )
list(APPEND CHPT_HEADERS )
convert_filenames_to_full_paths(CHPT_SOURCES)
convert_filenames_to_full_paths(CHPT_HEADERS)
set(CHPT_COMMON_FILES
EXTRA_SOURCES ${CHPT_SOURCES}
EXTRA_HEADERS ${CHPT_HEADERS})
add_mfem_miniapp(mtop_ex_scalar_chpt
MAIN ex_scalar_field.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_ex_adapt_chpt
MAIN ex_scalar_adaptive.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_ex_vector_chpt
MAIN ex_vector_field.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_ex_fixedslot
MAIN ex_fixedslot_scalar_vector.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
# add_mfem_miniapp(mtop_ex_segmentio
# MAIN ex_segmentio_scalar_vector_mfem.cpp
# ${CHPT_COMMON_FILES}
# LIBRARIES mfem)
add_mfem_miniapp(mtop_ex_file
MAIN ex_fileio_scalar_vector_mfem.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_ex_revolve
MAIN ex_revolve.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_ex_dyn
MAIN ex_dynamic_chpt.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
add_mfem_miniapp(mtop_ex_rev_fixedslot
MAIN ex_revolve_chpt_fixedslot.cpp
${CHPT_COMMON_FILES}
LIBRARIES mfem)
+718
View File
@@ -0,0 +1,718 @@
#include "mfem.hpp"
#include <cmath>
#include <iomanip>
#include <string>
using namespace mfem;
/************************************************************************
* The forward state consists of vector and several additional values.
* The goal is to demonstrate how different storages can be used together
* with DynamicCheckpointing in order to avoid unnecessary memory
* allocations, data copies, and deallocations.
* *********************************************************************/
struct State
{
mfem::real_t time = 0.0;
mfem::real_t obj = 0.0;
mfem::Vector v;
};
// ---------------------------
// Snapshot type used by storage:
// a lightweight view (non-owning).
// ---------------------------
struct StateSnapshotView
{
mfem::real_t time = 0.0;
mfem::real_t obj = 0.0;
// Points to n*sizeof(real_t) bytes.
// - during Store(): points to State::v host data
// - during Read(): points into storage's internal scratch buffer
const unsigned char *v_bytes = nullptr;
};
// ---------------------------
// Packer for fixed-slot storage
// Layout in slot bytes:
// [ time | obj | v[0..n-1] ]
// ---------------------------
class StateSnapshotViewPacker
{
public:
explicit StateSnapshotViewPacker(int n) : n_(n)
{
MFEM_VERIFY(n_ > 0, "StateSnapshotViewPacker: n must be > 0.");
}
std::size_t SlotBytes() const
{
return (std::size_t)(2 + n_) * sizeof(mfem::real_t);
}
void Pack(const StateSnapshotView &s, void *dst) const
{
MFEM_VERIFY(dst != nullptr, "Pack: dst is null.");
MFEM_VERIFY(s.v_bytes != nullptr, "Pack: v_bytes is null.");
unsigned char *b = static_cast<unsigned char*>(dst);
std::memcpy(b + 0*sizeof(mfem::real_t), &s.time, sizeof(mfem::real_t));
std::memcpy(b + 1*sizeof(mfem::real_t), &s.obj, sizeof(mfem::real_t));
std::memcpy(b + 2*sizeof(mfem::real_t),
s.v_bytes,
(std::size_t)n_ * sizeof(mfem::real_t));
}
// IMPORTANT:
// Unpack produces a view into the provided src buffer.
// The resulting pointer is only valid as long as src remains unchanged.
void Unpack(const void *src, StateSnapshotView &snap) const
{
MFEM_VERIFY(src != nullptr, "Unpack: src is null.");
const unsigned char *b = static_cast<const unsigned char*>(src);
std::memcpy(&snap.time, b + 0*sizeof(mfem::real_t), sizeof(mfem::real_t));
std::memcpy(&snap.obj, b + 1*sizeof(mfem::real_t), sizeof(mfem::real_t));
snap.v_bytes = b + 2*sizeof(mfem::real_t);
}
int N() const { return n_; }
private:
int n_ = 0;
};
/*****************************************************************************
* Provides a recipe for runing dynamic checkpointing with memory storage
* alocated as one single big block.
* **************************************************************************/
void run_fixed_slot_memory_storage( mfem::real_t Tfinal /*Final time*/,
mfem::real_t dtime /*time step*/,
int s /* checkpoint budget (real checkpoints) */,
int n /*State vector size*/,
bool print=true)
{
StateSnapshotViewPacker packer(n);
using Storage = mfem::FixedSlotMemoryCheckpointStorage<StateSnapshotView,
StateSnapshotViewPacker>;
Storage storage(s, packer);
// Snapshot type is StateSnapshotView
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshotView, Storage>;
Checkpointing ckpt(s, storage);
// Returns view of the State and avoids data transfer
auto make_snapshot = [&](const State &u) -> StateSnapshotView
{
MFEM_VERIFY(u.v.Size() == n, "make_snapshot: State.v size changed!");
const mfem::real_t *vh = u.v.HostRead(); // host pointer
StateSnapshotView snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v_bytes = reinterpret_cast<const unsigned char*>(vh);
return snap;
};
//Transfers data from the snaphot view to the State u_out.
auto restore_snapshot = [&](const StateSnapshotView &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
if (u_out.v.Size() != n) { u_out.v.SetSize(n); }
mfem::real_t *vh = u_out.v.HostWrite();
std::memcpy(vh,
snap.v_bytes,
(std::size_t)n * sizeof(mfem::real_t));
};
using Step = Checkpointing::Step;
State u;
u.v.SetSize(n); u.v=0.0;
auto primal_step = [&](State &u_inout, Step i)
{
// advance u_inout.v, u_inout.time, u_inout.obj
if (print)
{
mfem::out<<"Forward step: "<<i<<" time="<<u_inout.time<<" obj="<<u_inout.obj<<std::endl;
}
u_inout.time=i*0.1;
u_inout.obj=i*0.2;
};
struct AdjointState { /* ... */ };
AdjointState q;
auto adjoint_step = [&](AdjointState &q_ip1_inout, const State &u_i, Step i)
{
// use u_i.time/u_i.obj/u_i.v to update adjoint
if (print)
{
mfem::out<<"Adjoint step: "<<i<<" time="<<u_i.time<<" obj="<<u_i.obj<<std::endl;
}
};
// Forward sweep (unknown number of steps)
mfem::real_t t=0.0;
Step i=0;
while (t<Tfinal)
{
// Store snapshot of u_i when manager decides; then primal_step u_i -> u_{i+1}
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t+=dtime;
++i;
}
mfem::out << "Total number of steps m="<<i<<"\n";
mfem::out << "\nBackward sweep.\n";
const Step m=i;
// Backward sweep
State u_work;
u_work.v.SetSize(n); // allocate once
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, q, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (i == 0) { break; }
}
}
/****************************************************************************
* Provides a recipe for runing dynamic checkpointing with file storage
* alocated as one single big file.
****************************************************************************/
/****************************************************************************
* The dynamic algorithm conceptually maintains s + 1 checkpoints, but the
* last one is a placeholder checkpoint that stores no solution and takes
* little memory; only s are real stored solutions.
* So FixedSlotFileCheckpointStorage needs only s slots.
* File behavior for FixedSlotFileCheckpointStorage:
- Writes a small header + s * slot_bytes payload region,
- Erase(handle) just returns the slot to the free list
(file contents remain, will be overwritten later),
no filesystem metadata churn.
* Device memory: Runs on GPU -packing to a host file requires a host pointer.
Using:
u.v.HostRead() for packing,
u_out.v.HostWrite() for restore,
keeps memmory access correct.
* Snapshot pointer lifetime: In the read path, StateSnapshotViewPacker::Unpack
sets snap.v_bytes to point into the storages internal read buffer. That
pointer is only valid until the storage performs another Read/Store
(and in general, one should treat it as valid only during restore_snapshot).
*****************************************************************************/
void run_fixed_slot_file_storage(mfem::real_t Tfinal /*Final time*/,
mfem::real_t dtime /*time step*/,
int s /* checkpoint budget (real checkpoints) */,
int n /*State vector size*/,
const std::string &file_path,
bool print=true)
{
// s = number of REAL checkpoints (placeholder doesn't store a snapshot)
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(n > 0, "Need n > 0.");
StateSnapshotViewPacker packer(n);
// Fixed-slot FILE storage:
// - single file (file_path)
// - max_slots = s (real checkpoints)
// - slot_bytes = packer.SlotBytes()
// - truncate=true to start fresh
// - flush_on_store=false for performance (set true if you want extra safety)
using Storage =
mfem::FixedSlotFileCheckpointStorage<StateSnapshotView, StateSnapshotViewPacker>;
Storage storage(file_path,
/*max_slots=*/s,
/*packer=*/packer,
/*truncate=*/true,
/*flush_on_store=*/false);
// Dynamic checkpointing manager using this storage.
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshotView, Storage>;
Checkpointing ckpt(s, storage);
// ---------------------------
// Callbacks
// ---------------------------
// Convert State -> SnapshotView (no allocation).
auto make_snapshot = [&](const State &u) -> StateSnapshotView
{
MFEM_VERIFY(u.v.Size() == n, "make_snapshot: State.v size changed!");
// Ensure host pointer valid even when MFEM is using device memory.
const mfem::real_t *vh = u.v.HostRead();
StateSnapshotView snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v_bytes = reinterpret_cast<const unsigned char*>(vh);
return snap;
};
// Convert SnapshotView -> State (must COPY out of v_bytes).
auto restore_snapshot = [&](const StateSnapshotView &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
if (u_out.v.Size() != n) { u_out.v.SetSize(n); }
mfem::real_t *vh = u_out.v.HostWrite();
std::memcpy(vh,
snap.v_bytes,
(std::size_t)n * sizeof(mfem::real_t));
};
// Example primal/adjoint step signatures (replace with your own):
using Step = Checkpointing::Step;
auto primal_step = [&](State &u_inout, Step i)
{
if (print)
{
mfem::out<<"Forward step: "<<i<<" time="<<u_inout.time<<" obj="<<u_inout.obj<<std::endl;
}
u_inout.time=i*0.1;
u_inout.obj=i*0.2;
// update u_inout.time, u_inout.obj, u_inout.v
};
struct AdjointState
{
// your adjoint variables, e.g., mfem::Vector lambda;
};
auto adjoint_step = [&](AdjointState &q_ip1_inout, const State &u_i, Step i)
{
// update adjoint using u_i
if (print)
{
mfem::out<<"Adjoint step: "<<i<<" time="<<u_i.time<<" obj="<<u_i.obj<<std::endl;
}
};
// ---------------------------
// Forward/backward skeleton
// ---------------------------
State u;
u.v.SetSize(n);
u.time = 0.0;
u.obj = 0.0;
u.v = 0.0;
// Forward sweep (unknown number of steps)
mfem::real_t t=0.0;
Step i=0;
while (t<Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t+=dtime;
++i;
}
mfem::out << "Total number of steps m="<<i-1<<"\n";
mfem::out << "\nBackward sweep.\n";
// Backward sweep
AdjointState q;
const Step m=i;
State u_work;
u_work.v.SetSize(n);
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, q, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (i == 0) { break; }
}
}
/******************************************************************************
The following classes implement the IO and the data necessery for dynamic
checkpointing with mfem::FileCheckpointStorage (one file per stored snapshot).
With FileCheckpointStorage, the Snapshot must own the data which will be
written, i.e. it cannot be a view containing pointers into State::v
as those pointers would be meaningless when read back.
*******************************************************************************/
// The StateSnapshot is just a copy of the State
using StateSnapshot=State;
struct StateSnapshotBinaryIO
{
static void Write(std::ostream &os, const StateSnapshot &s)
{
os.write(reinterpret_cast<const char*>(&s.time), sizeof(mfem::real_t));
os.write(reinterpret_cast<const char*>(&s.obj), sizeof(mfem::real_t));
MFEM_VERIFY(os.good(), "StateSnapshotBinaryIO: write time/obj failed.");
const std::int64_t n = (std::int64_t)s.v.Size();
os.write(reinterpret_cast<const char*>(&n), sizeof(n));
MFEM_VERIFY(os.good(), "StateSnapshotBinaryIO: write vector size failed.");
if (n > 0)
{
const mfem::real_t *vh = s.v.HostRead(); // ensure host pointer
os.write(reinterpret_cast<const char*>(vh),
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
MFEM_VERIFY(os.good(), "StateSnapshotBinaryIO: write vector payload failed.");
}
}
static StateSnapshot Read(std::istream &is)
{
StateSnapshot s;
is.read(reinterpret_cast<char*>(&s.time), sizeof(mfem::real_t));
is.read(reinterpret_cast<char*>(&s.obj), sizeof(mfem::real_t));
MFEM_VERIFY(is.good(), "StateSnapshotBinaryIO: read time/obj failed.");
std::int64_t n = 0;
is.read(reinterpret_cast<char*>(&n), sizeof(n));
MFEM_VERIFY(is.good(), "StateSnapshotBinaryIO: read vector size failed.");
MFEM_VERIFY(n >= 0, "StateSnapshotBinaryIO: invalid negative vector size.");
s.v.SetSize((int)n);
if (n > 0)
{
mfem::real_t *vh = s.v.HostWrite(); // ensure host pointer
is.read(reinterpret_cast<char*>(vh),
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
MFEM_VERIFY(is.good(), "StateSnapshotBinaryIO: read vector payload failed.");
}
return s;
}
};
/***************************************************************************
Important differences vs FixedSlotFileCheckpointStorage
Metadata overhead
FileCheckpointStorage typically:
*creates a new file for each Store() (plus a temp file rename),
*deletes a file on each Erase() (unless keep_files=true).
Dynamic checkpointing can perform many store/erase operations during
forward and during recomputation in reverse, so this can hammer filesystem
metadata on parallel filesystems.
If thats a concern, prefer:
*fixed-slot single-file storage, or
*segmented range file storage (few files + offsets).
Correctness note about pointers:
This is why we used an owning snapshot (StateSnapshot): storing a view
with v_bytes into file storage would store pointer values, which are
meaningless when read back.
*****************************************************************************/
void run_file_storage(mfem::real_t Tfinal /*Final time*/,
mfem::real_t dtime /*time step*/,
int s /* checkpoint budget (real checkpoints) */,
int n /*State vector size*/,
const std::string &directory,
bool print=true)
{
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(n > 0, "Need n > 0.");
// One file per snapshot (create_dir=true). keep_files=false means Erase() removes files.
using Storage =
mfem::FileCheckpointStorage<StateSnapshot, StateSnapshotBinaryIO>;
Storage storage(directory, "ckpt_",
".bin", /*create_dir=*/true, /*keep_files=*/false);
// Snapshot type = StateSnapshot (owning)
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshot, Storage>;
Checkpointing ckpt(s, storage);
using Step = Checkpointing::Step;
// ---- Callbacks ----
// Make an owning snapshot from the current State (deep copy of vector payload)
auto make_snapshot = [&](const State &u) -> StateSnapshot
{
MFEM_VERIFY(u.v.Size() == n, "make_snapshot: State.v size changed!");
StateSnapshot snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v.SetSize(n);
const mfem::real_t *src = u.v.HostRead();
mfem::real_t *dst = snap.v.HostWrite();
std::memcpy(dst, src, (std::size_t)n * sizeof(mfem::real_t));
return snap; // move into storage
};
// Restore from snapshot into an actual State (copy payload)
auto restore_snapshot = [&](const StateSnapshot &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
MFEM_VERIFY(snap.v.Size() == n,
"restore_snapshot: snapshot vector size mismatch.");
if (u_out.v.Size() != n) { u_out.v.SetSize(n); }
const mfem::real_t *src = snap.v.HostRead();
mfem::real_t *dst = u_out.v.HostWrite();
std::memcpy(dst, src, (std::size_t)n * sizeof(mfem::real_t));
};
// Example primal step signature (replace with your integrator)
auto primal_step = [&](State &u_inout, Step i)
{
// advance u_inout.time, u_inout.obj, u_inout.v
if (print)
{
mfem::out<<"Forward step: "<<i<<" time="<<u_inout.time<<" obj="<<u_inout.obj<<std::endl;
}
u_inout.time=i*0.1;
u_inout.obj=i*0.2;
};
// Example adjoint state and adjoint step signature (replace with yours)
struct AdjointState
{
// e.g., mfem::Vector lambda;
};
auto adjoint_step = [&](AdjointState &q_ip1_inout, const State &u_i, Step i)
{
// update adjoint using u_i
if (print)
{
mfem::out<<"Adjoint step: "<<i<<" time="<<u_i.time<<" obj="<<u_i.obj<<std::endl;
}
};
// ---- Forward/backward skeleton ----
State u;
u.v.SetSize(n);
u.time = 0.0;
u.obj = 0.0;
u.v = 0.0;
// Forward sweep (unknown number of steps)
mfem::real_t t=0.0;
Step i=0;
while (t<Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t+=dtime;
++i;
}
mfem::out << "Total number of steps m="<<i-1<<"\n";
mfem::out << "\nBackward sweep.\n";
// backward
const Step m=i;
AdjointState q;
State u_work;
u_work.v.SetSize(n);
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, q, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (i == 0) { break; }
}
}
/****************************************************************************
* Using standard in memory storage. Works with variable snaphot sizes.
* The size of the vector (n) is provided in order to compare the run to
* the other implementations.
***************************************************************************/
void run_in_memory_storage(mfem::real_t Tfinal /*Final time*/,
mfem::real_t dtime /*time step*/,
int s /* checkpoint budget (real checkpoints) */,
int n /*State vector size*/,
bool print=true)
{
InMemoryCheckpointStorage<StateSnapshot> storage;
using Checkpointing = mfem::DynamicCheckpointing<StateSnapshot,
InMemoryCheckpointStorage<StateSnapshot>>;
Checkpointing ckpt(s, storage);
auto make_snapshot = [&](const State &u) -> StateSnapshot
{
StateSnapshot snap;
snap.time = u.time;
snap.obj = u.obj;
snap.v = u.v; // deep copy (MFEM handles memory)
return snap; // moved into storage by Store()
};
auto restore_snapshot = [&](const StateSnapshot &snap, State &u_out)
{
u_out.time = snap.time;
u_out.obj = snap.obj;
u_out.v = snap.v; // deep copy back into working state
};
using Step = Checkpointing::Step;
// Primal step: u_i -> u_{i+1}
auto primal_step = [&](State &u, Step i)
{
// ... update u.time, u.obj, u.v ...
if (print)
{
mfem::out<<"Forward step: "<<i<<" time="<<u.time<<" obj="<<u.obj<<std::endl;
}
u.time=i*0.1;
u.obj=i*0.2;
};
// Adjoint step: q_{i+1} -> q_i using u_i
struct AdjointState
{
// e.g. mfem::Vector lambda;
};
auto adjoint_step = [&](AdjointState &q, const State &u_i, Step i)
{
// ... update q using u_i ...
if (print)
{
mfem::out<<"Adjoint step: "<<i<<" time="<<u_i.time<<" obj="<<u_i.obj<<std::endl;
}
};
State u;
u.v.SetSize(n);
u.v=0.0;
u.time = 0.0;
u.obj = 0.0;
// Forward sweep (unknown number of steps)
mfem::real_t t=0.0;
Step i=0;
while (t<Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t+=dtime;
++i;
}
mfem::out << "Total number of steps m="<<i-1<<"\n";
mfem::out << "\nBackward sweep.\n";
// Backward
const Step m=i;
AdjointState q;
State u_work; // used to restore/recompute primal state at step i
u_work.v.SetSize(u.v.Size());
for (Step i = m - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, q, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (i == 0) { break; }
}
}
int main(int argc, char *argv[])
{
int s = 5/* checkpoint budget (real checkpoints) */;
int n = 30 /* fixed State.v size */;
std::string filepath=
"dynamic_ckpts.bin"; /*file name for fixed slot single file*/
std::string directory="dyn_ckpts";
mfem::real_t Tfinal=1;
mfem::real_t dt=0.1;
int print=true;
OptionsParser args(argc, argv);
args.AddOption(&s, "-s",
"--checkpoints", "Checkpoint budget s (real checkpoints).");
args.AddOption(&n, "-n",
"--size", "Size of the state vector.");
args.AddOption(&Tfinal, "-T",
"--tfinal", "Terminate when accumulated time reaches Tfinal.");
args.AddOption(&dt, "-dt", "--dt", "Time step.");
args.AddOption(&filepath, "-file", "--file",
"File name for storing the checkpoints.");
args.AddOption(&directory, "-dir", "--dir",
"Directory for storing the checkpoints.");
args.AddOption(&print, "-print", "--print",
"Turn on/off printing." );
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
double run_time;
mfem::out<<"\nFixedSlotMemoryStorage Start\n";
mfem::tic();
run_fixed_slot_memory_storage(Tfinal, dt, s, n, print);
run_time=mfem::toc();
mfem::out<<"FixedSlotMemoryStorage time= "<<run_time<<std::endl;
mfem::out<<"\nFixedSlotFileStorage Start\n";
mfem::tic();
run_fixed_slot_file_storage(Tfinal,dt, s,n, filepath,print);
run_time=mfem::toc();
mfem::out<<"FixedSlotFileStorage time= "<<run_time<<std::endl;
mfem::out<<"\nFileStorage Start\n";
mfem::tic();
run_file_storage(Tfinal,dt, s,n, directory, print);
run_time=mfem::toc();
mfem::out<<"FileStorage time= "<<run_time<<std::endl;
mfem::out<<"\nInMemoryStorage\n";
mfem::tic();
run_in_memory_storage(Tfinal,dt, s,n, print);
run_time=mfem::toc();
mfem::out<<"InMemoryStorage time= "<<run_time<<std::endl;
mfem::out << "\nDone.\n";
return 0;
}
@@ -0,0 +1,313 @@
#include "mfem.hpp"
#include <cmath>
#include <iomanip>
using namespace mfem;
static double LogisticStep(double u, double alpha, double dt)
{
return u + dt * alpha * u * (1.0 - u);
}
static double LogisticJac(double u, double alpha, double dt)
{
// d/du [u + dt*alpha*u*(1-u)] = 1 + dt*alpha*(1 - 2u)
return 1.0 + dt * alpha * (1.0 - 2.0*u);
}
#if MFEM_HAVE_FILESYSTEM
static void PurgeDir(const std::string &dir)
{
std::error_code ec;
mfem_fs::remove_all(mfem_fs::path(dir), ec);
mfem_fs::create_directories(mfem_fs::path(dir), ec);
MFEM_VERIFY(!ec, "Failed to purge/create directory.");
}
#endif
int main(int argc, char *argv[])
{
// ---------- shared options ----------
int s = 3;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
bool keep_files = false;
bool purge_dirs = true;
// scalar problem params
double alpha = 2.0;
double u0 = 0.2;
double target_s = 0.7;
double eps = 1e-7;
// vector problem params
int n = 16;
double target_v = 0.7;
std::string dir_scalar = "chk_scalar_files";
std::string dir_vector = "chk_vector_files";
OptionsParser args(argc, argv);
args.AddOption(&s, "-s", "--checkpoints",
"Checkpoint budget s (real checkpoints).");
args.AddOption(&dt0, "-dt0", "--dt0",
"Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega",
"Omega for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal",
"Stop when accumulated time reaches Tfinal.");
args.AddOption(&keep_files, "-k", "--keep-files", "-nk", "--no-keep-files",
"Keep checkpoint files (debug).");
args.AddOption(&purge_dirs, "-p", "--purge-dirs", "-np", "--no-purge-dirs",
"Purge checkpoint directories at start (recommended).");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&u0, "-u0", "--u0", "Scalar initial u0.");
args.AddOption(&target_s, "-ts", "--target-scalar", "Scalar target.");
args.AddOption(&eps, "-eps", "--fd-eps",
"FD epsilon (scalar and vector directional).");
args.AddOption(&n, "-n", "--size", "Vector dimension.");
args.AddOption(&target_v, "-tv", "--target-vector",
"Vector target value per component.");
args.AddOption(&dir_scalar, "-ds", "--dir-scalar",
"Directory for scalar checkpoint files.");
args.AddOption(&dir_vector, "-dv", "--dir-vector",
"Directory for vector checkpoint files.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
#if MFEM_HAVE_FILESYSTEM
if (purge_dirs)
{
PurgeDir(dir_scalar);
PurgeDir(dir_vector);
}
#else
MFEM_VERIFY(!purge_dirs, "purge_dirs requires <filesystem> support.");
#endif
using StepS =
mfem::DynamicCheckpointing<double, mfem::FileCheckpointStorage<double>>::Step;
auto dt_func = [&](StepS i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
mfem::out << std::setprecision(15);
// ============================================================
// A) Scalar run (double) with FileCheckpointStorage
// ============================================================
{
mfem::FileCheckpointStorage<double> storage(dir_scalar, "ckpt_", ".bin", true,
keep_files);
mfem::DynamicCheckpointing<double, mfem::FileCheckpointStorage<double>> ckpt(s,
storage);
auto make_snapshot = [](const double &u) { return u; };
auto restore_snapshot = [](const double &snap, double &state) { state = snap; };
auto primal_step = [&](double &u, StepS i)
{
const double dt = dt_func(i);
u = LogisticStep(u, alpha, dt);
};
auto adjoint_step = [&](double &lambda, const double &u_i, StepS i)
{
const double dt = dt_func(i);
const double dF = LogisticJac(u_i, alpha, dt);
lambda *= dF;
};
double u = u0;
double t_phys = 0.0;
StepS i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const StepS m = i;
const double u_m = u;
const double J = 0.5 * (u_m - target_s) * (u_m - target_s);
double lambda = (u_m - target_s); // terminal adjoint
double u_work = 0.0;
for (StepS j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const double dJ_du0_adj = lambda;
// FD check
auto forward_only_J = [&](double u_init)
{
double uu = u_init;
double tt = 0.0;
StepS k = 0;
while (tt < Tfinal)
{
uu = LogisticStep(uu, alpha, dt_func(k));
tt += dt_func(k);
++k;
}
const double r = uu - target_s;
return 0.5 * r * r;
};
const double Jp = forward_only_J(u0 + eps);
const double Jm = forward_only_J(u0 - eps);
const double dJ_du0_fd = (Jp - Jm) / (2.0 * eps);
mfem::out << "\n[FileIO][Scalar]\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " u_m = " << u_m << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " dJ/du0 adjoint = " << dJ_du0_adj << "\n";
mfem::out << " dJ/du0 FD = " << dJ_du0_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_du0_adj - dJ_du0_fd) << "\n";
}
// ============================================================
// B) Vector run (mfem::Vector) with FileCheckpointStorage
// ============================================================
{
mfem::FileCheckpointStorage<mfem::Vector> storage(dir_vector, "ckpt_", ".bin",
true, keep_files);
mfem::DynamicCheckpointing<mfem::Vector, mfem::FileCheckpointStorage<mfem::Vector>>
ckpt(s, storage);
using StepV =
mfem::DynamicCheckpointing<mfem::Vector, mfem::FileCheckpointStorage<mfem::Vector>>::Step;
auto dt_func_v = [&](StepV i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
auto make_snapshot = [](const mfem::Vector &u) { return u; };
auto restore_snapshot = [](const mfem::Vector &snap, mfem::Vector &state) { state = snap; };
auto primal_step = [&](mfem::Vector &u, StepV i)
{
const double dt = dt_func_v(i);
for (int j = 0; j < u.Size(); ++j)
{
u[j] = LogisticStep(u[j], alpha, dt);
}
};
auto adjoint_step = [&](mfem::Vector &lambda, const mfem::Vector &u_i, StepV i)
{
const double dt = dt_func_v(i);
MFEM_ASSERT(lambda.Size() == u_i.Size(), "Size mismatch.");
for (int j = 0; j < lambda.Size(); ++j)
{
lambda[j] *= LogisticJac(u_i[j], alpha, dt);
}
};
mfem::Vector u0v(n), ut(n);
for (int j = 0; j < n; ++j) { u0v[j] = 0.2 + 0.05 * std::cos(0.7*(j+1)); }
ut = target_v;
mfem::Vector u = u0v;
double t_phys = 0.0;
StepV i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func_v(i);
++i;
}
const StepV m = i;
const mfem::Vector u_m = u;
mfem::Vector diff(u_m);
diff -= ut;
const double J = 0.5 * mfem::InnerProduct(diff, diff);
// backward
mfem::Vector lambda = diff; // terminal adjoint
mfem::Vector u_work(n);
for (StepV j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const mfem::Vector &grad_u0 = lambda;
// directional FD check
mfem::Vector v(n);
for (int j = 0; j < n; ++j) { v[j] = std::sin(0.3*(j+1)) + 0.1; }
auto forward_only_J = [&](const mfem::Vector &u_init)
{
mfem::Vector uu = u_init;
double tt = 0.0;
StepV k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func_v(k);
++k;
}
mfem::Vector dd(uu);
dd -= ut;
return 0.5 * mfem::InnerProduct(dd, dd);
};
mfem::Vector u_plus(u0v), u_minus(u0v);
u_plus.Add(eps, v);
u_minus.Add(-eps, v);
const double Jp = forward_only_J(u_plus);
const double Jm = forward_only_J(u_minus);
const double dJ_dir_fd = (Jp - Jm) / (2.0 * eps);
const double dJ_dir_adj = mfem::InnerProduct(grad_u0, v);
mfem::out << "\n[FileIO][Vector]\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " v·grad adjoint = " << dJ_dir_adj << "\n";
mfem::out << " FD directional = " << dJ_dir_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_dir_adj - dJ_dir_fd) <<
"\n";
mfem::out << " ||u_m||_2 = " << u_m.Norml2() << "\n";
mfem::out << " ||u_m - u_target||2 = " << diff.Norml2() << "\n";
}
mfem::out << "\nDone.\n";
return 0;
}
@@ -0,0 +1,385 @@
#include "mfem.hpp"
#include <cmath>
#include <iomanip>
#include <string>
using namespace mfem;
// Logistic ODE (scalar or componentwise):
// du/dt = alpha*u*(1-u)
// Explicit Euler:
// u_{i+1} = u_i + dt_i * alpha*u_i*(1-u_i)
//
// Objective:
// Scalar: J = 0.5*(u_m - target)^2
// Vector: J = 0.5*||u_m - u_target||^2
//
// Discrete adjoint:
// dF/du = 1 + dt_i*alpha*(1 - 2*u_i)
// lambda_i = (dF/du at u_i) * lambda_{i+1}
static inline double LogisticStep(const double u, const double alpha,
const double dt)
{
return u + dt * alpha * u * (1.0 - u);
}
static inline double LogisticJac(const double u, const double alpha,
const double dt)
{
return 1.0 + dt * alpha * (1.0 - 2.0*u);
}
// --------------------------
// Scalar run (double)
// --------------------------
template <typename Storage>
static void RunScalarFixedSlot(int s,
Storage &storage,
double alpha,
double dt0,
double omega,
double Tfinal,
double u0,
double target,
double eps)
{
using CKPT = mfem::DynamicCheckpointing<double, Storage>;
using Step = typename CKPT::Step;
CKPT ckpt(s, storage);
auto dt_func = [&](Step i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
auto make_snapshot = [](const double &u) { return u; };
auto restore_snapshot = [](const double &snap, double &state) { state = snap; };
auto primal_step = [&](double &u, Step i)
{
const double dt = dt_func(i);
u = LogisticStep(u, alpha, dt);
};
auto adjoint_step = [&](double &lambda, const double &u_i, Step i)
{
const double dt = dt_func(i);
lambda *= LogisticJac(u_i, alpha, dt);
};
// Forward sweep (unknown m; stop on accumulated physical time)
double u = u0;
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const Step m = i;
const double u_m = u;
const double J = 0.5 * (u_m - target) * (u_m - target);
// Backward sweep
MFEM_VERIFY(m > 0, "Scalar run produced m=0 steps.");
double lambda = (u_m - target); // terminal condition dJ/du_m
double u_work = 0.0;
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const double dJ_du0_adj = lambda;
// FD check
auto forward_only_J = [&](double u_init)
{
double uu = u_init;
double tt = 0.0;
Step k = 0;
while (tt < Tfinal)
{
uu = LogisticStep(uu, alpha, dt_func(k));
tt += dt_func(k);
++k;
}
const double r = uu - target;
return 0.5 * r * r;
};
const double Jp = forward_only_J(u0 + eps);
const double Jm = forward_only_J(u0 - eps);
const double dJ_du0_fd = (Jp - Jm) / (2.0 * eps);
mfem::out << "\n[FixedSlot][Scalar]\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " u_m = " << u_m << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " dJ/du0 adjoint = " << dJ_du0_adj << "\n";
mfem::out << " dJ/du0 FD = " << dJ_du0_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_du0_adj - dJ_du0_fd) << "\n";
}
// --------------------------
// Vector run (mfem::Vector)
// --------------------------
template <typename Storage>
static void RunVectorFixedSlot(int s,
Storage &storage,
int n,
double alpha,
double dt0,
double omega,
double Tfinal,
double target_val,
double eps)
{
using CKPT = mfem::DynamicCheckpointing<mfem::Vector, Storage>;
using Step = typename CKPT::Step;
CKPT ckpt(s, storage);
auto dt_func = [&](Step i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
auto make_snapshot = [](const mfem::Vector &u) { return u; };
auto restore_snapshot = [](const mfem::Vector &snap, mfem::Vector &state) { state = snap; };
auto primal_step = [&](mfem::Vector &u, Step i)
{
const double dt = dt_func(i);
for (int j = 0; j < u.Size(); ++j)
{
u[j] = LogisticStep(u[j], alpha, dt);
}
};
auto adjoint_step = [&](mfem::Vector &lambda, const mfem::Vector &u_i, Step i)
{
const double dt = dt_func(i);
MFEM_ASSERT(lambda.Size() == u_i.Size(), "lambda and u_i size mismatch.");
for (int j = 0; j < lambda.Size(); ++j)
{
lambda[j] *= LogisticJac(u_i[j], alpha, dt);
}
};
// Initial condition and target
mfem::Vector u0(n), u_target(n);
for (int j = 0; j < n; ++j)
{
u0[j] = 0.2 + 0.05 * std::cos(0.7 * (j + 1));
}
u_target = target_val;
// Forward sweep
mfem::Vector u = u0;
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const Step m = i;
const mfem::Vector u_m = u;
mfem::Vector diff(u_m);
diff -= u_target;
const double J = 0.5 * mfem::InnerProduct(diff, diff);
// Backward sweep
MFEM_VERIFY(m > 0, "Vector run produced m=0 steps.");
mfem::Vector lambda = diff; // terminal adjoint = dJ/du_m
mfem::Vector u_work(n);
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const mfem::Vector &grad_u0 = lambda;
// Directional FD check
mfem::Vector v(n);
for (int j = 0; j < n; ++j) { v[j] = std::sin(0.3 * (j + 1)) + 0.1; }
auto forward_only_J = [&](const mfem::Vector &u_init)
{
mfem::Vector uu = u_init;
double tt = 0.0;
Step k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func(k);
++k;
}
mfem::Vector dd(uu);
dd -= u_target;
return 0.5 * mfem::InnerProduct(dd, dd);
};
mfem::Vector u_plus(u0), u_minus(u0);
u_plus.Add(eps, v);
u_minus.Add(-eps, v);
const double Jp = forward_only_J(u_plus);
const double Jm = forward_only_J(u_minus);
const double dJ_dir_fd = (Jp - Jm) / (2.0 * eps);
const double dJ_dir_adj = mfem::InnerProduct(grad_u0, v);
mfem::out << "\n[FixedSlot][Vector]\n";
mfem::out << " n = " << n << "\n";
mfem::out << " m steps = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " ||u_m||_2 = " << u_m.Norml2() << "\n";
mfem::out << " ||u_m-u_target|| = " << diff.Norml2() << "\n";
mfem::out << " v·grad adjoint = " << dJ_dir_adj << "\n";
mfem::out << " FD directional = " << dJ_dir_fd << "\n";
mfem::out << " abs err = " << std::abs(dJ_dir_adj - dJ_dir_fd) <<
"\n";
}
int main(int argc, char *argv[])
{
// Backend selection:
// 0 = fixed-slot memory (single RAM block)
// 1 = fixed-slot file (single file with fixed offsets)
int backend = 0;
// Common parameters
int s = 8; // number of REAL stored checkpoints
double alpha = 2.0;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
double eps = 1e-7;
// Scalar parameters
double u0 = 0.2;
double target_s = 0.7;
// Vector parameters
int n = 64; // must stay fixed for fixed-slot vector packing
double target_v = 0.7;
// File backend parameters
std::string scalar_file = "scalar_fixedslots.bin";
std::string vector_file = "vector_fixedslots.bin";
bool truncate_files = true;
bool flush_on_store = true; // safer for demo correctness with iostreams
OptionsParser args(argc, argv);
args.AddOption(&backend, "-b", "--backend",
"Backend: 0=memory fixed-slots, 1=file fixed-slots (single file).");
args.AddOption(&s, "-s", "--checkpoints",
"Checkpoint budget s (real checkpoints).");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&dt0, "-dt0", "--dt0",
"Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega",
"Omega for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal",
"Stop when accumulated time reaches Tfinal.");
args.AddOption(&eps, "-eps", "--fd-eps", "FD epsilon.");
args.AddOption(&u0, "-u0", "--u0", "Scalar initial condition u0.");
args.AddOption(&target_s, "-ts", "--target-scalar", "Scalar target.");
args.AddOption(&n, "-n", "--size", "Vector dimension (fixed).");
args.AddOption(&target_v, "-tv", "--target-vector",
"Vector target value per component.");
args.AddOption(&scalar_file, "-sf", "--scalar-file",
"File for scalar fixed-slot storage.");
args.AddOption(&vector_file, "-vf", "--vector-file",
"File for vector fixed-slot storage.");
args.AddOption(&truncate_files, "-tr", "--truncate", "-ntr", "--no-truncate",
"Truncate checkpoint files on startup.");
args.AddOption(&flush_on_store, "-fl", "--flush", "-nfl", "--no-flush",
"Flush on each Store() (demo-safety; slower).");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(n > 0, "Need n > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
mfem::out << std::setprecision(15);
if (backend == 0)
{
mfem::out << "\nUsing fixed-slot MEMORY backend (single RAM block)\n";
// Scalar: fixed-size POD => trivial packer
mfem::FixedSlotMemoryCheckpointStorage<double> stor_s(s);
RunScalarFixedSlot(s, stor_s, alpha, dt0, omega, Tfinal, u0, target_s, eps);
// Vector: fixed-size packing (n must remain constant)
mfem::FixedVectorPacker packer(n);
mfem::FixedSlotMemoryCheckpointStorage<mfem::Vector, mfem::FixedVectorPacker>
stor_v(s, packer);
RunVectorFixedSlot(s, stor_v, n, alpha, dt0, omega, Tfinal, target_v, eps);
}
else if (backend == 1)
{
mfem::out <<
"\nUsing fixed-slot FILE backend (single file with fixed offsets)\n";
// Scalar file
mfem::FixedSlotFileCheckpointStorage<double> stor_s(scalar_file, s,
mfem::TrivialFixedPacker<double>(),
truncate_files,
flush_on_store);
RunScalarFixedSlot(s, stor_s, alpha, dt0, omega, Tfinal, u0, target_s, eps);
// Vector file (fixed-size packing with n)
mfem::FixedVectorPacker packer(n);
mfem::FixedSlotFileCheckpointStorage<mfem::Vector, mfem::FixedVectorPacker>
stor_v(vector_file, s, packer, truncate_files, flush_on_store);
RunVectorFixedSlot(s, stor_v, n, alpha, dt0, omega, Tfinal, target_v, eps);
}
else
{
MFEM_ABORT("Unknown backend. Use -b 0 (memory) or -b 1 (file).");
}
mfem::out << "\nDone.\n";
return 0;
}
+183
View File
@@ -0,0 +1,183 @@
#include "mfem.hpp"
using namespace mfem;
// ============================================================
// Mini example 1: scalar state (double)
// u_{n+1} = factor * u_n
// J = 0.5 (u_N - target)^2 => lambda_N = (u_N - target)
// lambda_n = factor * lambda_{n+1}
// ============================================================
static void RunScalarExample(int Nsteps, int Ncheck, double factor)
{
mfem::out << "\n--- Scalar REVOLVE example ---\n";
mfem::out << "Nsteps=" << Nsteps << ", Ncheckpoints=" << Ncheck << ", factor="
<<
factor << "\n";
const size_t snap_bytes = sizeof(double);
RevolveFixedSlotMemoryStorage storage(Ncheck, snap_bytes);
FixedStepRevolveCheckpointing<RevolveFixedSlotMemoryStorage> ckpt(Nsteps,
Ncheck,
snap_bytes, storage);
auto make_snapshot = [](const double &u, uint8_t *outb, size_t bytes)
{
MFEM_VERIFY(bytes == sizeof(double), "scalar snapshot size mismatch");
std::memcpy(outb, &u, sizeof(double));
};
auto restore_snapshot = [](double &u, const uint8_t *inb, size_t bytes)
{
MFEM_VERIFY(bytes == sizeof(double), "scalar snapshot size mismatch");
std::memcpy(&u, inb, sizeof(double));
};
auto primal_step = [factor](int /*step*/, double &u)
{
u *= factor;
};
auto adjoint_step = [factor](int /*step*/, const double & /*u_step*/,
double &lambda)
{
lambda *= factor;
};
const double target = 2.0;
// Forward
double u = 1.0;
for (int i = 0; i < Nsteps; ++i)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
}
const double uN = u;
// Init adjoint at final state
double lambda = (uN - target);
// Reverse
double u_work = 0.0; // will be overwritten by restore_snapshot
for (int i = Nsteps - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
}
// Analytic lambda_0 = factor^Nsteps * (uN - target)
double factorN = 1.0;
for (int k = 0; k < Nsteps; ++k) { factorN *= factor; }
const double lambda0_exact = factorN * (uN - target);
mfem::out << "uN = " << uN << "\n";
mfem::out << "lambda0 (computed) = " << lambda << "\n";
mfem::out << "lambda0 (exact) = " << lambda0_exact << "\n";
}
// ============================================================
// Mini example 2: mfem::Vector state
// u_{n+1} = factor * u_n (elementwise scalar multiply)
// J = 0.5 ||u_N - target||^2 => lambda_N = (u_N - target)
// lambda_n = factor * lambda_{n+1}
// ============================================================
static void RunVectorExample(int Nsteps, int Ncheck, int dim, double factor)
{
mfem::out << "\n--- mfem::Vector REVOLVE example ---\n";
mfem::out << "Nsteps=" << Nsteps << ", Ncheckpoints=" << Ncheck
<< ", dim=" << dim << ", factor=" << factor << "\n";
MFEM_VERIFY(dim > 0, "dim must be > 0");
const size_t snap_bytes = sizeof(double) * size_t(dim);
RevolveFixedSlotMemoryStorage storage(Ncheck, snap_bytes);
FixedStepRevolveCheckpointing<RevolveFixedSlotMemoryStorage> ckpt(Nsteps,
Ncheck,
snap_bytes, storage);
auto make_snapshot = [](const mfem::Vector &u, uint8_t *outb, size_t bytes)
{
MFEM_VERIFY(bytes == sizeof(double) * size_t(u.Size()),
"Vector snapshot size mismatch");
std::memcpy(outb, u.GetData(), bytes);
};
auto restore_snapshot = [](mfem::Vector &u, const uint8_t *inb, size_t bytes)
{
MFEM_VERIFY(bytes == sizeof(double) * size_t(u.Size()),
"Vector snapshot size mismatch");
std::memcpy(u.GetData(), inb, bytes);
};
auto primal_step = [factor](int /*step*/, mfem::Vector &u)
{
u *= factor;
};
auto adjoint_step = [factor](int /*step*/, const mfem::Vector & /*u_step*/,
mfem::Vector &lambda)
{
lambda *= factor;
};
mfem::Vector u(dim);
for (int k = 0; k < dim; ++k) { u[k] = 1.0 + 0.1*k; }
mfem::Vector target(dim);
target = 2.0;
// Forward
for (int i = 0; i < Nsteps; ++i)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
}
mfem::Vector uN(u); // copy for reporting
// Init adjoint: lambda_N = uN - target
mfem::Vector lambda(dim);
lambda = u;
lambda -= target;
// Reverse
mfem::Vector u_work(dim);
u_work = 0.0;
for (int i = Nsteps - 1; i >= 0; --i)
{
ckpt.BackwardStep(i, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
}
mfem::out << "||uN||_2 = " << uN.Norml2() << "\n";
mfem::out << "||lambda0||_2 = " << lambda.Norml2() << "\n";
}
int main(int argc, char *argv[])
{
mfem::Device device("cpu");
device.Print();
int Nsteps = 20;
int Ncheck = 3;
int dim = 5;
double factor = 1.05;
mfem::OptionsParser args(argc, argv);
args.AddOption(&Nsteps, "-n", "--num-steps", "Number of primal steps.");
args.AddOption(&Ncheck, "-s", "--num-checkpoints",
"Number of checkpoints (snaps).");
args.AddOption(&dim, "-d", "--dim",
"Vector dimension for the mfem::Vector example.");
args.AddOption(&factor, "-f", "--factor",
"Scalar factor in the toy update u_{n+1}=f*u_n.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
RunScalarExample(Nsteps, Ncheck, factor);
RunVectorExample(Nsteps, Ncheck, dim, factor);
return 0;
}
@@ -0,0 +1,174 @@
#include "mfem.hpp"
#include <cmath>
#include <iomanip>
#include <string>
using namespace mfem;
// the forward state consists of vector and
// several additional values
struct State
{
mfem::real_t time = 0.0;
mfem::real_t obj = 0.0;
mfem::Vector v;
};
/// class to manage the Snaphot Layout
class StateSnapshotLayout
{
public:
// Constructor: n - size of the vector in the state
explicit StateSnapshotLayout(int n) : n_(n)
{
MFEM_VERIFY(n_ > 0, "StateSnapshotLayout: n must be > 0.");
}
// returns the total size of the snapshot in mfem::real_t
int N() const { return n_; }
// returns the total number of bytes necessary to store the state
int Bytes() const
{
return (2 + n_) * (int)sizeof(mfem::real_t);
}
// Pack State -> bytes (host bytes)
void Pack(const State &s, unsigned char *dst, int bytes) const
{
MFEM_VERIFY(dst != nullptr, "Pack: dst is null.");
MFEM_VERIFY(bytes == Bytes(), "Pack: snapshot byte size mismatch.");
MFEM_VERIFY(s.v.Size() == n_, "Pack: State.v size mismatch.");
// Ensure we read from host even if MFEM uses device memory.
const mfem::real_t *vh = s.v.HostRead();
std::memcpy(dst + 0*sizeof(mfem::real_t), &s.time, sizeof(mfem::real_t));
std::memcpy(dst + 1*sizeof(mfem::real_t), &s.obj, sizeof(mfem::real_t));
std::memcpy(dst + 2*sizeof(mfem::real_t),
vh, (std::size_t)n_ * sizeof(mfem::real_t));
}
// Unpack bytes -> State (host write)
void Unpack(const unsigned char *src, int bytes, State &state) const
{
MFEM_VERIFY(src != nullptr, "Unpack: src is null.");
MFEM_VERIFY(bytes == Bytes(), "Unpack: snapshot byte size mismatch.");
if (state.v.Size() != n_) { state.v.SetSize(n_); }
mfem::real_t *vh = state.v.HostWrite();
std::memcpy(&state.time, src + 0*sizeof(mfem::real_t), sizeof(mfem::real_t));
std::memcpy(&state.obj, src + 1*sizeof(mfem::real_t), sizeof(mfem::real_t));
std::memcpy(vh,
src + 2*sizeof(mfem::real_t),
(std::size_t)n_ * sizeof(mfem::real_t));
}
private:
int n_ = 0;
};
/* The REVOLVE manager (as provided earlier) uses this callback style:
* make_snapshot(const State&, uint8_t* out, size_t bytes)
* restore_snapshot(State&, const uint8_t* in, size_t bytes)
* primal_step(int step, State&)
* adjoint_step(int step, const State&, AdjointState&)
and a storage backend with:
* Save(slot, bytes)
* Load(slot, bytes)
REVOLVE manager will:
*request storing snapshots into checkpoint slots (takeshot)
*request restoring a checkpoint slot (restore)
*request recomputation forward (advance)
*then request the next adjoint step (firsturn / youturn)
all while using only (Ncheck) stored checkpoints.
Fixed-step REVOLVE manager:
* Snapshot is raw bytes packed/unpacked by your callbacks.
* Storage is a fixed indexed array (Save(slot) / Load(slot)),
because REVOLVE addresses checkpoints by slot index.
Best use when Nsteps is known in the begining of the simulation.
*/
int main(int argc, char *argv[])
{
const int n = 100/* fixed State.v size */;
StateSnapshotLayout layout(n);
const std::size_t snapshot_bytes = (std::size_t)layout.Bytes();
const int Nsteps = 20 /* known number of time steps */;
const int Ncheck = 5 /* number of checkpoints (snaps) */;
// Fixed-slot file backend (single file)
RevolveFixedSlotFileStorage storage("revolve_ckpts.bin", Ncheck,
snapshot_bytes);
FixedStepRevolveCheckpointing<RevolveFixedSlotFileStorage>
cktp(Nsteps, Ncheck, snapshot_bytes, storage);
// Memory backend (single block) alternative:
// RevolveFixedSlotMemoryStorage storage(Ncheck, snapshot_bytes);
// FixedStepRevolveCheckpointing<RevolveFixedSlotMemoryStorage>
// cktp(Nsteps, Ncheck, snapshot_bytes, storage);
auto make_snapshot = [&](const State &s, uint8_t *outb, std::size_t bytes)
{
MFEM_VERIFY(bytes == snapshot_bytes, "make_snapshot: byte size mismatch");
layout.Pack(s, reinterpret_cast<unsigned char*>(outb), (int)bytes);
};
auto restore_snapshot = [&](State &s, const uint8_t *inb, std::size_t bytes)
{
MFEM_VERIFY(bytes == snapshot_bytes, "restore_snapshot: byte size mismatch");
layout.Unpack(reinterpret_cast<const unsigned char*>(inb), (int)bytes, s);
};
// set the work state
State u_work;
u_work.v.SetSize(n); u_work.v=0.0;
State u;
u.v.SetSize(n); u.v=0.0; u.obj=0.0; u.time=0.0;
// init u.time, u.obj, u.v ...
auto primal_step = [&](int step, State &u_inout)
{
(void)step;
// advance u_inout -> u_{step+1}
};
struct AdjointState { /* ... */ };
AdjointState lambda;
auto adjoint_step = [&](int step, const State &u_step,
AdjointState &lambda_inout)
{
(void)step;
// update lambda_{step+1} -> lambda_step using u_step
};
// Forward: i = 0..Nsteps-1
for (int i = 0; i < Nsteps; ++i)
{
cktp.ForwardStep(i, u, primal_step, make_snapshot);
}
// Reverse: i = Nsteps-1..0
for (int i = Nsteps - 1; i >= 0; --i)
{
cktp.BackwardStep(i, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
}
mfem::out << "\nDone.\n";
return 0;
};
+212
View File
@@ -0,0 +1,212 @@
#include "mfem.hpp"
#include <cmath>
#include <iomanip>
using namespace mfem;
// Scalar logistic ODE:
// du/dt = alpha*u*(1-u)
// Explicit Euler with variable dt(i):
// u_{i+1} = u_i + dt_i*alpha*u_i*(1-u_i)
// Objective:
// J = 0.5*(u_m - target)^2
// Discrete adjoint (scalar):
// lambda_m = (u_m - target)
// lambda_i = (dF/du at u_i)^T * lambda_{i+1}
// dF/du = 1 + dt_i*alpha*(1 - 2*u_i)
int main(int argc, char *argv[])
{
int s = 3;
double alpha = 2.0;
double dt0 = 0.001;
double Tfinal = 1.0;
double u0 = 0.2;
double target = 0.7;
double eps = 1e-7;
double err = 1e-4;
OptionsParser args(argc, argv);
args.AddOption(&s, "-s", "--checkpoints",
"Checkpoint budget s (real checkpoints).");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&dt0, "-dt0", "--dt0", "Base for the time step dt.");
args.AddOption(&Tfinal, "-T", "--tfinal",
"Terminate when accumulated time reaches Tfinal.");
args.AddOption(&u0, "-u0", "--u0", "Initial scalar state u0.");
args.AddOption(&target, "-ut", "--target",
"Target value in J=0.5*(u_m-target)^2.");
args.AddOption(&eps, "-eps", "--fd-eps", "Finite-difference epsilon.");
args.AddOption(&err, "-err", "--time_err",
"Allowed time integration error per time step.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
using Step = mfem::DynamicCheckpointing<double>::Step;
struct my_state
{
my_state(double a, double b, double c, double d=0.0, double tp_=0.0)
{
t=a; dt=b; u=c; up=d; tp=tp_;
}
//t,dt,u
double t;
double dt;
double u;
double tp;
double up;
};
// Checkpoint manager:
// State = double
// Snapshot= double
mfem::DynamicCheckpointing<my_state> ckpt(s);
auto make_snapshot = [](const my_state &u) -> my_state { return u; };
auto restore_snapshot = [](const my_state &snap, my_state &state) { state = snap; };
double tmax=Tfinal;
auto primal_step = [&](my_state &su, Step i)
{
su.up=su.u;
su.tp=su.t;
double t=su.t;
double dt = su.dt;
bool flag=true;
if ((tmax-t)<su.dt)
{
dt=tmax-t;
flag=false;
}
double u=su.u;
double s0=alpha * u * (1.0 - u);
double ue=u+dt*s0;
double s1=alpha * ue * (1.0 - ue);
double uh=u + 0.5 * dt * (s0 + s1);
double ee = std::abs(uh-ue);
if ((ee < 0.5*err) && (flag))
{
dt=1.25*dt;
ue=u+dt*s0;
s1=alpha * ue * (1.0 - ue);
uh=u + 0.5 * dt * (s0 + s1);
ee = std::abs(uh-ue);
}
while (ee > err)
{
dt=0.5*dt;
ue=u+dt*s0;
s1=alpha * ue * (1.0 - ue);
uh=u + 0.5 * dt * (s0 + s1);
ee = std::abs(uh-ue);
}
//mfem::out<<" t="<<t+dt<<" dt="<<dt<<" err="<<ee<<std::endl;
su.t = t+dt;
su.dt = dt;
su.u = uh;
};
auto adjoint_step = [&](double &lambda, const my_state &u_i, Step i)
{
const double dt = u_i.dt;
// const double dF_du = 1.0 + dt * alpha * (1.0 - 2.0 * u_i.u);
// lambda = dF_du * lambda;
const double s0=alpha * (1.0 -2.0 * u_i.u) *lambda;
const double le= lambda+dt*s0;
const double s1= alpha * (1.0 -2.0 * u_i.up) * le;
lambda = lambda + 0.5 *dt *(s0+s1);
};
// ---------------- Forward sweep (unknown m) ----------------
my_state u(0.0, dt0, u0);
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys = u.t;
++i;
}
const Step m = i;
const double u_m = u.u;
const double J = 0.5 * (u_m - target) * (u_m - target);
mfem::out << std::setprecision(15);
mfem::out << "\n[Scalar] Forward finished:\n";
mfem::out << " m (steps) = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " u_m = " << u_m << "\n";
mfem::out << " J = " << J << "\n\n";
mfem::out <<
"[Scalar] Checkpoint set after forward sweep (step, level, stored):\n";
for (const auto &cp : ckpt.GetCheckpointInfo())
{
mfem::out << " step=" << cp.step
<< ", level=" << cp.level
<< ", stored=" << (cp.stored ? "yes" : "no")
<< (cp.stored ? "" : " (placeholder)")
<< "\n";
}
mfem::out << "\n";
MFEM_VERIFY(m > 0, "Forward produced m=0 steps; nothing to do.");
// ---------------- Backward sweep (adjoint) ----------------
double lambda = (u_m - target); // terminal adjoint = dJ/du_m
my_state u_work (0.0,0.0,0.0); // scratch primal state u_i
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; } // avoid signed underflow
}
const double dJ_du0_adjoint = lambda;
// analytic solution
const double sol=u0*exp(alpha*Tfinal)/(1.0-u0+u0*exp(alpha*Tfinal));
const double grd=exp(alpha*Tfinal)/std::pow(1.0-u0+u0*exp(alpha*Tfinal),2.0);
mfem::out << "true sol = "<<sol<<" \n";
const double dJ_du0_fd = (sol - target) * grd;
const double abs_err = std::abs(dJ_du0_adjoint - dJ_du0_fd);
const double rel_err = abs_err / (std::abs(dJ_du0_fd) + 1e-30);
mfem::out << "[Scalar] Gradient check (dJ/du0):\n";
mfem::out << " adjoint = " << dJ_du0_adjoint << "\n";
mfem::out << " true grad = " << dJ_du0_fd << "\n";
mfem::out << " abs err = " << abs_err << "\n";
mfem::out << " rel err = " << rel_err << "\n\n";
return 0;
}
+168
View File
@@ -0,0 +1,168 @@
#include "mfem.hpp"
#include <cmath>
#include <iomanip>
using namespace mfem;
// Scalar logistic ODE:
// du/dt = alpha*u*(1-u)
// Explicit Euler with variable dt(i):
// u_{i+1} = u_i + dt_i*alpha*u_i*(1-u_i)
// Objective:
// J = 0.5*(u_m - target)^2
// Discrete adjoint (scalar):
// lambda_m = (u_m - target)
// lambda_i = (dF/du at u_i)^T * lambda_{i+1}
// dF/du = 1 + dt_i*alpha*(1 - 2*u_i)
int main(int argc, char *argv[])
{
int s = 3;
double alpha = 2.0;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
double u0 = 0.2;
double target = 0.7;
double eps = 1e-7;
OptionsParser args(argc, argv);
args.AddOption(&s, "-s", "--checkpoints",
"Checkpoint budget s (real checkpoints).");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&dt0, "-dt0", "--dt0",
"Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega",
"Omega in dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal",
"Terminate when accumulated time reaches Tfinal.");
args.AddOption(&u0, "-u0", "--u0", "Initial scalar state u0.");
args.AddOption(&target, "-ut", "--target",
"Target value in J=0.5*(u_m-target)^2.");
args.AddOption(&eps, "-eps", "--fd-eps", "Finite-difference epsilon.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
using Step = mfem::DynamicCheckpointing<double>::Step;
auto dt_func = [&](Step i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
// Checkpoint manager:
// State = double
// Snapshot= double
mfem::DynamicCheckpointing<double> ckpt(s);
auto make_snapshot = [](const double &u) -> double { return u; };
auto restore_snapshot = [](const double &snap, double &state) { state = snap; };
auto primal_step = [&](double &u, Step i)
{
const double dt = dt_func(i);
u = u + dt * alpha * u * (1.0 - u);
};
auto adjoint_step = [&](double &lambda, const double &u_i, Step i)
{
const double dt = dt_func(i);
const double dF_du = 1.0 + dt * alpha * (1.0 - 2.0 * u_i);
lambda = dF_du * lambda;
};
// ---------------- Forward sweep (unknown m) ----------------
double u = u0;
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const Step m = i;
const double u_m = u;
const double J = 0.5 * (u_m - target) * (u_m - target);
mfem::out << std::setprecision(15);
mfem::out << "\n[Scalar] Forward finished:\n";
mfem::out << " m (steps) = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " u_m = " << u_m << "\n";
mfem::out << " J = " << J << "\n\n";
mfem::out <<
"[Scalar] Checkpoint set after forward sweep (step, level, stored):\n";
for (const auto &cp : ckpt.GetCheckpointInfo())
{
mfem::out << " step=" << cp.step
<< ", level=" << cp.level
<< ", stored=" << (cp.stored ? "yes" : "no")
<< (cp.stored ? "" : " (placeholder)")
<< "\n";
}
mfem::out << "\n";
MFEM_VERIFY(m > 0, "Forward produced m=0 steps; nothing to do.");
// ---------------- Backward sweep (adjoint) ----------------
double lambda = (u_m - target); // terminal adjoint = dJ/du_m
double u_work = 0.0; // scratch primal state u_i
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; } // avoid signed underflow
}
const double dJ_du0_adjoint = lambda;
// ---------------- Finite-difference gradient check ----------------
auto forward_only_J = [&](double u_init)
{
double uu = u_init;
double tt = 0.0;
Step k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func(k);
++k;
}
const double r = (uu - target);
return 0.5 * r * r;
};
const double Jp = forward_only_J(u0 + eps);
const double Jm = forward_only_J(u0 - eps);
const double dJ_du0_fd = (Jp - Jm) / (2.0 * eps);
const double abs_err = std::abs(dJ_du0_adjoint - dJ_du0_fd);
const double rel_err = abs_err / (std::abs(dJ_du0_fd) + 1e-30);
mfem::out << "[Scalar] Gradient check (dJ/du0):\n";
mfem::out << " adjoint = " << dJ_du0_adjoint << "\n";
mfem::out << " FD = " << dJ_du0_fd << "\n";
mfem::out << " abs err = " << abs_err << "\n";
mfem::out << " rel err = " << rel_err << "\n\n";
return 0;
}
+199
View File
@@ -0,0 +1,199 @@
#include "mfem.hpp"
#include <cmath>
#include <iomanip>
using namespace mfem;
// Vector logistic ODE (componentwise):
// du/dt = alpha*u*(1-u) (applied to each component)
// Explicit Euler:
// u_{i+1}[j] = u_i[j] + dt_i*alpha*u_i[j]*(1-u_i[j])
// Objective:
// J = 0.5*||u_m - u_target||^2
// Terminal adjoint:
// lambda_m = u_m - u_target
// Adjoint step (componentwise):
// lambda_i[j] = (1 + dt_i*alpha*(1 - 2*u_i[j])) * lambda_{i+1}[j]
int main(int argc, char *argv[])
{
int s = 3;
int n = 16;
double alpha = 2.0;
double dt0 = 0.02;
double omega = 0.2;
double Tfinal = 1.0;
double target_val = 0.7;
double eps = 1e-7;
OptionsParser args(argc, argv);
args.AddOption(&s, "-s", "--checkpoints",
"Checkpoint budget s (real checkpoints).");
args.AddOption(&n, "-n", "--size", "Vector dimension n.");
args.AddOption(&alpha, "-a", "--alpha", "Logistic growth alpha.");
args.AddOption(&dt0, "-dt0", "--dt0",
"Base dt for dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&omega, "-om", "--omega",
"Omega in dt(i)=dt0*(1+0.5*sin(omega*i)).");
args.AddOption(&Tfinal, "-T", "--tfinal",
"Terminate when accumulated time reaches Tfinal.");
args.AddOption(&target_val,"-tv", "--target",
"Target value for each component.");
args.AddOption(&eps, "-eps", "--fd-eps",
"Finite-difference epsilon (directional).");
args.Parse();
if (!args.Good())
{
args.PrintUsage(mfem::out);
return 1;
}
args.PrintOptions(mfem::out);
MFEM_VERIFY(s > 0, "Need s > 0.");
MFEM_VERIFY(n > 0, "Need n > 0.");
MFEM_VERIFY(dt0 > 0.0, "Need dt0 > 0.");
MFEM_VERIFY(Tfinal > 0.0, "Need Tfinal > 0.");
using Step = mfem::DynamicCheckpointing<mfem::Vector>::Step;
auto dt_func = [&](Step i)
{
const double dt = dt0 * (1.0 + 0.5 * std::sin(omega * double(i)));
MFEM_VERIFY(dt > 0.0, "dt_func produced non-positive dt.");
return dt;
};
// Checkpoint manager:
// State = mfem::Vector
// Snapshot= mfem::Vector
mfem::DynamicCheckpointing<mfem::Vector> ckpt(s);
auto make_snapshot = [](const mfem::Vector &u) -> mfem::Vector { return u; };
auto restore_snapshot = [](const mfem::Vector &snap, mfem::Vector &state) { state = snap; };
auto primal_step = [&](mfem::Vector &u, Step i)
{
const double dt = dt_func(i);
for (int j = 0; j < u.Size(); ++j)
{
const double uj = u[j];
u[j] = uj + dt * alpha * uj * (1.0 - uj);
}
};
auto adjoint_step = [&](mfem::Vector &lambda, const mfem::Vector &u_i, Step i)
{
const double dt = dt_func(i);
MFEM_ASSERT(lambda.Size() == u_i.Size(), "lambda and u_i size mismatch");
for (int j = 0; j < lambda.Size(); ++j)
{
const double dF_du = 1.0 + dt * alpha * (1.0 - 2.0 * u_i[j]);
lambda[j] *= dF_du;
}
};
// Initial condition and target
mfem::Vector u0(n), u_target(n);
for (int j = 0; j < n; ++j)
{
u0[j] = 0.2 + 0.05 * std::cos(0.7 * (j + 1));
}
u_target = target_val;
// ---------------- Forward sweep (unknown m) ----------------
mfem::Vector u = u0;
double t_phys = 0.0;
Step i = 0;
while (t_phys < Tfinal)
{
ckpt.ForwardStep(i, u, primal_step, make_snapshot);
t_phys += dt_func(i);
++i;
}
const Step m = i;
const mfem::Vector u_m = u;
mfem::Vector diff(u_m);
diff -= u_target;
const double J = 0.5 * mfem::InnerProduct(diff, diff);
mfem::out << std::setprecision(15);
mfem::out << "\n[Vector] Forward finished:\n";
mfem::out << " m (steps) = " << m << "\n";
mfem::out << " t_phys = " << t_phys << "\n";
mfem::out << " J = " << J << "\n";
mfem::out << " ||u_m|| = " << u_m.Norml2() << "\n";
mfem::out << " ||u_m-ut||= " << diff.Norml2() << "\n\n";
mfem::out <<
"[Vector] Checkpoint set after forward sweep (step, level, stored):\n";
for (const auto &cp : ckpt.GetCheckpointInfo())
{
mfem::out << " step=" << cp.step
<< ", level=" << cp.level
<< ", stored=" << (cp.stored ? "yes" : "no")
<< (cp.stored ? "" : " (placeholder)")
<< "\n";
}
mfem::out << "\n";
MFEM_VERIFY(m > 0, "Forward produced m=0 steps; nothing to do.");
// ---------------- Backward sweep (adjoint) ----------------
mfem::Vector lambda = diff; // terminal = dJ/du_m
mfem::Vector u_work(n); // scratch primal u_i
for (Step j = m - 1; j >= 0; --j)
{
ckpt.BackwardStep(j, lambda, u_work,
primal_step, adjoint_step,
make_snapshot, restore_snapshot);
if (j == 0) { break; }
}
const mfem::Vector &grad_u0 = lambda;
// ---------------- Directional FD check ----------------
mfem::Vector v(n);
for (int j = 0; j < n; ++j) { v[j] = std::sin(0.3 * (j + 1)) + 0.1; }
auto forward_only_J = [&](const mfem::Vector &u_init)
{
mfem::Vector uu = u_init;
double tt = 0.0;
Step k = 0;
while (tt < Tfinal)
{
primal_step(uu, k);
tt += dt_func(k);
++k;
}
mfem::Vector dd(uu);
dd -= u_target;
return 0.5 * mfem::InnerProduct(dd, dd);
};
mfem::Vector u_plus(u0), u_minus(u0);
u_plus.Add(eps, v);
u_minus.Add(-eps, v);
const double Jp = forward_only_J(u_plus);
const double Jm = forward_only_J(u_minus);
const double dJ_dir_fd = (Jp - Jm) / (2.0 * eps);
const double dJ_dir_adj = mfem::InnerProduct(grad_u0, v);
const double abs_err = std::abs(dJ_dir_adj - dJ_dir_fd);
const double rel_err = abs_err / (std::abs(dJ_dir_fd) + 1e-30);
mfem::out << "[Vector] Directional derivative check:\n";
mfem::out << " v·grad adjoint = " << dJ_dir_adj << "\n";
mfem::out << " FD directional = " << dJ_dir_fd << "\n";
mfem::out << " abs err = " << abs_err << "\n";
mfem::out << " rel err = " << rel_err << "\n\n";
return 0;
}
+92
View File
@@ -0,0 +1,92 @@
# Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
# at the Lawrence Livermore National Laboratory. All Rights reserved. See files
# LICENSE and NOTICE for details. LLNL-CODE-806117.
#
# This file is part of the MFEM library. For more information and source code
# availability visit https://mfem.org.
#
# MFEM is free software; you can redistribute it and/or modify it under the
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
# Use the MFEM build directory
MFEM_DIR ?= ../../..
MFEM_BUILD_DIR ?= ../../..
MFEM_INSTALL_DIR ?= ../../../mfem
SRC = $(if $(MFEM_DIR:../../..=),$(MFEM_DIR)/miniapps/mtop/chpt/,)
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
MFEM_LIB_FILE = mfem_is_not_built
-include $(CONFIG_MK)
SEQ_MINIAPPS = mtop_ex_scalar_chpt mtop_ex_adapt_chpt mtop_ex_vector_chpt \
mtop_ex_fixedslot mtop_ex_file mtop_ex_revolve mtop_ex_dyn \
mtop_ex_rev_fixedslot
PAR_MINIAPPS =
MINIAPPS_CLEAN := $(SEQ_MINIAPPS) $(PAR_MINIAPPS)
ifeq ($(MFEM_USE_MPI),NO)
MINIAPPS = $(SEQ_MINIAPPS)
else
MINIAPPS = $(SEQ_MINIAPPS) $(PAR_MINIAPPS)
endif
.SUFFIXES:
.SUFFIXES: .o .cpp .mk
.PHONY: all clean clean-build clean-exec
.PRECIOUS: %.o
# Remove built-in rules
%: %.cpp
%.o: %.cpp
all: $(MINIAPPS)
# Rules for building the miniapps
mtop_ex_scalar_chpt: $(SRC)ex_scalar_field.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
mtop_ex_adapt_chpt: $(SRC)ex_scalar_adaptive.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
mtop_ex_vector_chpt: $(SRC)ex_vector_field.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
mtop_ex_fixedslot: $(SRC)ex_fixedslot_scalar_vector.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
mtop_ex_file: $(SRC)ex_fileio_scalar_vector_mfem.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
mtop_ex_revolve: $(SRC)ex_revolve.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
mtop_ex_dyn: $(SRC)ex_dynamic_chpt.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
mtop_ex_rev_fixedslot: $(SRC)ex_revolve_chpt_fixedslot.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
MFEM_TESTS = MINIAPPS
include $(MFEM_TEST_MK)
TEST_NAME := MTop checkpointing miniapp
%-test-seq: %
@$(call mfem-test,$<,, $(TEST_NAME))
# Testing: "test" target and mfem-test* variables are defined in config/test.mk
# Generate an error message if the MFEM library is not built and exit
$(MFEM_LIB_FILE):
$(error The MFEM library is not built)
clean: clean-build clean-exec
clean-build:
rm -f *.o *~ $(MINIAPPS_CLEAN)
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
rm -rf chk_scalar_files chk_vector_files dyn_ckpts
rm -f dynamic_ckpts.bin revolve_ckpts.bin
+1 -1
View File
@@ -83,7 +83,7 @@ real_t IntegrateBC(const ParGridFunction &x, const Array<int> &bdr,
/// where A is
/// A = div ( Theta(x) grad + Id ) u(x)
/// and alpha is given as
/// alpha = (2 nu + dim) / 2.
/// alpha = (2 nu + dim) / 4.
/// Theta (anisotropy tensor) and nu (smoothness) can be specified in the
/// constructor. Traditionally, the SPDE method requires the specification of
/// a white noise right hands side. SPDESolver accepts arbitrary right hand
+337 -8
View File
@@ -12,6 +12,8 @@
#include "unit_tests.hpp"
#include "mfem.hpp"
#include <random>
using namespace mfem;
#ifdef MFEM_USE_GSLIB
namespace gslib_test
@@ -36,6 +38,165 @@ void F_exact(const Vector &p, Vector &F)
enum class Space { H1, L2 };
enum class SurfaceMeshType { Segment2D, Segment3D, Quad3D, Tri3D };
const char *SurfaceMeshName(const SurfaceMeshType type)
{
switch (type)
{
case SurfaceMeshType::Segment2D: return "segment-2d";
case SurfaceMeshType::Segment3D: return "segment-3d";
case SurfaceMeshType::Quad3D: return "quad-3d";
case SurfaceMeshType::Tri3D: return "tri-3d";
}
return "unknown";
}
int SurfaceSpaceDim(const SurfaceMeshType type)
{
switch (type)
{
case SurfaceMeshType::Segment2D: return 2;
case SurfaceMeshType::Segment3D: return 3;
case SurfaceMeshType::Quad3D: return 3;
case SurfaceMeshType::Tri3D: return 3;
}
return -1;
}
Mesh MakeSurfaceMesh(const SurfaceMeshType type, const int ne)
{
switch (type)
{
case SurfaceMeshType::Segment2D:
return Mesh::MakeCartesian1D(ne);
case SurfaceMeshType::Segment3D:
return Mesh::MakeCartesian1D(ne);
case SurfaceMeshType::Quad3D:
return Mesh::MakeCartesian2D(ne, ne, Element::QUADRILATERAL);
case SurfaceMeshType::Tri3D:
return Mesh::MakeCartesian2D(ne, ne, Element::TRIANGLE);
}
MFEM_ABORT("Unknown surface mesh type.");
return Mesh();
}
void GetSurfaceInteriorPoints(Mesh &mesh, const int npt_per_el,
const int ordering, Vector &xyz,
const int p0 = 0)
{
MFEM_VERIFY(mesh.GetNodes() != nullptr, "Mesh nodes are required.");
const int sdim = mesh.SpaceDimension();
const int npt = xyz.Size()/sdim;
MFEM_VERIFY((p0 + mesh.GetNE()*npt_per_el)*sdim <= xyz.Size(),
"Output vector is too small.");
Vector point(sdim);
std::mt19937 gen(123);
std::uniform_real_distribution<double> uni(0.01, 0.99);
int p = p0;
for (int e = 0; e < mesh.GetNE(); e++)
{
ElementTransformation *T = mesh.GetElementTransformation(e);
const Geometry::Type geom = mesh.GetElementBaseGeometry(e);
for (int j = 0; j < npt_per_el; j++)
{
IntegrationPoint ip;
real_t xv = uni(gen);
if (geom == Geometry::SEGMENT)
{
ip.x = xv;
}
else if (geom == Geometry::SQUARE)
{
ip.Set2(xv, uni(gen));
}
else
{
MFEM_VERIFY(geom == Geometry::TRIANGLE,
"Unsupported surface element geometry.");
ip.Set2(xv, uni(gen)*(1.0 - xv));
}
T->Transform(ip, point);
for (int d = 0; d < sdim; d++)
{
const int idx = (ordering == Ordering::byNODES) ?
d*npt + p :
p*sdim + d;
xyz(idx) = point(d);
}
p++;
}
}
}
void GetSurfaceBoundaryPoints(Mesh &mesh, const int npt_per_el,
const int ordering, Vector &xyz,
const int p0 = 0)
{
MFEM_VERIFY(mesh.GetNodes() != nullptr, "Mesh nodes are required.");
const int sdim = mesh.SpaceDimension();
const int npt = xyz.Size()/sdim;
MFEM_VERIFY((p0 + mesh.GetNE()*npt_per_el)*sdim <= xyz.Size(),
"Output vector is too small.");
Vector point(sdim);
std::mt19937 gen(246);
std::uniform_real_distribution<double> uni(0.01, 0.99);
int p = p0;
for (int e = 0; e < mesh.GetNE(); e++)
{
ElementTransformation *T = mesh.GetElementTransformation(e);
const Geometry::Type geom = mesh.GetElementBaseGeometry(e);
for (int j = 0; j < npt_per_el; j++)
{
IntegrationPoint ip;
if (geom == Geometry::SEGMENT)
{
MFEM_VERIFY(npt_per_el == 2,
"Segment boundary sampling requires npt_per_el = 2.");
ip.x = (j == 0) ? 0.0 : 1.0;
}
else
{
const double t = uni(gen);
if (geom == Geometry::SQUARE)
{
switch (j % 4)
{
case 0: ip.Set2(t, 0.0); break;
case 1: ip.Set2(1.0, t); break;
case 2: ip.Set2(t, 1.0); break;
case 3: ip.Set2(0.0, t); break;
}
}
else
{
MFEM_VERIFY(geom == Geometry::TRIANGLE,
"Unsupported surface element geometry.");
switch (j % 3)
{
case 0: ip.Set2(t, 0.0); break;
case 1: ip.Set2(t, 1.0 - t); break;
case 2: ip.Set2(0.0, t); break;
}
}
}
T->Transform(ip, point);
for (int d = 0; d < sdim; d++)
{
const int idx = (ordering == Ordering::byNODES) ?
d*npt + p :
p*sdim + d;
xyz(idx) = point(d);
}
p++;
}
}
}
TEST_CASE("GSLIBInterpolate", "[GSLIBInterpolate][GSLIB]")
{
auto space = GENERATE(Space::H1, Space::L2);
@@ -190,6 +351,92 @@ TEST_CASE("GSLIBInterpolate", "[GSLIBInterpolate][GSLIB]")
delete c_fec;
}
TEST_CASE("GSLIBSurfInterpolate", "[GSLIBSurfInterpolate][GSLIB]")
{
auto surface_mesh_type = GENERATE(SurfaceMeshType::Segment2D,
SurfaceMeshType::Segment3D,
SurfaceMeshType::Quad3D,
SurfaceMeshType::Tri3D);
func_order = GENERATE(1, 2);
int mesh_order = GENERATE(1, 2);
int mesh_node_ordering = GENERATE(0, 1);
int point_ordering = GENERATE(0, 1);
int ncomp = GENERATE(1, 2);
int gf_ordering = GENERATE(0, 1);
int func_out_ordering = GENERATE(0, 1);
const char *mesh_name = SurfaceMeshName(surface_mesh_type);
CAPTURE(mesh_name, func_order, mesh_order, mesh_node_ordering,
point_ordering, ncomp, gf_ordering, func_out_ordering);
if (ncomp == 1 && gf_ordering == 1)
{
return;
}
Mesh mesh = MakeSurfaceMesh(surface_mesh_type, 4);
const int sdim = SurfaceSpaceDim(surface_mesh_type);
mesh.SetCurvature(mesh_order, false, sdim, mesh_node_ordering);
H1_FECollection c_fec(func_order, mesh.Dimension());
FiniteElementSpace c_fespace(&mesh, &c_fec, ncomp, gf_ordering);
GridFunction field_vals(&c_fespace);
VectorFunctionCoefficient F(ncomp, F_exact);
field_vals.ProjectCoefficient(F);
const int npt_per_el = 8;
const int pts_cnt = mesh.GetNE()*npt_per_el;
Vector vxyz(pts_cnt*sdim);
GetSurfaceInteriorPoints(mesh, npt_per_el, point_ordering, vxyz);
Vector interp_vals(pts_cnt*ncomp);
FindPointsGSLIB finder;
finder.SetupSurf(mesh);
finder.SetL2AvgType(FindPointsGSLIB::NONE);
finder.Interpolate(vxyz, field_vals, interp_vals, point_ordering,
func_out_ordering);
Array<unsigned int> code_out = finder.GetCode();
Vector dist_p_out = finder.GetDist();
int not_found = 0;
double err = 0.0, max_err = 0.0, max_dist = 0.0;
Vector pos(sdim);
Vector exact_val(ncomp);
for (int i = 0; i < pts_cnt; i++)
{
max_dist = std::max(max_dist, dist_p_out(i));
for (int d = 0; d < sdim; d++)
{
const int idx = (point_ordering == Ordering::byNODES) ?
d*pts_cnt + i :
i*sdim + d;
pos(d) = vxyz(idx);
}
F_exact(pos, exact_val);
for (int j = 0; j < ncomp; j++)
{
if (code_out[i] < 2)
{
err = func_out_ordering == Ordering::byNODES ?
fabs(exact_val(j) - interp_vals[i + j*pts_cnt]) :
fabs(exact_val(j) - interp_vals[i*ncomp + j]);
max_err = std::max(max_err, err);
}
else if (j == 0)
{
not_found++;
}
}
}
REQUIRE(max_err < 1e-12);
REQUIRE(max_dist < 1e-10);
REQUIRE(not_found == 0);
}
// Generates meshes with different element types, followed by points at
// element faces and interior, and finally checks to see if these points are
// correctly detected at element boundary or not.
@@ -257,9 +504,8 @@ TEST_CASE("GSLIBFindAtElementBoundary",
int nptface = xyz.Size()/dim;
// Generate points inside each element
FiniteElementCollection *l2_fec = new L2_FECollection(l2_order, dim);
FiniteElementSpace l2_fespace =
FiniteElementSpace(&mesh, l2_fec, 1);
L2_FECollection l2_fec(l2_order, dim);
FiniteElementSpace l2_fespace(&mesh, &l2_fec, 1);
DenseMatrix vals;
DenseMatrix tr;
for (int e = 0; e < mesh.GetNE(); e++)
@@ -295,7 +541,92 @@ TEST_CASE("GSLIBFindAtElementBoundary",
cmax = std::max(code_out[i], cmax);
}
REQUIRE((cmin == 0 && cmax == 0)); // should be found inside element
delete l2_fec;
}
}
TEST_CASE("GSLIBSurfFindAtElementBoundary",
"[GSLIBSurfFindAtElementBoundary][GSLIB]")
{
auto surface_mesh_type = GENERATE(SurfaceMeshType::Segment2D,
SurfaceMeshType::Segment3D,
SurfaceMeshType::Quad3D,
SurfaceMeshType::Tri3D);
const char *mesh_name = SurfaceMeshName(surface_mesh_type);
CAPTURE(mesh_name);
Mesh mesh = MakeSurfaceMesh(surface_mesh_type, 4);
const int sdim = SurfaceSpaceDim(surface_mesh_type);
mesh.SetCurvature(2, false, sdim);
const int nptface_per_el = (mesh.Dimension() == 1) ? 2 : 8;
const int nptint_per_el = 8;
const int nptface = mesh.GetNE()*nptface_per_el;
const int nptint = mesh.GetNE()*nptint_per_el;
Vector xyz((nptface + nptint)*sdim);
GetSurfaceBoundaryPoints(mesh, nptface_per_el, Ordering::byVDIM, xyz, 0);
GetSurfaceInteriorPoints(mesh, nptint_per_el, Ordering::byVDIM, xyz,
nptface);
FindPointsGSLIB finder;
finder.SetupSurf(mesh);
finder.FindPoints(xyz, Ordering::byVDIM);
Array<unsigned int> code_out = finder.GetCode();
for (int i = 0; i < nptface; i++)
{
REQUIRE(code_out[i] == 1);
}
for (int i = nptface; i < nptface + nptint; i++)
{
REQUIRE(code_out[i] == 0);
}
}
TEST_CASE("GSLIBSurfAABBExpansion", "[GSLIBSurfAABBExpansion][GSLIB]")
{
auto surface_mesh_type = GENERATE(SurfaceMeshType::Segment2D,
SurfaceMeshType::Segment3D,
SurfaceMeshType::Quad3D,
SurfaceMeshType::Tri3D);
const char *mesh_name = SurfaceMeshName(surface_mesh_type);
CAPTURE(mesh_name);
constexpr double offset = 1.0e-3;
const int npt_per_el = 8;
Mesh mesh = MakeSurfaceMesh(surface_mesh_type, 4);
const int sdim = SurfaceSpaceDim(surface_mesh_type);
mesh.SetCurvature(2, false, sdim);
const int npt = mesh.GetNE()*npt_per_el;
Vector xyz(npt*sdim);
GetSurfaceInteriorPoints(mesh, npt_per_el, Ordering::byVDIM, xyz);
// offset them to move away from the surface
const int off_d = (surface_mesh_type == SurfaceMeshType::Segment2D) ? 1 : 2;
for (int i = 0; i < npt; i++)
{
xyz(i*sdim + off_d) += offset;
}
FindPointsGSLIB finder;
finder.SetupSurf(mesh, 0.0);
finder.FindPoints(xyz, Ordering::byVDIM);
Array<unsigned int> code_no_pad = finder.GetCode();
for (int i = 0; i < code_no_pad.Size(); i++)
{
REQUIRE(code_no_pad[i] == 2);
}
// make aabb at least big enough to include the offset points
Vector aabb_sz_inc({2.1*offset});
finder.SetupSurfWithAABBExpansion(mesh, aabb_sz_inc);
finder.FindPoints(xyz, Ordering::byVDIM);
Array<unsigned int> code_with_pad = finder.GetCode();
for (int i = 0; i < npt; i++)
{
REQUIRE(code_with_pad[i] == 1);
}
}
@@ -322,9 +653,8 @@ TEST_CASE("GSLIBInterpolateL2ElementBoundary",
mesh.SetCurvature(mesh_order);
// Set GridFunction to be interpolated
FiniteElementCollection *c_fec = new L2_FECollection(3, dim);
FiniteElementSpace c_fespace =
FiniteElementSpace(&mesh, c_fec, 1);
L2_FECollection c_fec(3, dim);
FiniteElementSpace c_fespace(&mesh, &c_fec, 1);
GridFunction field_vals(&c_fespace);
Array<int> dofs;
double leftval = 1.0;
@@ -366,7 +696,6 @@ TEST_CASE("GSLIBInterpolateL2ElementBoundary",
REQUIRE(interp_vals(0) == MFEM_Approx(0.5*(leftval+rightval)));
finder.FreeData();
delete c_fec;
}
#ifdef MFEM_USE_MPI
+34 -16
View File
@@ -328,7 +328,7 @@ TEST_CASE("Linear Form Extension", "[LinearFormExtension], [GPU]")
}
}
TEST_CASE("H(div) Linear Form Extension", "[LinearFormExtension], [GPU]")
TEST_CASE("Vector FE Linear Form Extension", "[LinearFormExtension], [GPU]")
{
const bool all = launch_all_non_regression_tests;
@@ -341,26 +341,44 @@ TEST_CASE("H(div) Linear Form Extension", "[LinearFormExtension], [GPU]")
Mesh mesh(mesh_file);
const int dim = mesh.Dimension();
CAPTURE(mesh_file, dim, p);
{
const auto space_type =
dim == 3 ? GENERATE(FiniteElement::DIV, FiniteElement::CURL)
: FiniteElement::DIV;
RT_FECollection fec(p, dim);
FiniteElementSpace fes(&mesh, &fec);
CAPTURE(mesh_file, dim, p, space_type);
VectorFunctionCoefficient coeff(dim, fvec_dim);
std::unique_ptr<FiniteElementCollection> fec;
LinearForm d1(&fes);
d1.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d1.UseFastAssembly(true);
d1.Assemble();
switch (space_type)
{
case FiniteElement::DIV:
fec.reset(new RT_FECollection(p, dim));
break;
case FiniteElement::CURL:
fec.reset(new ND_FECollection(p, dim));
break;
default:
MFEM_ABORT("unsupported space type");
}
FiniteElementSpace fes(&mesh, fec.get());
LinearForm d2(&fes);
d2.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d2.UseFastAssembly(false);
d2.Assemble();
VectorFunctionCoefficient coeff(dim, fvec_dim);
CAPTURE(d1.Norml2(), d2.Norml2());
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
LinearForm d1(&fes);
d1.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d1.UseFastAssembly(true);
d1.Assemble();
LinearForm d2(&fes);
d2.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d2.UseFastAssembly(false);
d2.Assemble();
CAPTURE(d1.Norml2(), d2.Norml2());
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
}
#ifdef MFEM_USE_MPI
+79 -4
View File
@@ -200,10 +200,71 @@ int CheckArrayEquality(const Array<T> &arr1, const Array<T> &arr2)
return wrong_ct;
}
// Apply a deterministic perturbation to particle data on host.
void PerturbParticleDataOnHost(std::vector<Particle> &particles)
{
for (auto &p : particles)
{
for (int f = -1; f < p.GetNFields(); f++)
{
Vector &field = f == -1 ? p.Coords() : p.Field(f);
field.HostReadWrite();
const real_t scale = (f == -1) ? 0.001 : 1.0;
for (int c = 0; c < field.Size(); c++)
{
field(c) += scale * (f + c + 2);
}
}
for (int t = 0; t < p.GetNTags(); t++)
{
p.Tag(t) += t + 1;
}
}
}
// Apply a deterministic perturbation to particle data on device.
void PerturbParticleDataOnDevice(ParticleSet &pset)
{
const int np = pset.GetNParticles();
// Shift coordinates and fields using the same per-component formula while
// honoring the ParticleVector ordering selected by the test.
for (int f = -1; f < pset.GetNFields(); f++)
{
ParticleVector &field = f == -1 ? pset.Coords() : pset.Field(f);
const int vdim = field.GetVDim();
const bool by_vdim = (field.GetOrdering() == Ordering::byVDIM);
const real_t scale = (f == -1) ? 0.001 : 1.0;
auto d_field = field.ReadWrite();
mfem::forall(np, [=] MFEM_HOST_DEVICE (int i)
{
for (int c = 0; c < vdim; c++)
{
const int idx = by_vdim ? i * vdim + c : i + c * np;
d_field[idx] += scale * (f + c + 2);
}
});
}
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag = pset.Tag(t);
auto d_tag = tag.ReadWrite();
mfem::forall(np, [=] MFEM_HOST_DEVICE (int i)
{
d_tag[i] += t + 1;
});
}
}
void TestRedistribute(Ordering::Type ordering)
{
int size = Mpi::WorldSize();
int rank = Mpi::WorldRank();
const bool use_device = Device::IsEnabled();
// Create a 3D hex mesh
Mesh m = Mesh::MakeCartesian3D(N_e, N_e, N_e, Element::Type::HEXAHEDRON);
@@ -252,15 +313,22 @@ void TestRedistribute(Ordering::Type ordering)
SECTION(std::string("Ordering: ") +
(ordering == Ordering::byNODES ? "byNODES" : "byVDIM"))
{
// Add the particles uniquely to each rank particleset
ParticleSet pset(MPI_COMM_WORLD, 0, SpaceDim, FieldVDims,
NumTags, ordering);
NumTags, ordering, use_device);
CHECK(pset.IsParticleRefValid() ==
(!use_device && ordering == Ordering::byVDIM));
for (int i = 0; i < N_rank; i++)
{
pset.AddParticle(all_particles[i*size+rank]);
}
if (use_device)
{
PerturbParticleDataOnDevice(pset);
PerturbParticleDataOnHost(all_particles);
}
// Find points
FindPointsGSLIB finder(MPI_COMM_WORLD);
finder.Setup(pmesh);
@@ -270,6 +338,7 @@ void TestRedistribute(Ordering::Type ordering)
int code_1_count = 0;
int code_2_count = 0;
const Array<unsigned int> &code = finder.GetCode();
code.HostRead();
for (int i = 0; i < code.Size(); i++)
{
if (code[i] == 1)
@@ -292,6 +361,7 @@ void TestRedistribute(Ordering::Type ordering)
finder.FindPoints(pset.Coords(), ordering);
const Array<unsigned int> &procs = finder.GetProc();
procs.HostRead();
int wrong_proc_count = 0;
for (int i = 0; i < procs.Size(); i++)
@@ -307,6 +377,11 @@ void TestRedistribute(Ordering::Type ordering)
// Check that coordinates + fields + tags are all still correct
int wrong_particle_count = 0;
pset.GetIDs().HostRead();
for (int t = 0; t < pset.GetNTags(); t++)
{
pset.Tag(t).HostRead();
}
for (int i = 0; i < pset.GetNParticles(); i++)
{
Particle &actual_p = all_particles[pset.GetIDs()[i]];
@@ -317,13 +392,13 @@ void TestRedistribute(Ordering::Type ordering)
wrong_particle_count++;
}
}
MPI_Allreduce(MPI_IN_PLACE, &wrong_proc_count, 1, MPI_INT, MPI_SUM,
MPI_Allreduce(MPI_IN_PLACE, &wrong_particle_count, 1, MPI_INT, MPI_SUM,
MPI_COMM_WORLD);
CHECK(wrong_particle_count == 0);
}
}
TEST_CASE("Particle Redistribution", "[ParticleSet][Parallel]")
TEST_CASE("Particle Redistribution", "[ParticleSet][Parallel][GPU]")
{
TestRedistribute(Ordering::byNODES);
TestRedistribute(Ordering::byVDIM);
+2 -2
View File
@@ -124,8 +124,8 @@ TEST_CASE("3D ProjectBdrCoefficientNormal Scalar",
const double tol = 1e-6;
const char bdrs_axis[] = {2, 1, 0, 1, 0, 2};
const char bdrs_sign[] = {-1, -1, +1, +1, -1, +1};
const int bdrs_axis[] = {2, 1, 0, 1, 0, 2};
const int bdrs_sign[] = {-1, -1, +1, +1, -1, +1};
for (int type = (int)Element::TETRAHEDRON;
type <= (int)Element::HEXAHEDRON; type++)
@@ -113,3 +113,65 @@ TEST_CASE("ComplexOperator Quaternion Tests", "[ComplexOperator]")
REQUIRE(qikx.Normlinf() < tol);
}
}
#ifdef MFEM_USE_MPI
TEST_CASE("ComplexHypreParMatrix GetSystemMatrix",
"[ComplexOperator][Parallel][GPU]")
{
// This test reproduces the issue described in PR #5200 on GitHub. See also
// the follow up PR #5346.
// 1. Construct ComplexHypreParMatrix similar to ex25p.
const char mesh_file[] = "../../data/inline-quad.mesh";
Mesh *mesh = new Mesh(mesh_file, 1, 1);
int dim = mesh->Dimension();
int ref_levels = 1;
for (int l = 0; l < ref_levels; l++)
{
mesh->UniformRefinement();
}
ParMesh pmesh(MPI_COMM_WORLD, *mesh);
delete mesh;
int par_ref_levels = 1;
for (int l = 0; l < par_ref_levels; l++)
{
pmesh.UniformRefinement();
}
int order = 1;
ND_FECollection fec(order, dim);
ParFiniteElementSpace fespace(&pmesh, &fec);
Array<int> ess_tdof_list;
Array<int> ess_bdr;
if (pmesh.bdr_attributes.Size())
{
ess_bdr.SetSize(pmesh.bdr_attributes.Max());
ess_bdr = 1;
}
fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
ComplexOperator::Convention conv = ComplexOperator::HERMITIAN;
VectorConstantCoefficient f(Vector{1_r, 2_r});
ParComplexLinearForm b(&fespace, conv);
b.AddDomainIntegrator(NULL, new VectorFEDomainLFIntegrator(f));
b = 0.0;
b.Assemble();
ParComplexGridFunction x(&fespace);
x = 0.0;
ConstantCoefficient one(1_r);
ParSesquilinearForm a(&fespace, conv);
a.AddDomainIntegrator(new CurlCurlIntegrator(one),
new CurlCurlIntegrator(one));
a.AddDomainIntegrator(new VectorFEMassIntegrator(one),
new VectorFEMassIntegrator(one));
a.Assemble();
OperatorPtr Ah;
Vector B, X;
a.FormLinearSystem(ess_tdof_list, x, b, Ah, X, B);
// 2. Test the call to ComplexHypreParMatrix::GetSystemMatrix and destroying
// the returned matrix.
HypreParMatrix *A = Ah.As<ComplexHypreParMatrix>()->GetSystemMatrix();
delete A;
}
#endif // MFEM_USE_MPI