Compare commits
117
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b86fb5308a | ||
|
|
4463d921f6 | ||
|
|
20c657559d | ||
|
|
7a999efb0d | ||
|
|
442ac18b65 | ||
|
|
5097a44411 | ||
|
|
e84fef4191 | ||
|
|
64ef39bbe6 | ||
|
|
ec39b3509c | ||
|
|
399d8e1e9b | ||
|
|
10dbed9658 | ||
|
|
f09a062c04 | ||
|
|
0c97d6f375 | ||
|
|
aed9c8ef4a | ||
|
|
e4e85e28ef | ||
|
|
fff973f192 | ||
|
|
775f06c43b | ||
|
|
18ff1d8289 | ||
|
|
5a0962c674 | ||
|
|
0d999709e6 | ||
|
|
c2649eb998 | ||
|
|
a1ce49fb57 | ||
|
|
f58cfc8170 | ||
|
|
6c837d2954 | ||
|
|
5b37c3b595 | ||
|
|
e49f9f7988 | ||
|
|
7c36b55628 | ||
|
|
faa73ef554 | ||
|
|
ecb6b06aa0 | ||
|
|
af4649a088 | ||
|
|
a9f58f3982 | ||
|
|
6de6675783 | ||
|
|
085ee02a29 | ||
|
|
9a124335a7 | ||
|
|
91d5e490aa | ||
|
|
dd931b2584 | ||
|
|
8a42ea2834 | ||
|
|
24e5d5fc0a | ||
|
|
6722dd7a70 | ||
|
|
cb862cbfa1 | ||
|
|
f7445844ba | ||
|
|
672e2a442b | ||
|
|
416536eb9d | ||
|
|
f557e348da | ||
|
|
881598e5da | ||
|
|
564b7ab4ec | ||
|
|
3f2f925400 | ||
|
|
463e34dc7f | ||
|
|
9a456b908e | ||
|
|
616839388a | ||
|
|
2fda3db982 | ||
|
|
4823a33a6a | ||
|
|
a96319e0be | ||
|
|
5f4283f512 | ||
|
|
8735d28561 | ||
|
|
a10c7a943b | ||
|
|
96eff4684f | ||
|
|
b6255fc825 | ||
|
|
18d27f6ffb | ||
|
|
7bfb57ef17 | ||
|
|
ab394d795e | ||
|
|
cad9cc4c82 | ||
|
|
4dc741ca48 | ||
|
|
918eb114d3 | ||
|
|
3341acf0f7 | ||
|
|
287cb24d0a | ||
|
|
70370b6241 | ||
|
|
ea291fb157 | ||
|
|
fce4ae7bb0 | ||
|
|
ef44f047aa | ||
|
|
ae002f7369 | ||
|
|
e4cd3f9e18 | ||
|
|
abdcf82d70 | ||
|
|
ad93d526b7 | ||
|
|
87c1a5cb77 | ||
|
|
89ad250940 | ||
|
|
d5dec97d23 | ||
|
|
2d401bcb74 | ||
|
|
4f383f4b19 | ||
|
|
7f35ecb8f5 | ||
|
|
9e4d9799dc | ||
|
|
ac4e558164 | ||
|
|
691cd8a687 | ||
|
|
cdc327a511 | ||
|
|
0d2e8f93e6 | ||
|
|
47c9ad2e34 | ||
|
|
ed862050b2 | ||
|
|
3c6c1eb634 | ||
|
|
22851a9463 | ||
|
|
38df8156b9 | ||
|
|
542467fd6a | ||
|
|
5986542e3d | ||
|
|
5163313285 | ||
|
|
2201f3354a | ||
|
|
f5b03af9d6 | ||
|
|
80c7823ac7 | ||
|
|
a443f003bb | ||
|
|
1730b05078 | ||
|
|
8a522f5e7d | ||
|
|
fcbd105b82 | ||
|
|
b82dcf1387 | ||
|
|
d3471aef59 | ||
|
|
822555df0b | ||
|
|
4626d65ac1 | ||
|
|
38a80ea0e4 | ||
|
|
590f954d6f | ||
|
|
bc5fc2b0f3 | ||
|
|
0f78d8aa5c | ||
|
|
3f98aa1cfb | ||
|
|
feecd75ff3 | ||
|
|
248bdcc149 | ||
|
|
e4e354834d | ||
|
|
d64a6d6255 | ||
|
|
510387a605 | ||
|
|
e99b2a8410 | ||
|
|
6608111315 | ||
|
|
b7253275fc |
@@ -25,7 +25,7 @@ runs:
|
||||
steps:
|
||||
- uses: ./.github/actions/sanitize/config
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
if: ${{env.DEBUG == 'true'}}
|
||||
id: debug
|
||||
with:
|
||||
|
||||
@@ -36,7 +36,7 @@ runs:
|
||||
steps:
|
||||
- uses: ./.github/actions/sanitize/config
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
if: ${{env.DEBUG == 'true' && inputs.cache-skip != 'true'}}
|
||||
id: debug
|
||||
with:
|
||||
|
||||
@@ -23,7 +23,7 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/cache/restore@v4 # Cache for LLVM libcxx
|
||||
- uses: actions/cache/restore@v5 # Cache for LLVM libcxx
|
||||
with:
|
||||
path: ${{env.LLVM_DIR}}
|
||||
fail-on-cache-miss: true
|
||||
@@ -32,14 +32,14 @@ runs:
|
||||
- uses: ./.github/actions/sanitize/mpi
|
||||
if: ${{inputs.par == 'true'}}
|
||||
|
||||
- uses: actions/cache/restore@v4 # Cache for Hypre
|
||||
- uses: actions/cache/restore@v5 # Cache for Hypre
|
||||
if: ${{inputs.par == 'true'}}
|
||||
with:
|
||||
path: ${{env.HYPRE_DIR}}
|
||||
fail-on-cache-miss: true
|
||||
key: ${{runner.os}}-ompi-build-${{env.HYPRE_DIR}}-int32-fp64-v2.5
|
||||
|
||||
- uses: actions/cache/restore@v4 # Cache for Metis
|
||||
- uses: actions/cache/restore@v5 # Cache for Metis
|
||||
if: ${{inputs.par == 'true'}}
|
||||
with:
|
||||
path: ${{env.METIS_DIR}}
|
||||
@@ -51,13 +51,13 @@ runs:
|
||||
run: ln -s -f ${{env.HYPRE_DIR}} hypre && ln -s -f ${{env.METIS_DIR}} metis-4.0
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache/restore@v4 # Cache for LSAN suppression file
|
||||
- uses: actions/cache/restore@v5 # Cache for LSAN suppression file
|
||||
with:
|
||||
path: ${{env.LSAN_DIR}}
|
||||
fail-on-cache-miss: true
|
||||
key: build-lsan-suppression-file
|
||||
|
||||
- uses: actions/checkout@v4 # Checkout the repository
|
||||
- uses: actions/checkout@v6 # Checkout the repository
|
||||
with:
|
||||
path: mfem
|
||||
# ref: ${{env.BRANCH}}
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
remove-docker-images: 'true'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# It's easier to reference named variables than indexes of the matrix
|
||||
- name: Set Environment
|
||||
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
# /home/runner/work/mfem/mfem/mfem
|
||||
# Note: Done now to access "install-hypre" and "install-metis" actions.
|
||||
- name: checkout mfem
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: ${{ env.MFEM_TOP_DIR }}
|
||||
# Fetch the complete history for codecov to access commits ID
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
- name: cache hypre
|
||||
id: hypre-cache
|
||||
if: matrix.mpi == 'par'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.HYPRE_TOP_DIR }}
|
||||
key: ${{ runner.os }}-ompi-build-${{ env.HYPRE_TOP_DIR }}-${{ matrix.hypre-target }}-${{ matrix.precision }}-v2.5
|
||||
@@ -255,7 +255,7 @@ jobs:
|
||||
- name: cache metis
|
||||
id: metis-cache
|
||||
if: matrix.mpi == 'par' && matrix.os != 'windows-latest'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.METIS_TOP_DIR }}
|
||||
key: ${{ runner.os }}-build-${{ env.METIS_TOP_DIR }}-v2.5
|
||||
@@ -270,7 +270,7 @@ jobs:
|
||||
- name: cache vcpkg (Windows)
|
||||
id: vcpkg-cache
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: vcpkg_cache
|
||||
key: ${{ runner.os }}-${{ matrix.mpi }}-vcpkg-v1
|
||||
@@ -295,7 +295,8 @@ jobs:
|
||||
export HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
brew update
|
||||
brew install enzyme
|
||||
ENZYME_LLVM=$(brew info enzyme | sed -n 's/^Required:.*\(llvm[^ ]*\).*/\1/p')
|
||||
ENZYME_LLVM=$(brew info enzyme | sed -n 's/^Required.*:.*\(llvm[^ ]*\).*/\1/p')
|
||||
echo "ENZYME_LLVM=$ENZYME_LLVM"
|
||||
LLVM_PREFIX=$(brew --prefix $ENZYME_LLVM)
|
||||
echo "LLVM_PREFIX=$LLVM_PREFIX" >> $GITHUB_ENV
|
||||
echo "OMPI_CC=$LLVM_PREFIX/bin/clang" >> $GITHUB_ENV
|
||||
|
||||
@@ -40,11 +40,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout MFEM
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: mfem
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Cache Hypre Install
|
||||
id: hypre-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.HYPRE_TOP_DIR }}
|
||||
key: ${{ runner.os }}-ompi-build-${{ env.HYPRE_TOP_DIR }}-v2.5
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
- name: Cache Metis Install
|
||||
id: metis-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.METIS_TOP_DIR }}
|
||||
key: ${{ runner.os }}-build-${{ env.METIS_TOP_DIR }}-v2.5
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
github.event.pull_request.head.repo.full_name != github.repository)
|
||||
steps:
|
||||
- name: checkout mfem
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: copyright check
|
||||
id: copyright
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
github.event.pull_request.head.repo.full_name != github.repository)
|
||||
steps:
|
||||
- name: checkout mfem
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: get astyle
|
||||
run: |
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
github.event.pull_request.head.repo.full_name != github.repository)
|
||||
steps:
|
||||
- name: checkout mfem
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: get doxygen and graphviz
|
||||
run: |
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout mfem
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: 2.19.0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/config
|
||||
- name: Cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{env.HYPRE_DIR}}
|
||||
key: ${{runner.os}}-ompi-build-${{env.HYPRE_DIR}}-int32-fp64-v2.5
|
||||
|
||||
@@ -27,13 +27,13 @@ jobs:
|
||||
llvm_use_sanitizer: "Undefined"
|
||||
name: ${{matrix.sanitizer}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/config
|
||||
with:
|
||||
NO_FLAGS: true
|
||||
- name: Cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{env.LLVM_DIR}}
|
||||
key: build-libcxx-${{env.LLVM_VER}}-${{matrix.sanitizer}}
|
||||
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: lsan.supp
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/config
|
||||
- name: Cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{env.LSAN_DIR}}
|
||||
key: build-lsan-suppression-file
|
||||
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: 4.0.3
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/config
|
||||
- name: Cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{env.METIS_DIR}}
|
||||
key: ${{runner.os}}-build-${{env.METIS_DIR}}-v2.5
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/mfem
|
||||
with:
|
||||
par: ${{inputs.par}}
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
env:
|
||||
ex: ${{inputs.par && 'ex1p' || 'ex1'}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/restore
|
||||
id: restore
|
||||
with:
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
env:
|
||||
exclude: ${{inputs.par && '-E "_ser"' || ''}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/restore
|
||||
id: restore
|
||||
with:
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
env:
|
||||
exclude: ${{inputs.par && '-E "_ser"' || ''}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/restore
|
||||
id: restore
|
||||
with:
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
run: ${{inputs.par && '-R "_cpu_np"' || ''}}
|
||||
exclude: ${{inputs.par && '"unit_tests|debug"' || '"^unit_tests$|debug"'}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/restore
|
||||
id: restore
|
||||
with:
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
env:
|
||||
unit_tests: ${{inputs.par && 'punit_tests' || 'unit_tests'}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/restore
|
||||
id: restore
|
||||
with:
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
unit_tests: ${{inputs.par && 'punit_tests' || 'unit_tests'}}
|
||||
np: ${{inputs.par && '_np=2' || ''}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/sanitize/restore
|
||||
id: restore
|
||||
with:
|
||||
|
||||
@@ -8,6 +8,21 @@
|
||||
https://mfem.org
|
||||
|
||||
|
||||
Version 4.10 (development)
|
||||
==========================
|
||||
|
||||
Discretization improvements
|
||||
---------------------------
|
||||
- Replaced legacy simplex quadrature rules with symmetric positive-weight
|
||||
rules for triangles (orders 0-25) and tetrahedra (orders 0-20). These
|
||||
rules guarantee all-positive weights and interior quadrature points,
|
||||
improving numerical stability. Higher orders fall back to Grundmann-Moller.
|
||||
Triangle rules: Witherden & Vincent, Comput. Math. Appl. 69(10):1232-1241,
|
||||
2015.
|
||||
Tet rules (d=1-13): Witherden & Vincent (ibid).
|
||||
Tet rules (d=14-20): Chuluunbaatar et al., Comput. Math. Appl. 124:89-97,
|
||||
2022.
|
||||
|
||||
Version 4.9.1 (development)
|
||||
===========================
|
||||
|
||||
@@ -30,6 +45,11 @@ New and updated examples and miniapps
|
||||
- Electromagnetics/lorentz miniapp has been updated to leverage the ParticleSet
|
||||
capability.
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
- Removes the SidreDataCollection class from MFEM in favor of the
|
||||
MFEMSidreDataCollection class in the Axom library (https://github.com/llnl/axom).
|
||||
|
||||
|
||||
Version 4.9, released on Dec 11, 2025
|
||||
=====================================
|
||||
|
||||
+2
-9
@@ -75,12 +75,10 @@ set(XSDK_ENABLE_Fortran OFF)
|
||||
|
||||
# Check if we need to enable C or Fortran.
|
||||
if (MFEM_USE_CONDUIT OR
|
||||
MFEM_USE_SIDRE OR
|
||||
MFEM_USE_PETSC)
|
||||
# This seems to be needed by:
|
||||
# * find_package(BLAS REQUIRED) and
|
||||
# * find_package(HDF5 REQUIRED) needed, in turn, by:
|
||||
# - find_package(AXOM REQUIRED)
|
||||
# * find_package(HDF5 REQUIRED) and
|
||||
# * find_package(PETSc REQUIRED)
|
||||
set(XSDK_ENABLE_C ON)
|
||||
endif()
|
||||
@@ -478,11 +476,6 @@ if (MFEM_USE_FMS)
|
||||
find_package(FMS REQUIRED fms)
|
||||
endif()
|
||||
|
||||
# Axom/Sidre
|
||||
if (MFEM_USE_SIDRE)
|
||||
find_package(Axom REQUIRED Axom)
|
||||
endif()
|
||||
|
||||
# PUMI
|
||||
if (MFEM_USE_PUMI)
|
||||
# If PUMI_DIR was specified, only link to that directory,
|
||||
@@ -629,7 +622,7 @@ find_package(Threads REQUIRED)
|
||||
# integers, the METIS header (with 32-bit indices, as used by mfem) needs to
|
||||
# be before SuiteSparse.
|
||||
set(MFEM_TPLS OPENMP HYPRE LAPACK BLAS SuperLUDist STRUMPACK METIS SuiteSparse
|
||||
SUNDIALS PETSC SLEPC MUMPS AXOM FMS CONDUIT Ginkgo GNUTLS GSLIB HDF5
|
||||
SUNDIALS PETSC SLEPC MUMPS FMS CONDUIT Ginkgo GNUTLS GSLIB HDF5
|
||||
NETCDF MPFR PUMI HIOP POSIXCLOCKS MFEMBacktrace ZLIB OCCA CEED RAJA UMPIRE
|
||||
ADIOS2 MKL_CPARDISO MKL_PARDISO AMGX MAGMA CUSPARSE CUBLAS CALIPER CODIPACK
|
||||
BENCHMARK PARELAG TRIBOL MPI_CXX HIP HIPBLAS HIPSPARSE MOONOLITH BLITZ
|
||||
|
||||
@@ -452,13 +452,6 @@ MFEM_USE_MPFR = YES/NO
|
||||
quadrature rules. When enabled, this option uses the MPFR_* library options,
|
||||
see below.
|
||||
|
||||
MFEM_USE_SIDRE = YES/NO
|
||||
Sidre is a component of LLNL's axom project, https://github.com/LLNL/axom,
|
||||
that provides an HDF5-based file format for visualization or restart
|
||||
capability following the Conduit (https://github.com/LLNL/conduit) mesh
|
||||
blueprint specification. When enabled, this option requires installation of
|
||||
HDF5 (see also MFEM_USE_NETCDF), Conduit and LLNL's axom project.
|
||||
|
||||
MFEM_USE_SIMD = YES/NO
|
||||
Enables the high performance templated classes to use architecture dependent
|
||||
SIMD intrinsics instead of the generic implementation of class AutoSIMD in
|
||||
@@ -778,14 +771,6 @@ The specific libraries and their options are:
|
||||
Options: SLEPC_OPT, SLEPC_LIB.
|
||||
Versions: SLEPc >= 3.8.0.
|
||||
|
||||
- Sidre (optional), part of LLNL's axom project, used when MFEM_USE_SIDRE = YES.
|
||||
Starting with MFEM v4.1, Axom version 0.3.1 or later is required.
|
||||
URL: https://github.com/LLNL/axom
|
||||
https://github.com/LLNL/conduit (Conduit)
|
||||
https://support.hdfgroup.org/HDF5 (HDF5)
|
||||
Options: SIDRE_OPT, SIDRE_LIB.
|
||||
Versions: Axom >= 0.3.1.
|
||||
|
||||
- Conduit (optional), used when MFEM_USE_CONDUIT = YES. Conduit Mesh Blueprint
|
||||
support requires Conduit >= v0.3.1 and VisIt >= v2.13.1 to read the output.
|
||||
URL: https://github.com/LLNL/conduit (Conduit)
|
||||
@@ -1069,7 +1054,6 @@ MFEM_USE_OCCA
|
||||
MFEM_USE_CEED
|
||||
MFEM_USE_RAJA
|
||||
MFEM_USE_UMPIRE
|
||||
MFEM_USE_SIDRE
|
||||
MFEM_USE_MOONOLITH
|
||||
MFEM_USE_CALIPER
|
||||
MFEM_USE_FMS
|
||||
@@ -1133,7 +1117,6 @@ The CMake build system adds auto-detection for the following packages/libraries:
|
||||
- OCCA
|
||||
- RAJA
|
||||
- UMPIRE
|
||||
- AXOM - Used when MFEM_USE_SIDRE is enabled
|
||||
- MOONOLITH
|
||||
- CALIPER
|
||||
- FMS
|
||||
|
||||
@@ -248,10 +248,6 @@ IF (DEFINED TPL_ENABLE_MPFR)
|
||||
SET(MFEM_USE_MPFR ${TPL_ENABLE_MPFR} CACHE BOOL "Enable MPFR usage." FORCE)
|
||||
ENDIF()
|
||||
|
||||
IF (DEFINED TPL_ENABLE_SIDRE)
|
||||
SET(MFEM_USE_SIDRE ${TPL_ENABLE_SIDRE} CACHE BOOL "Enable Axom/Sidre usage" FORCE)
|
||||
ENDIF()
|
||||
|
||||
IF (DEFINED TPL_ENABLE_FMS)
|
||||
SET(MFEM_USE_FMS ${TPL_ENABLE_FMS} CACHE BOOL "Enable FMS usage" FORCE)
|
||||
ENDIF()
|
||||
|
||||
@@ -46,7 +46,6 @@ set(MFEM_USE_NETCDF @MFEM_USE_NETCDF@)
|
||||
set(MFEM_USE_PETSC @MFEM_USE_PETSC@)
|
||||
set(MFEM_USE_SLEPC @MFEM_USE_SLEPC@)
|
||||
set(MFEM_USE_MPFR @MFEM_USE_MPFR@)
|
||||
set(MFEM_USE_SIDRE @MFEM_USE_SIDRE@)
|
||||
set(MFEM_USE_FMS @MFEM_USE_FMS@)
|
||||
set(MFEM_USE_CONDUIT @MFEM_USE_CONDUIT@)
|
||||
set(MFEM_USE_PUMI @MFEM_USE_PUMI@)
|
||||
|
||||
@@ -120,9 +120,6 @@
|
||||
// Enable secure socket streams based on the GNUTLS library.
|
||||
#cmakedefine MFEM_USE_GNUTLS
|
||||
|
||||
// Enable Sidre support.
|
||||
#cmakedefine MFEM_USE_SIDRE
|
||||
|
||||
// Enable the use of SIMD in the high performance templated classes.
|
||||
#cmakedefine MFEM_USE_SIMD
|
||||
|
||||
|
||||
@@ -9,24 +9,16 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
if(MFEM_USE_MPI)
|
||||
# Defines the following variables:
|
||||
# - ADIAK_FOUND
|
||||
# - ADIAK_LIBRARIES
|
||||
# - ADIAK_INCLUDE_DIRS
|
||||
|
||||
list(APPEND AN_SOURCES linear_anisotropic_elasticity.hpp)
|
||||
list(APPEND AN_HEADERS linear_anisotropic_elasticity.cpp)
|
||||
|
||||
convert_filenames_to_full_paths(AN_SOURCES)
|
||||
convert_filenames_to_full_paths(AN_HEADERS)
|
||||
|
||||
set(EX_COMMON_FILES
|
||||
EXTRA_SOURCES ${AN_SOURCES}
|
||||
EXTRA_HEADERS ${AN_HEADERS})
|
||||
|
||||
add_mfem_miniapp(mtop_test_anisotropic_le
|
||||
MAIN test_anisotropic_le.cpp
|
||||
${EX_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
|
||||
endif (MFEM_USE_MPI)
|
||||
include(MfemCmakeUtilities)
|
||||
|
||||
mfem_find_package(Adiak ADIAK ADIAK_DIR
|
||||
"include" "adiak.h"
|
||||
"lib" "adiak"
|
||||
"Paths to headers required by Adiak."
|
||||
"Libraries required by Adiak.")
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
# - AXOM_FOUND
|
||||
# - AXOM_LIBRARIES
|
||||
# - AXOM_INCLUDE_DIRS
|
||||
#
|
||||
# MFEM itself does not depend on Axom, however Tribol does. This module exists
|
||||
# to support MFEM's Tribol integration (e.g. the contact miniapp).
|
||||
|
||||
include(MfemCmakeUtilities)
|
||||
# Note: components are enabled based on the find_package() parameters.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# 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.
|
||||
|
||||
# Defines the following variables:
|
||||
# - CAMP_FOUND
|
||||
# - CAMP_LIBRARIES
|
||||
# - CAMP_INCLUDE_DIRS
|
||||
|
||||
include(MfemCmakeUtilities)
|
||||
|
||||
mfem_find_package(CAMP CAMP CAMP_DIR
|
||||
"include" "camp/camp.hpp"
|
||||
"lib" "camp"
|
||||
"Paths to headers required by CAMP."
|
||||
"Libraries required by CAMP.")
|
||||
|
||||
# RAJA commonly lists "camp" in INTERFACE_LINK_LIBRARIES. If there is no CMake
|
||||
# target named "camp", CMake treats it as a bare library name (-lcamp).
|
||||
if (CAMP_FOUND AND NOT TARGET camp)
|
||||
list(GET CAMP_LIBRARIES 0 _camp_lib0)
|
||||
add_library(camp UNKNOWN IMPORTED)
|
||||
set_target_properties(camp PROPERTIES
|
||||
IMPORTED_LOCATION "${_camp_lib0}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CAMP_INCLUDE_DIRS}")
|
||||
set(CAMP_LIBRARIES "camp" CACHE STRING "CAMP imported target." FORCE)
|
||||
unset(_camp_lib0)
|
||||
endif()
|
||||
|
||||
@@ -21,6 +21,21 @@ mfem_find_package(Caliper CALIPER CALIPER_DIR
|
||||
"Paths to headers required by Caliper."
|
||||
"Libraries required by Caliper.")
|
||||
|
||||
# Some downstream CMake packages (notably RAJA) may list "caliper" in their
|
||||
# INTERFACE_LINK_LIBRARIES. If there is no CMake target named "caliper", CMake
|
||||
# treats it as a bare library name and will pass -lcaliper to the linker.
|
||||
# Create a minimal imported target when we only located the library by path.
|
||||
if (CALIPER_FOUND AND NOT TARGET caliper)
|
||||
list(GET CALIPER_LIBRARIES 0 _caliper_lib0)
|
||||
add_library(caliper UNKNOWN IMPORTED)
|
||||
set_target_properties(caliper PROPERTIES
|
||||
IMPORTED_LOCATION "${_caliper_lib0}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CALIPER_INCLUDE_DIRS}")
|
||||
# Prefer linking via the target.
|
||||
set(CALIPER_LIBRARIES "caliper" CACHE STRING "Caliper imported target." FORCE)
|
||||
unset(_caliper_lib0)
|
||||
endif()
|
||||
|
||||
# Append adiak path/lib if the user provided ADIAK_DIR
|
||||
if(ADIAK_DIR AND EXISTS ${ADIAK_DIR})
|
||||
find_package(adiak NO_DEFAULT_PATH REQUIRED PATHS ${ADIAK_DIR}/lib/cmake/adiak ${ADIAK_DIR})
|
||||
|
||||
@@ -878,7 +878,7 @@ function(mfem_export_mk_files)
|
||||
MFEM_USE_SUITESPARSE MFEM_USE_SUPERLU MFEM_USE_SUPERLU5 MFEM_USE_MUMPS
|
||||
MFEM_USE_STRUMPACK MFEM_USE_GINKGO MFEM_USE_AMGX MFEM_USE_MAGMA
|
||||
MFEM_USE_GNUTLS MFEM_USE_NETCDF MFEM_USE_PETSC MFEM_USE_SLEPC
|
||||
MFEM_USE_MPFR MFEM_USE_SIDRE MFEM_USE_FMS MFEM_USE_CONDUIT MFEM_USE_PUMI
|
||||
MFEM_USE_MPFR MFEM_USE_FMS MFEM_USE_CONDUIT MFEM_USE_PUMI
|
||||
MFEM_USE_HIOP MFEM_USE_GSLIB MFEM_USE_CUDA MFEM_USE_HIP MFEM_USE_RAJA
|
||||
MFEM_USE_OCCA MFEM_USE_CEED MFEM_USE_CALIPER MFEM_USE_UMPIRE MFEM_USE_SIMD
|
||||
MFEM_USE_ADIOS2 MFEM_USE_MKL_CPARDISO MFEM_USE_MKL_PARDISO
|
||||
|
||||
@@ -120,9 +120,6 @@
|
||||
// Enable secure socket streams based on the GNUTLS library.
|
||||
// #define MFEM_USE_GNUTLS
|
||||
|
||||
// Enable Sidre support.
|
||||
// #define MFEM_USE_SIDRE
|
||||
|
||||
// Enable the use of SIMD in the high performance templated classes.
|
||||
// #define MFEM_USE_SIMD
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ MFEM_USE_NETCDF = @MFEM_USE_NETCDF@
|
||||
MFEM_USE_PETSC = @MFEM_USE_PETSC@
|
||||
MFEM_USE_SLEPC = @MFEM_USE_SLEPC@
|
||||
MFEM_USE_MPFR = @MFEM_USE_MPFR@
|
||||
MFEM_USE_SIDRE = @MFEM_USE_SIDRE@
|
||||
MFEM_USE_FMS = @MFEM_USE_FMS@
|
||||
MFEM_USE_CONDUIT = @MFEM_USE_CONDUIT@
|
||||
MFEM_USE_PUMI = @MFEM_USE_PUMI@
|
||||
|
||||
+9
-14
@@ -48,7 +48,6 @@ option(MFEM_USE_NETCDF "Enable NETCDF usage" OFF)
|
||||
option(MFEM_USE_PETSC "Enable PETSc support." OFF)
|
||||
option(MFEM_USE_SLEPC "Enable SLEPc support." OFF)
|
||||
option(MFEM_USE_MPFR "Enable MPFR usage." OFF)
|
||||
option(MFEM_USE_SIDRE "Enable Axom/Sidre usage" OFF)
|
||||
option(MFEM_USE_FMS "Enable FMS usage" OFF)
|
||||
option(MFEM_USE_CONDUIT "Enable Conduit usage" OFF)
|
||||
option(MFEM_USE_PUMI "Enable PUMI" OFF)
|
||||
@@ -224,17 +223,8 @@ set(FMS_DIR "${MFEM_DIR}/../fms" CACHE PATH
|
||||
set(CONDUIT_DIR "${MFEM_DIR}/../conduit" CACHE PATH
|
||||
"Path to the Conduit library.")
|
||||
|
||||
set(AXOM_DIR "${MFEM_DIR}/../axom" CACHE PATH "Path to the Axom library.")
|
||||
# May need to add "Boost" as requirement.
|
||||
if (MFEM_USE_SIDRE)
|
||||
if (MFEM_USE_MPI)
|
||||
set(Axom_REQUIRED_PACKAGES "Conduit/blueprint/blueprint_mpi/relay/relay_mpi" CACHE STRING
|
||||
"Additional packages required by Axom.")
|
||||
elseif()
|
||||
set(Axom_REQUIRED_PACKAGES "Conduit/blueprint/relay" CACHE STRING
|
||||
"Additional packages required by Axom.")
|
||||
endif()
|
||||
endif()
|
||||
set(AXOM_DIR "${MFEM_DIR}/../axom" CACHE PATH
|
||||
"Path to the Axom library (required by Tribol for the contact mini-app).")
|
||||
|
||||
set(PUMI_DIR "${MFEM_DIR}/../pumi-2.1.0" CACHE STRING
|
||||
"Directory where PUMI is installed")
|
||||
@@ -252,6 +242,7 @@ set(MKL_PARDISO_DIR "" CACHE STRING "MKL installation path.")
|
||||
|
||||
set(OCCA_DIR "${MFEM_DIR}/../occa" CACHE PATH "Path to OCCA")
|
||||
set(RAJA_DIR "${MFEM_DIR}/../raja" CACHE PATH "Path to RAJA")
|
||||
set(CAMP_DIR "${MFEM_DIR}/../camp" CACHE PATH "Path to CAMP (required by RAJA/Umpire)")
|
||||
set(CEED_DIR "${MFEM_DIR}/../libCEED" CACHE PATH "Path to libCEED")
|
||||
set(UMPIRE_DIR "${MFEM_DIR}/../umpire" CACHE PATH "Path to Umpire")
|
||||
set(CALIPER_DIR "${MFEM_DIR}/../caliper" CACHE PATH "Path to Caliper")
|
||||
@@ -272,8 +263,12 @@ set(PARELAG_LIBRARIES "${PARELAG_DIR}/build/src/libParELAG.a" CACHE STRING
|
||||
"The ParELAG library.")
|
||||
|
||||
set(TRIBOL_DIR "${MFEM_DIR}/../tribol" CACHE PATH "Path to Tribol")
|
||||
set(Tribol_REQUIRED_PACKAGES "Axom/core/mint/slam/slic" CACHE STRING
|
||||
"Additional packages required by Tribol")
|
||||
# Tribol requires Axom. Many Tribol builds also enable optional TPLs like
|
||||
# RAJA/UMPIRE/Caliper, and may pull additional Axom components (e.g. quest,
|
||||
# lumberjack) via its exported targets.
|
||||
set(Tribol_REQUIRED_PACKAGES
|
||||
"REQUIRED:;Axom/core/primal/mint/slam/slic/quest/lumberjack;OPTIONAL:;Adiak;CAMP;RAJA;UMPIRE;Caliper"
|
||||
CACHE STRING "Additional packages required by Tribol")
|
||||
|
||||
set(ENZYME_DIR "${MFEM_DIR}/../enzyme" CACHE PATH "Path to Enzyme")
|
||||
|
||||
|
||||
+78
-15
@@ -162,7 +162,6 @@ MFEM_USE_NETCDF = NO
|
||||
MFEM_USE_PETSC = NO
|
||||
MFEM_USE_SLEPC = NO
|
||||
MFEM_USE_MPFR = NO
|
||||
MFEM_USE_SIDRE = NO
|
||||
MFEM_USE_FMS = NO
|
||||
MFEM_USE_CONDUIT = NO
|
||||
MFEM_USE_PUMI = NO
|
||||
@@ -249,6 +248,15 @@ endif
|
||||
|
||||
# METIS library configuration
|
||||
ifeq ($(MFEM_USE_SUPERLU)$(MFEM_USE_STRUMPACK)$(MFEM_USE_MUMPS),NONONO)
|
||||
# MFEM_USE_METIS_5: when the user supplies METIS_DIR, try to auto-detect
|
||||
# METIS 5 installs that follow the common <prefix>/{include,lib,lib64} layout.
|
||||
ifeq ($(MFEM_USE_METIS_5),NO)
|
||||
ifneq ($(wildcard $(METIS_DIR)/include/metis.h),)
|
||||
ifneq ($(wildcard $(METIS_DIR)/lib/libmetis.* $(METIS_DIR)/lib64/libmetis.*),)
|
||||
MFEM_USE_METIS_5 = YES
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
ifeq ($(MFEM_USE_METIS_5),NO)
|
||||
METIS_DIR = @MFEM_DIR@/../metis-4.0
|
||||
METIS_OPT =
|
||||
@@ -487,17 +495,6 @@ ifneq (,$(wildcard $(CONDUIT_HDF5_HEADER)))
|
||||
-lhdf5 $(ZLIB_LIB)
|
||||
endif
|
||||
|
||||
# Sidre and required libraries configuration
|
||||
# Be sure to check the HDF5_DIR (set above) is correct
|
||||
SIDRE_DIR = @MFEM_DIR@/../axom
|
||||
SIDRE_OPT = -I$(SIDRE_DIR)/include -I$(CONDUIT_DIR)/include/conduit\
|
||||
-I$(HDF5_DIR)/include
|
||||
SIDRE_LIB = \
|
||||
$(XLINKER)-rpath,$(SIDRE_DIR)/lib -L$(SIDRE_DIR)/lib \
|
||||
$(XLINKER)-rpath,$(CONDUIT_DIR)/lib -L$(CONDUIT_DIR)/lib \
|
||||
$(XLINKER)-rpath,$(HDF5_DIR)/lib -L$(HDF5_DIR)/lib \
|
||||
-laxom -lconduit -lconduit_relay -lconduit_blueprint -lhdf5 $(ZLIB_LIB) -ldl
|
||||
|
||||
# PUMI
|
||||
# Note that PUMI_DIR is needed -- it is used to check for gmi_sim.h
|
||||
PUMI_DIR = @MFEM_DIR@/../pumi-2.1.0
|
||||
@@ -579,7 +576,13 @@ ifdef CUB_DIR
|
||||
RAJA_OPT += -I$(CUB_DIR)
|
||||
endif
|
||||
|
||||
# CAMP library configuration (required by RAJA/Umpire for most installs)
|
||||
CAMP_LIB = -lcamp
|
||||
# If the common sibling layout exists, use it as a default (handles versioned
|
||||
# directories like camp-<hash>).
|
||||
ifneq ($(wildcard $(RAJA_DIR)/../camp*/include/camp/camp.hpp),)
|
||||
CAMP_DIR ?= $(patsubst %/include/camp/camp.hpp,%,$(firstword $(wildcard $(RAJA_DIR)/../camp*/include/camp/camp.hpp)))
|
||||
endif
|
||||
ifdef CAMP_DIR
|
||||
RAJA_OPT += -I$(CAMP_DIR)/include
|
||||
CAMP_LIB = $(XLINKER)-rpath,$(CAMP_DIR)/lib -L$(CAMP_DIR)/lib -lcamp
|
||||
@@ -589,7 +592,12 @@ RAJA_LIB = $(XLINKER)-rpath,$(RAJA_DIR)/lib -L$(RAJA_DIR)/lib -lRAJA $(CAMP_LIB)
|
||||
# UMPIRE library configuration
|
||||
UMPIRE_DIR = @MFEM_DIR@/../umpire
|
||||
UMPIRE_OPT = -I$(UMPIRE_DIR)/include $(if $(CAMP_DIR), -I$(CAMP_DIR)/include)
|
||||
UMPIRE_LIB = -L$(UMPIRE_DIR)/lib -L$(UMPIRE_DIR)/lib64 -lumpire $(CAMP_LIB)
|
||||
UMPIRE_LIB = -L$(UMPIRE_DIR)/lib -L$(UMPIRE_DIR)/lib64 -lumpire $(CAMP_LIB) -lpthread
|
||||
# If the common sibling layout exists, use it as a default (handles versioned
|
||||
# directories like fmt-<hash>).
|
||||
ifneq ($(wildcard $(UMPIRE_DIR)/../fmt*/include/fmt/format.h),)
|
||||
FMT_DIR ?= $(patsubst %/include/fmt/format.h,%,$(firstword $(wildcard $(UMPIRE_DIR)/../fmt*/include/fmt/format.h)))
|
||||
endif
|
||||
ifdef FMT_DIR
|
||||
UMPIRE_OPT += -I$(FMT_DIR)/include
|
||||
UMPIRE_LIB += -L$(FMT_DIR)/lib -L$(FMT_DIR)/lib64 -lfmt
|
||||
@@ -621,8 +629,63 @@ PARELAG_LIB = -L$(PARELAG_DIR)/build/src -lParELAG
|
||||
AXOM_DIR = @MFEM_DIR@/../axom
|
||||
TRIBOL_DIR = @MFEM_DIR@/../tribol
|
||||
TRIBOL_OPT = -I$(TRIBOL_DIR)/include -I$(AXOM_DIR)/include
|
||||
TRIBOL_LIB = -L$(TRIBOL_DIR)/lib -ltribol -lredecomp -L$(AXOM_DIR)/lib -laxom_mint\
|
||||
-laxom_slam -laxom_slic -laxom_core
|
||||
# Tribol may be built with optional dependencies (e.g. RAJA/UMPIRE/CALIPER).
|
||||
# Add those options only when the corresponding headers/libraries exist.
|
||||
ifneq ($(wildcard $(RAJA_DIR)/include/RAJA/RAJA.hpp),)
|
||||
TRIBOL_OPT += $(RAJA_OPT)
|
||||
endif
|
||||
ifneq ($(wildcard $(UMPIRE_DIR)/include/umpire/Umpire.hpp),)
|
||||
TRIBOL_OPT += $(UMPIRE_OPT)
|
||||
endif
|
||||
ifneq ($(wildcard $(CALIPER_DIR)/include/caliper/cali.h),)
|
||||
TRIBOL_OPT += $(CALIPER_OPT)
|
||||
endif
|
||||
|
||||
TRIBOL_LIB = -L$(TRIBOL_DIR)/lib -L$(TRIBOL_DIR)/lib64
|
||||
ifneq ($(wildcard $(TRIBOL_DIR)/lib/libtribol.* $(TRIBOL_DIR)/lib64/libtribol.*),)
|
||||
TRIBOL_LIB += -ltribol
|
||||
endif
|
||||
ifneq ($(wildcard $(TRIBOL_DIR)/lib/libtribol_shared.* $(TRIBOL_DIR)/lib64/libtribol_shared.*),)
|
||||
TRIBOL_LIB += -ltribol_shared
|
||||
endif
|
||||
ifneq ($(wildcard $(TRIBOL_DIR)/lib/libredecomp.* $(TRIBOL_DIR)/lib64/libredecomp.*),)
|
||||
TRIBOL_LIB += -lredecomp
|
||||
endif
|
||||
|
||||
TRIBOL_LIB += -L$(AXOM_DIR)/lib -L$(AXOM_DIR)/lib64
|
||||
ifneq ($(wildcard $(AXOM_DIR)/lib/libaxom_quest.* $(AXOM_DIR)/lib64/libaxom_quest.*),)
|
||||
TRIBOL_LIB += -laxom_quest
|
||||
endif
|
||||
ifneq ($(wildcard $(AXOM_DIR)/lib/libaxom_mint.* $(AXOM_DIR)/lib64/libaxom_mint.*),)
|
||||
TRIBOL_LIB += -laxom_mint
|
||||
endif
|
||||
ifneq ($(wildcard $(AXOM_DIR)/lib/libaxom_slam.* $(AXOM_DIR)/lib64/libaxom_slam.*),)
|
||||
TRIBOL_LIB += -laxom_slam
|
||||
endif
|
||||
ifneq ($(wildcard $(AXOM_DIR)/lib/libaxom_slic.* $(AXOM_DIR)/lib64/libaxom_slic.*),)
|
||||
TRIBOL_LIB += -laxom_slic
|
||||
endif
|
||||
ifneq ($(wildcard $(AXOM_DIR)/lib/libaxom_lumberjack.* $(AXOM_DIR)/lib64/libaxom_lumberjack.*),)
|
||||
TRIBOL_LIB += -laxom_lumberjack
|
||||
endif
|
||||
ifneq ($(wildcard $(AXOM_DIR)/lib/libaxom_core.* $(AXOM_DIR)/lib64/libaxom_core.*),)
|
||||
TRIBOL_LIB += -laxom_core
|
||||
endif
|
||||
|
||||
# Add common optional Tribol TPLs when their libraries are present.
|
||||
ifneq ($(wildcard $(ADIAK_DIR)/lib/libadiak.* $(ADIAK_DIR)/lib64/libadiak.*),)
|
||||
TRIBOL_LIB += $(XLINKER)-rpath,$(ADIAK_DIR)/lib64 $(XLINKER)-rpath,$(ADIAK_DIR)/lib \
|
||||
-L$(ADIAK_DIR)/lib64 -L$(ADIAK_DIR)/lib -ladiak -ldl
|
||||
endif
|
||||
ifneq ($(wildcard $(UMPIRE_DIR)/lib/libumpire.* $(UMPIRE_DIR)/lib64/libumpire.*),)
|
||||
TRIBOL_LIB += $(UMPIRE_LIB)
|
||||
endif
|
||||
ifneq ($(wildcard $(RAJA_DIR)/lib/libRAJA.* $(RAJA_DIR)/lib64/libRAJA.*),)
|
||||
TRIBOL_LIB += $(RAJA_LIB)
|
||||
endif
|
||||
ifneq ($(wildcard $(CALIPER_DIR)/lib/libcaliper.* $(CALIPER_DIR)/lib64/libcaliper.*),)
|
||||
TRIBOL_LIB += $(CALIPER_LIB)
|
||||
endif
|
||||
|
||||
# Enzyme configuration
|
||||
ENZYME_DIR = @MFEM_DIR@/../enzyme
|
||||
|
||||
+2
-2
@@ -5,9 +5,9 @@
|
||||
// Sample runs:
|
||||
// mpirun -np 4 ex12p -m ../data/beam-tri.mesh
|
||||
// mpirun -np 4 ex12p -m ../data/beam-quad.mesh
|
||||
// mpirun -np 4 ex12p -m ../data/beam-tet.mesh -s 462 -n 10 -o 2 -elast
|
||||
// mpirun -np 4 ex12p -m ../data/beam-tet.mesh -s 464 -n 10 -o 2 -elast
|
||||
// mpirun -np 4 ex12p -m ../data/beam-hex.mesh -s 3878
|
||||
// mpirun -np 4 ex12p -m ../data/beam-wedge.mesh -s 81
|
||||
// mpirun -np 4 ex12p -m ../data/beam-wedge.mesh -s 82
|
||||
// mpirun -np 4 ex12p -m ../data/beam-tri.mesh -s 3877 -o 2 -sys
|
||||
// mpirun -np 4 ex12p -m ../data/beam-quad.mesh -s 4544 -n 6 -o 3 -elast
|
||||
// mpirun -np 4 ex12p -m ../data/beam-quad-nurbs.mesh
|
||||
|
||||
+27
-9
@@ -302,15 +302,21 @@ int main(int argc, char *argv[])
|
||||
char vishost[] = "localhost";
|
||||
int visport = 19916;
|
||||
socketstream sol_sock_r(vishost, visport);
|
||||
socketstream sol_sock_i(vishost, visport);
|
||||
sol_sock_r << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_i << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_r.precision(8);
|
||||
sol_sock_i.precision(8);
|
||||
sol_sock_r << "solution\n" << *pmesh << u_exact->real()
|
||||
<< "window_title 'Exact: Real Part'" << flush;
|
||||
// Make sure all ranks have sent their real solution before initiating
|
||||
// another set of GLVis connections (one from each rank):
|
||||
MPI_Barrier(pmesh->GetComm());
|
||||
socketstream sol_sock_i(vishost, visport);
|
||||
sol_sock_i << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_i.precision(8);
|
||||
sol_sock_i << "solution\n" << *pmesh << u_exact->imag()
|
||||
<< "window_title 'Exact: Imaginary Part'" << flush;
|
||||
// Make sure all ranks have sent their imaginary solution before initiating
|
||||
// another set of GLVis connections (one from each rank):
|
||||
MPI_Barrier(pmesh->GetComm());
|
||||
}
|
||||
|
||||
// 11. Set up the parallel sesquilinear form a(.,.) on the finite element
|
||||
@@ -534,15 +540,21 @@ int main(int argc, char *argv[])
|
||||
char vishost[] = "localhost";
|
||||
int visport = 19916;
|
||||
socketstream sol_sock_r(vishost, visport);
|
||||
socketstream sol_sock_i(vishost, visport);
|
||||
sol_sock_r << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_i << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_r.precision(8);
|
||||
sol_sock_i.precision(8);
|
||||
sol_sock_r << "solution\n" << *pmesh << u.real()
|
||||
<< "window_title 'Solution: Real Part'" << flush;
|
||||
// Make sure all ranks have sent their real solution before initiating
|
||||
// another set of GLVis connections (one from each rank):
|
||||
MPI_Barrier(pmesh->GetComm());
|
||||
socketstream sol_sock_i(vishost, visport);
|
||||
sol_sock_i << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_i.precision(8);
|
||||
sol_sock_i << "solution\n" << *pmesh << u.imag()
|
||||
<< "window_title 'Solution: Imaginary Part'" << flush;
|
||||
// Make sure all ranks have sent their imaginary solution before initiating
|
||||
// another set of GLVis connections (one from each rank):
|
||||
MPI_Barrier(pmesh->GetComm());
|
||||
}
|
||||
if (visualization && exact_sol)
|
||||
{
|
||||
@@ -551,15 +563,21 @@ int main(int argc, char *argv[])
|
||||
char vishost[] = "localhost";
|
||||
int visport = 19916;
|
||||
socketstream sol_sock_r(vishost, visport);
|
||||
socketstream sol_sock_i(vishost, visport);
|
||||
sol_sock_r << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_i << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_r.precision(8);
|
||||
sol_sock_i.precision(8);
|
||||
sol_sock_r << "solution\n" << *pmesh << u_exact->real()
|
||||
<< "window_title 'Error: Real Part'" << flush;
|
||||
// Make sure all ranks have sent their real solution before initiating
|
||||
// another set of GLVis connections (one from each rank):
|
||||
MPI_Barrier(pmesh->GetComm());
|
||||
socketstream sol_sock_i(vishost, visport);
|
||||
sol_sock_i << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock_i.precision(8);
|
||||
sol_sock_i << "solution\n" << *pmesh << u_exact->imag()
|
||||
<< "window_title 'Error: Imaginary Part'" << flush;
|
||||
// Make sure all ranks have sent their imaginary solution before initiating
|
||||
// another set of GLVis connections (one from each rank):
|
||||
MPI_Barrier(pmesh->GetComm());
|
||||
}
|
||||
if (visualization)
|
||||
{
|
||||
|
||||
+6
-4
@@ -433,16 +433,18 @@ int main(int argc, char *argv[])
|
||||
u.ProjectCoefficient(*u0);
|
||||
|
||||
// Create data collection for solution output: either VisItDataCollection for
|
||||
// ascii data files, or SidreDataCollection for binary data files.
|
||||
// ascii data files, or ConduitDataCollection for binary data files.
|
||||
DataCollection *dc = NULL;
|
||||
if (visit)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example41", &mesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example41", &mesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
+5
-3
@@ -518,10 +518,12 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example41-Parallel", pmesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example41-Parallel", pmesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
+6
-4
@@ -305,16 +305,18 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Create data collection for solution output: either VisItDataCollection for
|
||||
// ascii data files, or SidreDataCollection for binary data files.
|
||||
// ascii data files, or ConduitDataCollection for binary data files.
|
||||
DataCollection *dc = NULL;
|
||||
if (visit)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example9", &mesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example9", &mesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
+6
-4
@@ -441,16 +441,18 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Create data collection for solution output: either VisItDataCollection for
|
||||
// ascii data files, or SidreDataCollection for binary data files.
|
||||
// ascii data files, or ConduitDataCollection for binary data files.
|
||||
DataCollection *dc = NULL;
|
||||
if (visit)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example9-Parallel", pmesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example9-Parallel", pmesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
@@ -354,16 +354,18 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Create data collection for solution output: either VisItDataCollection for
|
||||
// ascii data files, or SidreDataCollection for binary data files.
|
||||
// ascii data files, or ConduitDataCollection for binary data files.
|
||||
DataCollection *dc = NULL;
|
||||
if (visit)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example9", mesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example9", mesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
@@ -414,16 +414,18 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Create data collection for solution output: either VisItDataCollection for
|
||||
// ascii data files, or SidreDataCollection for binary data files.
|
||||
// ascii data files, or ConduitDataCollection for binary data files.
|
||||
DataCollection *dc = NULL;
|
||||
if (visit)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example9-Parallel", pmesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example9-Parallel", pmesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
@@ -368,16 +368,18 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Create data collection for solution output: either VisItDataCollection for
|
||||
// ascii data files, or SidreDataCollection for binary data files.
|
||||
// ascii data files, or ConduitDataCollection for binary data files.
|
||||
DataCollection *dc = NULL;
|
||||
if (visit)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example9-Parallel", pmesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example9-Parallel", pmesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
@@ -316,16 +316,18 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Create data collection for solution output: either VisItDataCollection for
|
||||
// ascii data files, or SidreDataCollection for binary data files.
|
||||
// ascii data files, or ConduitDataCollection for binary data files.
|
||||
DataCollection *dc = NULL;
|
||||
if (visit)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example9", &mesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example9", &mesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
@@ -453,16 +453,18 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Create data collection for solution output: either VisItDataCollection for
|
||||
// ascii data files, or SidreDataCollection for binary data files.
|
||||
// ascii data files, or ConduitDataCollection for binary data files.
|
||||
DataCollection *dc = NULL;
|
||||
if (visit)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
dc = new SidreDataCollection("Example9-Parallel", pmesh);
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
auto conduit_dc = new ConduitDataCollection("Example9-Parallel", pmesh);
|
||||
conduit_dc->SetProtocol("hdf5");
|
||||
dc = conduit_dc;
|
||||
#else
|
||||
MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output.");
|
||||
MFEM_ABORT("Must build with MFEM_USE_CONDUIT=YES for binary output.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
@@ -312,11 +312,6 @@ set(HDRS
|
||||
particleset.hpp
|
||||
)
|
||||
|
||||
if (MFEM_USE_SIDRE)
|
||||
list(APPEND SRCS sidredatacollection.cpp)
|
||||
list(APPEND HDRS sidredatacollection.hpp)
|
||||
endif()
|
||||
|
||||
if (MFEM_USE_CONDUIT)
|
||||
list(APPEND SRCS conduitdatacollection.cpp)
|
||||
list(APPEND HDRS conduitdatacollection.hpp)
|
||||
|
||||
@@ -729,7 +729,8 @@ void BilinearForm::Assemble(int skip_zeros)
|
||||
tr = mesh -> GetBdrFaceTransformations (i);
|
||||
if (tr != NULL)
|
||||
{
|
||||
fes -> GetElementVDofs (tr -> Elem1No, vdofs);
|
||||
mfem::DofTransformation doftrans;
|
||||
fes -> GetElementVDofs (tr -> Elem1No, vdofs, doftrans);
|
||||
fe1 = fes -> GetFE (tr -> Elem1No);
|
||||
// The fe2 object is really a dummy and not used on the boundaries,
|
||||
// but we can't dereference a NULL pointer, and we don't want to
|
||||
@@ -743,6 +744,7 @@ void BilinearForm::Assemble(int skip_zeros)
|
||||
|
||||
boundary_face_integs[k] -> AssembleFaceMatrix (*fe1, *fe2, *tr,
|
||||
elemmat);
|
||||
doftrans.TransformDual(elemmat);
|
||||
mat -> AddSubMatrix (vdofs, vdofs, elemmat, skip_zeros);
|
||||
}
|
||||
}
|
||||
@@ -1723,6 +1725,7 @@ void MixedBilinearForm::Assemble(int skip_zeros)
|
||||
}
|
||||
}
|
||||
|
||||
DofTransformation dom_dof_trans, ran_dof_trans;
|
||||
for (int i = 0; i < trial_fes -> GetNBE(); i++)
|
||||
{
|
||||
const int bdr_attr = mesh->GetBdrAttribute(i);
|
||||
@@ -1731,8 +1734,8 @@ void MixedBilinearForm::Assemble(int skip_zeros)
|
||||
ftr = mesh -> GetBdrFaceTransformations (i);
|
||||
if (ftr != NULL)
|
||||
{
|
||||
trial_fes->GetElementVDofs(ftr->Elem1No, trial_vdofs);
|
||||
test_fes->GetElementVDofs(ftr->Elem1No, test_vdofs);
|
||||
trial_fes->GetElementVDofs(ftr->Elem1No, trial_vdofs, dom_dof_trans);
|
||||
test_fes->GetElementVDofs(ftr->Elem1No, test_vdofs, ran_dof_trans);
|
||||
trial_fe1 = trial_fes->GetFE(ftr->Elem1No);
|
||||
test_fe1 = test_fes->GetFE(ftr->Elem1No);
|
||||
// The test_fe2 object is really a dummy and not used on the
|
||||
@@ -1748,6 +1751,7 @@ void MixedBilinearForm::Assemble(int skip_zeros)
|
||||
boundary_face_integs[k]->AssembleFaceMatrix(*trial_fe1, *test_fe1, *trial_fe2,
|
||||
*test_fe2,
|
||||
*ftr, elemmat);
|
||||
TransformDual(ran_dof_trans, dom_dof_trans, elemmat);
|
||||
mat->AddSubMatrix(test_vdofs, trial_vdofs, elemmat, skip_zeros);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2710,7 +2710,7 @@ public:
|
||||
|
||||
|
||||
/** Integrator for $(-Q u, \nabla v)$ for Nedelec ($u$) and $H^1$ ($v$) elements.
|
||||
This is equivalent to a weak divergence of the $H(curl$ basis functions. */
|
||||
This is equivalent to a weak divergence of the $H(curl)$ basis functions. */
|
||||
class VectorFEWeakDivergenceIntegrator: public BilinearFormIntegrator
|
||||
{
|
||||
protected:
|
||||
|
||||
@@ -1453,8 +1453,6 @@ ConduitDataCollection::LoadMeshAndFields(int domain_id,
|
||||
std::string
|
||||
ConduitDataCollection::ElementTypeToShapeName(Element::Type element_type)
|
||||
{
|
||||
// Adapted from SidreDataCollection
|
||||
|
||||
// Note -- the mapping from Element::Type to string is based on
|
||||
// enum Element::Type { POINT, SEGMENT, TRIANGLE, QUADRILATERAL,
|
||||
// TETRAHEDRON, HEXAHEDRON };
|
||||
|
||||
@@ -34,10 +34,10 @@ namespace mfem
|
||||
- HDF5 library, https://support.hdfgroup.org/HDF5
|
||||
|
||||
@note The ConduitDataCollection only wraps the mfem objects to save them and
|
||||
creates them on load, Conduit does not own any of the data. The
|
||||
SidreDataCollection provides more features, for example the
|
||||
SidreDataCollection allocates and will own the data backing the mfem objects
|
||||
in the data collection.
|
||||
creates them on load, Conduit does not own any of the data.
|
||||
The MFEMSidreDataCollection in the Axom package (https://github.com/LLNL/axom)
|
||||
derives from mfem::DataCollection and provides more features, for example
|
||||
it allocates and will own the data backing the mfem objects in the data collection.
|
||||
|
||||
This class also provides public static methods that convert between MFEM
|
||||
Meshes and GridFunctions and Conduit Mesh Blueprint descriptions.
|
||||
|
||||
+41
-215
@@ -84,33 +84,29 @@ public:
|
||||
const std::vector<derivative_action_t> &derivative_actions,
|
||||
const FieldDescriptor &direction,
|
||||
const int &daction_l_size,
|
||||
const int &derivative_action_tr_l_size,
|
||||
const std::vector<derivative_action_t> &derivative_tr_actions,
|
||||
const std::vector<derivative_action_t> &derivative_actions_transpose,
|
||||
const FieldDescriptor &transpose_direction,
|
||||
const int &daction_transpose_l_size,
|
||||
const std::vector<Vector *> &solutions_l,
|
||||
const std::vector<Vector *> ¶meters_l,
|
||||
const restriction_callback_t &restriction_callback,
|
||||
const std::function<void(Vector &, Vector &)> &prolongation_transpose,
|
||||
const std::function<void(Vector &, Vector &)> &tr_prolongation_transpose,
|
||||
const std::vector<assemble_derivative_sparsematrix_callback_t>
|
||||
&assemble_derivative_sparsematrix_callbacks,
|
||||
const assemble_derivative_hypreparmatrix_callback_t
|
||||
&assemble_derivative_hypreparmatrix_callback) :
|
||||
const std::vector<assemble_derivative_hypreparmatrix_callback_t>
|
||||
&assemble_derivative_hypreparmatrix_callbacks) :
|
||||
Operator(height, width),
|
||||
derivative_actions(derivative_actions),
|
||||
direction(direction),
|
||||
daction_l(daction_l_size),
|
||||
daction_l_size(daction_l_size),
|
||||
derivative_action_tr_l_size(derivative_action_tr_l_size),
|
||||
derivative_tr_actions(derivative_tr_actions),
|
||||
derivative_actions_transpose(derivative_actions_transpose),
|
||||
transpose_direction(transpose_direction),
|
||||
prolongation_transpose(prolongation_transpose),
|
||||
tr_prolongation_transpose(tr_prolongation_transpose),
|
||||
assemble_derivative_sparsematrix_callbacks(
|
||||
assemble_derivative_sparsematrix_callbacks),
|
||||
assemble_derivative_hypreparmatrix_callback(
|
||||
assemble_derivative_hypreparmatrix_callback)
|
||||
assemble_derivative_hypreparmatrix_callbacks(
|
||||
assemble_derivative_hypreparmatrix_callbacks)
|
||||
{
|
||||
std::vector<Vector> s_l(solutions_l.size());
|
||||
for (size_t i = 0; i < s_l.size(); i++)
|
||||
@@ -160,18 +156,18 @@ public:
|
||||
/// direction_t on T-dofs.
|
||||
void MultTranspose(const Vector &direction_t, Vector &result_t) const override
|
||||
{
|
||||
MFEM_ASSERT(!derivative_tr_actions.empty(),
|
||||
MFEM_ASSERT(!derivative_actions_transpose.empty(),
|
||||
"derivative can't be used to be multiplied in transpose mode");
|
||||
|
||||
daction_l.SetSize(derivative_action_tr_l_size);
|
||||
daction_l.SetSize(width);
|
||||
daction_l = 0.0;
|
||||
|
||||
prolongation(transpose_direction, direction_t, direction_l);
|
||||
for (const auto &f : derivative_tr_actions)
|
||||
for (const auto &f : derivative_actions_transpose)
|
||||
{
|
||||
f(fields_e, direction_l, daction_l);
|
||||
}
|
||||
tr_prolongation_transpose(daction_l, result_t);
|
||||
prolongation_transpose(daction_l, result_t);
|
||||
};
|
||||
|
||||
/// @brief Assemble the derivative operator into a SparseMatrix.
|
||||
@@ -187,10 +183,6 @@ public:
|
||||
{
|
||||
f(fields_e, A);
|
||||
}
|
||||
|
||||
// SparseMatrix A is finalized after all callbacks have contributed to
|
||||
// it.
|
||||
A->Finalize();
|
||||
}
|
||||
|
||||
/// @brief Assemble the derivative operator into a HypreParMatrix.
|
||||
@@ -199,7 +191,13 @@ public:
|
||||
/// be an uninitialized object.
|
||||
void Assemble(HypreParMatrix *&A)
|
||||
{
|
||||
assemble_derivative_hypreparmatrix_callback(fields_e, A);
|
||||
MFEM_ASSERT(!assemble_derivative_hypreparmatrix_callbacks.empty(),
|
||||
"derivative can't be assembled into a HypreParMatrix");
|
||||
|
||||
for (const auto &f : assemble_derivative_hypreparmatrix_callbacks)
|
||||
{
|
||||
f(fields_e, A);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -214,12 +212,10 @@ private:
|
||||
|
||||
const int daction_l_size;
|
||||
|
||||
const int derivative_action_tr_l_size;
|
||||
|
||||
/// Transpose Derivative action callbacks. Depending on the requested
|
||||
/// derivatives in DifferentiableOperator the callbacks represent certain
|
||||
/// combinations of actions of derivatives of the forward operator.
|
||||
std::vector<derivative_action_t> derivative_tr_actions;
|
||||
std::vector<derivative_action_t> derivative_actions_transpose;
|
||||
|
||||
FieldDescriptor transpose_direction;
|
||||
|
||||
@@ -229,15 +225,13 @@ private:
|
||||
|
||||
std::function<void(Vector &, Vector &)> prolongation_transpose;
|
||||
|
||||
std::function<void(Vector &, Vector &)> tr_prolongation_transpose;
|
||||
|
||||
/// Callbacks that assemble derivatives into a SparseMatrix.
|
||||
std::vector<assemble_derivative_sparsematrix_callback_t>
|
||||
assemble_derivative_sparsematrix_callbacks;
|
||||
|
||||
/// Callbacks that assemble derivatives into a HypreParMatrix.
|
||||
assemble_derivative_hypreparmatrix_callback_t
|
||||
assemble_derivative_hypreparmatrix_callback;
|
||||
std::vector<assemble_derivative_hypreparmatrix_callback_t>
|
||||
assemble_derivative_hypreparmatrix_callbacks;
|
||||
};
|
||||
|
||||
/// Class representing a differentiable operator which acts on solution and
|
||||
@@ -463,10 +457,7 @@ public:
|
||||
dir_l = s_l[derivative_idx];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < derivative_setup_callbacks[derivative_id].size(); i++)
|
||||
{
|
||||
derivative_setup_callbacks[derivative_id][i](fields_e, dir_l);
|
||||
}
|
||||
derivative_setup_callbacks[derivative_id][0](fields_e, dir_l);
|
||||
|
||||
return std::make_shared<DerivativeOperator>(
|
||||
height,
|
||||
@@ -474,17 +465,15 @@ public:
|
||||
derivative_action_callbacks[derivative_id],
|
||||
fields[derivative_idx],
|
||||
residual_l.Size(),
|
||||
derivative_action_tr_l_size[derivative_id],
|
||||
derivative_action_tr_callbacks[derivative_id],
|
||||
daction_transpose_callbacks[derivative_id],
|
||||
fields[test_space_field_idx],
|
||||
GetVSize(fields[test_space_field_idx]),
|
||||
sol_l,
|
||||
par_l,
|
||||
restriction_callback,
|
||||
prolongation_transpose,
|
||||
derivative_tr_prolongation_transpose[derivative_id],
|
||||
assemble_derivative_sparsematrix_callbacks[derivative_id],
|
||||
assemble_derivative_hypreparmatrix_callback[derivative_id]);
|
||||
assemble_derivative_hypreparmatrix_callbacks[derivative_id]);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -497,15 +486,13 @@ private:
|
||||
std::map<size_t,
|
||||
std::vector<derivative_action_t>> derivative_action_callbacks;
|
||||
std::map<size_t,
|
||||
std::vector<derivative_action_t>> derivative_action_tr_callbacks;
|
||||
std::map<size_t,
|
||||
std::function<void(Vector &, Vector &)>> derivative_tr_prolongation_transpose;
|
||||
std::map<size_t, int> derivative_action_tr_l_size;
|
||||
std::vector<derivative_action_t>> daction_transpose_callbacks;
|
||||
std::map<size_t,
|
||||
std::vector<assemble_derivative_sparsematrix_callback_t>>
|
||||
assemble_derivative_sparsematrix_callbacks;
|
||||
std::map<size_t, assemble_derivative_hypreparmatrix_callback_t>
|
||||
assemble_derivative_hypreparmatrix_callback;
|
||||
std::map<size_t,
|
||||
std::vector<assemble_derivative_hypreparmatrix_callback_t>>
|
||||
assemble_derivative_hypreparmatrix_callbacks;
|
||||
|
||||
std::vector<FieldDescriptor> solutions;
|
||||
std::vector<FieldDescriptor> parameters;
|
||||
@@ -523,7 +510,7 @@ private:
|
||||
std::function<void(Vector &, Vector &)> output_restriction_transpose;
|
||||
restriction_callback_t restriction_callback;
|
||||
|
||||
std::map<size_t, std::vector<Vector>> derivative_qp_caches;
|
||||
std::map<size_t, Vector> derivative_qp_caches;
|
||||
|
||||
std::map<size_t, size_t> assembled_vector_sizes;
|
||||
|
||||
@@ -782,10 +769,9 @@ void DifferentiableOperator::AddIntegrator(
|
||||
auto input_size_on_qp =
|
||||
get_input_size_on_qp(inputs, std::make_index_sequence<num_inputs> {});
|
||||
|
||||
// printf("calculate shmem action info\n");
|
||||
auto action_shmem_info =
|
||||
get_shmem_info<entity_t, num_fields, num_inputs, num_outputs>
|
||||
(input_dtq_maps, output_dtq_maps, fields, num_entities, num_qp,
|
||||
(input_dtq_maps, output_dtq_maps, fields, num_entities, inputs, num_qp,
|
||||
input_size_on_qp, residual_size_on_qp, element_dof_ordering);
|
||||
|
||||
Vector shmem_cache(action_shmem_info.total_size);
|
||||
@@ -908,10 +894,9 @@ void DifferentiableOperator::AddIntegrator(
|
||||
const int da_size_on_qp =
|
||||
GetSizeOnQP<entity_t>(output_fop, fields[test_space_field_idx]);
|
||||
|
||||
// printf("calculate shmem derivative action info\n");
|
||||
auto shmem_info =
|
||||
get_shmem_info<entity_t, num_fields, num_inputs, num_outputs>(
|
||||
input_dtq_maps, output_dtq_maps, fields, num_entities,
|
||||
input_dtq_maps, output_dtq_maps, fields, num_entities, inputs,
|
||||
num_qp, input_size_on_qp, residual_size_on_qp,
|
||||
element_dof_ordering, d_field_idx);
|
||||
|
||||
@@ -983,12 +968,9 @@ void DifferentiableOperator::AddIntegrator(
|
||||
// Quadrature point local derivative cache for each element, with data
|
||||
// layout:
|
||||
// [test_vdim, test_op_dim, trial_vdim, trial_op_dim, qp, num_entities].
|
||||
derivative_qp_caches[derivative_id].push_back(
|
||||
Vector(test_vdim * test_op_dim * trial_vdim * total_trial_op_dim * num_qp *
|
||||
num_entities));
|
||||
|
||||
const int cache_index = this->derivative_qp_caches[derivative_id].size() - 1;
|
||||
|
||||
derivative_qp_caches[derivative_id] = Vector(test_vdim * test_op_dim *
|
||||
trial_vdim *
|
||||
total_trial_op_dim * num_qp * num_entities);
|
||||
// Create local references for MSVC lambda capture compatibility
|
||||
auto& fields_ref = this->fields;
|
||||
auto& derivative_qp_caches_ref = this->derivative_qp_caches[derivative_id];
|
||||
@@ -1030,8 +1012,6 @@ void DifferentiableOperator::AddIntegrator(
|
||||
trial_vdim,
|
||||
inputs_trial_op_dim,
|
||||
|
||||
qpdc_idx = cache_index,
|
||||
|
||||
// capture by ref:
|
||||
&qpdc_mem = derivative_qp_caches_ref
|
||||
](std::vector<Vector> &f_e, const Vector &dir_l) mutable
|
||||
@@ -1044,7 +1024,7 @@ void DifferentiableOperator::AddIntegrator(
|
||||
shmem_info.direction_size,
|
||||
num_entities);
|
||||
|
||||
auto qpdc = Reshape(qpdc_mem[qpdc_idx].ReadWrite(), test_vdim, test_op_dim,
|
||||
auto qpdc = Reshape(qpdc_mem.ReadWrite(), test_vdim, test_op_dim,
|
||||
trial_vdim, total_trial_op_dim, num_qp, num_entities);
|
||||
|
||||
auto itod = Reshape(inputs_trial_op_dim.Read(), num_inputs);
|
||||
@@ -1115,7 +1095,6 @@ void DifferentiableOperator::AddIntegrator(
|
||||
inputs_trial_op_dim,
|
||||
total_trial_op_dim,
|
||||
trial_vdim,
|
||||
qpdc_idx = cache_index,
|
||||
// capture by ref:
|
||||
&qpdc_mem = derivative_qp_caches_ref,
|
||||
&or_transpose
|
||||
@@ -1133,7 +1112,7 @@ void DifferentiableOperator::AddIntegrator(
|
||||
shmem_info.direction_size,
|
||||
num_entities);
|
||||
|
||||
auto qpdc = Reshape(qpdc_mem[qpdc_idx].Read(), test_vdim, test_op_dim,
|
||||
auto qpdc = Reshape(qpdc_mem.Read(), test_vdim, test_op_dim,
|
||||
trial_vdim, total_trial_op_dim, num_qp, num_entities);
|
||||
|
||||
auto itod = Reshape(inputs_trial_op_dim.Read(), num_inputs);
|
||||
@@ -1178,154 +1157,6 @@ void DifferentiableOperator::AddIntegrator(
|
||||
or_transpose(derivative_action_e, der_action_l);
|
||||
});
|
||||
|
||||
// This prevents Sum/Identity. These are invalid
|
||||
// as input FieldOperators anyways.
|
||||
constexpr auto dummy_fop = Value<0> {};
|
||||
|
||||
auto [input_rt,
|
||||
input_e_sz] = get_restriction_transpose<entity_t>
|
||||
(fields[d_field_idx],
|
||||
element_dof_ordering, dummy_fop);
|
||||
|
||||
const auto input_restriction_transpose = input_rt;
|
||||
|
||||
derivative_tr_prolongation_transpose[derivative_id] =
|
||||
get_prolongation_transpose(
|
||||
fields[d_field_idx], dummy_fop, mesh.GetComm());
|
||||
|
||||
const auto d_tr_field_idx = test_space_field_idx;
|
||||
const auto direction_tr = fields[d_tr_field_idx];
|
||||
|
||||
auto output_size_on_qp =
|
||||
get_input_size_on_qp(outputs, std::make_index_sequence<num_outputs> {});
|
||||
|
||||
const int residual_tr_size_on_qp = trial_vdim * total_trial_op_dim;
|
||||
|
||||
auto shmem_tr_info =
|
||||
get_shmem_info<entity_t, num_fields, num_outputs, num_inputs>(
|
||||
output_dtq_maps, input_dtq_maps, fields, num_entities,
|
||||
num_qp, output_size_on_qp, residual_tr_size_on_qp,
|
||||
element_dof_ordering, test_space_field_idx);
|
||||
|
||||
// print_shared_memory_info(shmem_tr_info);
|
||||
|
||||
// TODO: this is a hack to extend the shared memory with a known
|
||||
// offset for a temp variable
|
||||
Vector shmem_tr_cache(shmem_tr_info.total_size + residual_tr_size_on_qp *
|
||||
num_qp);
|
||||
|
||||
Vector direction_tr_e(get_restriction<entity_t>(
|
||||
fields[test_space_field_idx],
|
||||
element_dof_ordering)->Height());
|
||||
|
||||
derivative_action_tr_l_size[derivative_id] =
|
||||
get_restriction<entity_t>(fields[d_field_idx],
|
||||
element_dof_ordering)->Width();
|
||||
|
||||
Vector derivative_action_tr_e(input_e_sz);
|
||||
derivative_action_tr_e = 0.0;
|
||||
|
||||
derivative_action_tr_callbacks[derivative_id].push_back(
|
||||
[
|
||||
// capture by copy:
|
||||
dimension, // int
|
||||
num_entities, // int
|
||||
num_trial_dof, // int
|
||||
num_qp, // int
|
||||
q1d, // int
|
||||
test_vdim, // int (= output_fop.vdim)
|
||||
test_op_dim, // int (derived from output_fop)
|
||||
inputs, // mfem::future::tuple
|
||||
outputs, // mfem::future::tuple
|
||||
attributes, // Array<int>
|
||||
ir_weights, // DeviceTensor
|
||||
use_sum_factorization, // bool
|
||||
input_dtq_maps, // std::array<DofToQuadMap, num_fields>
|
||||
output_dtq_maps, // std::array<DofToQuadMap, num_fields>
|
||||
// output_fop, // class derived from FieldOperator
|
||||
thread_blocks, // ThreadBlocks
|
||||
shmem_tr_cache, // Vector (local)
|
||||
shmem_tr_info, // SharedMemoryInfo
|
||||
// TODO: make this Array<int> a member of the DifferentiableOperator
|
||||
// and capture it by ref.
|
||||
elem_attributes, // Array<int>
|
||||
|
||||
input_is_dependent,
|
||||
direction_tr, // FieldDescriptor
|
||||
direction_tr_e, // Vector
|
||||
derivative_action_tr_e, // Vector
|
||||
element_dof_ordering, // ElementDofOrdering
|
||||
inputs_trial_op_dim,
|
||||
total_trial_op_dim,
|
||||
trial_vdim,
|
||||
input_restriction_transpose,
|
||||
qpdc_idx = cache_index,
|
||||
// capture by ref:
|
||||
&qpdc_mem = derivative_qp_caches_ref
|
||||
](
|
||||
std::vector<Vector> &f_e, const Vector &dir_tr_l,
|
||||
Vector &derivative_action_tr_l) mutable
|
||||
{
|
||||
restriction<entity_t>(direction_tr, dir_tr_l, direction_tr_e,
|
||||
element_dof_ordering);
|
||||
auto ye = Reshape(derivative_action_tr_e.ReadWrite(), num_trial_dof,
|
||||
trial_vdim, num_entities);
|
||||
auto wrapped_fields_e = wrap_fields(f_e, shmem_tr_info.field_sizes,
|
||||
num_entities);
|
||||
auto wrapped_direction_e = Reshape(direction_tr_e.ReadWrite(),
|
||||
shmem_tr_info.direction_size,
|
||||
num_entities);
|
||||
|
||||
auto qpdc = Reshape(qpdc_mem[qpdc_idx].Read(), test_vdim, test_op_dim,
|
||||
trial_vdim, total_trial_op_dim, num_qp, num_entities);
|
||||
|
||||
auto itod = Reshape(inputs_trial_op_dim.Read(), num_inputs);
|
||||
|
||||
const bool has_attr = attributes.Size() > 0;
|
||||
const auto d_attr = attributes.Read();
|
||||
const auto d_elem_attr = elem_attributes->Read();
|
||||
|
||||
derivative_action_tr_e = 0.0;
|
||||
forall([=] MFEM_HOST_DEVICE (int e, real_t *shmem)
|
||||
{
|
||||
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
|
||||
|
||||
auto [output_dtq_shmem, input_dtq_shmem, fields_shmem,
|
||||
direction_shmem, input_shmem,
|
||||
shadow_shmem_, residual_shmem,
|
||||
scratch_shmem] =
|
||||
unpack_shmem(shmem, shmem_tr_info, output_dtq_maps, input_dtq_maps,
|
||||
wrapped_fields_e, wrapped_direction_e, num_qp, e);
|
||||
auto &shadow_shmem = shadow_shmem_;
|
||||
|
||||
std::array<bool, num_outputs> all_true{true};
|
||||
map_direction_to_quadrature_data_conditional(
|
||||
shadow_shmem, direction_shmem, output_dtq_shmem, outputs,
|
||||
ir_weights, scratch_shmem, all_true, dimension,
|
||||
use_sum_factorization);
|
||||
|
||||
auto fhat = Reshape(&residual_shmem(0, 0), trial_vdim,
|
||||
total_trial_op_dim, num_qp);
|
||||
|
||||
auto qpdce = Reshape(&qpdc(0, 0, 0, 0, 0, e), test_vdim, test_op_dim,
|
||||
trial_vdim, total_trial_op_dim, num_qp);
|
||||
|
||||
constexpr bool transpose = true;
|
||||
apply_qpdc(fhat, shadow_shmem, qpdce, itod, q1d, dimension,
|
||||
use_sum_factorization, transpose);
|
||||
|
||||
auto y = Reshape(&ye(0, 0, e), num_trial_dof, trial_vdim);
|
||||
auto fi_shmem = Reshape(shmem + shmem_tr_info.total_size, trial_vdim,
|
||||
total_trial_op_dim, num_qp);
|
||||
|
||||
map_quadrature_data_to_fields_conditional(
|
||||
y, fhat, inputs, itod, input_dtq_shmem, scratch_shmem, fi_shmem,
|
||||
input_is_dependent, dimension, use_sum_factorization);
|
||||
}, num_entities, thread_blocks, shmem_tr_info.total_size,
|
||||
shmem_tr_cache.ReadWrite());
|
||||
input_restriction_transpose(derivative_action_tr_e, derivative_action_tr_l);
|
||||
});
|
||||
|
||||
assemble_derivative_sparsematrix_callbacks[derivative_id].push_back(
|
||||
[
|
||||
// capture by copy:
|
||||
@@ -1359,7 +1190,7 @@ void DifferentiableOperator::AddIntegrator(
|
||||
inputs_trial_op_dim,
|
||||
Ae_mem,
|
||||
output_to_field,
|
||||
qpdc_idx = cache_index,
|
||||
|
||||
// capture by ref:
|
||||
&qpdc_mem = derivative_qp_caches_ref,
|
||||
&fields = fields_ref
|
||||
@@ -1371,7 +1202,7 @@ void DifferentiableOperator::AddIntegrator(
|
||||
shmem_info.direction_size,
|
||||
num_entities);
|
||||
|
||||
auto qpdc = Reshape(qpdc_mem[qpdc_idx].Read(), test_vdim, test_op_dim,
|
||||
auto qpdc = Reshape(qpdc_mem.Read(), test_vdim, test_op_dim,
|
||||
trial_vdim, total_trial_op_dim, num_qp, num_entities);
|
||||
|
||||
auto itod = Reshape(inputs_trial_op_dim.Read(), num_inputs);
|
||||
@@ -1419,10 +1250,7 @@ void DifferentiableOperator::AddIntegrator(
|
||||
auto test_fes = *std::get_if<const ParFiniteElementSpace *>
|
||||
(&fields[output_to_field[0]].data);
|
||||
|
||||
if (A == nullptr)
|
||||
{
|
||||
A = new SparseMatrix(test_fes->GetVSize(), trial_fes->GetVSize());
|
||||
}
|
||||
A = new SparseMatrix(test_fes->GetVSize(), trial_fes->GetVSize());
|
||||
|
||||
auto tmp = Reshape(Ae_mem.HostReadWrite(), num_test_dof * test_vdim,
|
||||
num_trial_dof * trial_vdim, num_entities);
|
||||
@@ -1493,15 +1321,14 @@ void DifferentiableOperator::AddIntegrator(
|
||||
A->AddSubMatrix(test_vdofs, trial_vdofs, Aee, 1);
|
||||
}
|
||||
}
|
||||
// Don't finalize here since multiple callbacks might contribute to the same matrix
|
||||
// A->Finalize() will be called after all callbacks have contributed
|
||||
A->Finalize();
|
||||
});
|
||||
|
||||
// Create local references for MSVC lambda capture compatibility
|
||||
auto& assemble_derivative_sparsematrix_callbacks_ref =
|
||||
this->assemble_derivative_sparsematrix_callbacks[derivative_id];
|
||||
|
||||
assemble_derivative_hypreparmatrix_callback[derivative_id] =
|
||||
assemble_derivative_hypreparmatrix_callbacks[derivative_id].push_back(
|
||||
[
|
||||
input_is_dependent,
|
||||
input_to_field,
|
||||
@@ -1515,7 +1342,6 @@ void DifferentiableOperator::AddIntegrator(
|
||||
{
|
||||
f(f_e, spmat);
|
||||
}
|
||||
spmat->Finalize();
|
||||
|
||||
if (spmat == nullptr)
|
||||
{
|
||||
@@ -1569,7 +1395,7 @@ void DifferentiableOperator::AddIntegrator(
|
||||
trial_fes->Dof_TrueDof_Matrix());
|
||||
}
|
||||
delete spmat;
|
||||
};
|
||||
});
|
||||
}, derivative_ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,65 +533,4 @@ void map_quadrature_data_to_fields(
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N, typename field_operator_ts>
|
||||
MFEM_HOST_DEVICE
|
||||
void map_quadrature_data_to_fields_conditional(
|
||||
DeviceTensor<2, real_t> &y,
|
||||
const DeviceTensor<3, real_t> &f,
|
||||
const field_operator_ts &fops,
|
||||
const DeviceTensor<1, const real_t> &op_dims,
|
||||
const std::array<DofToQuadMap, N> &dtqmaps,
|
||||
std::array<DeviceTensor<1>, 6> &scratch_mem,
|
||||
const DeviceTensor<3> &fi_shmem,
|
||||
const std::array<bool, N> &conditions,
|
||||
const int &dimension,
|
||||
const bool &use_sum_factorization)
|
||||
{
|
||||
int offset = 0;
|
||||
for_constexpr<N>([&](auto i)
|
||||
{
|
||||
if (conditions[i])
|
||||
{
|
||||
[[maybe_unused]] const auto [K, unused, M] = f.GetShape();
|
||||
const int L = static_cast<int>(op_dims(static_cast<size_t>(i)));
|
||||
auto fi = Reshape(&fi_shmem(0, 0, 0), K, L, M);
|
||||
for (int k = 0; k < K; k++)
|
||||
{
|
||||
for (int l = 0; l < L; l++)
|
||||
{
|
||||
for (int m = 0; m < M; m++)
|
||||
{
|
||||
fi(k, l, m) = f(k, l + offset, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (use_sum_factorization)
|
||||
{
|
||||
if (dimension == 1)
|
||||
{
|
||||
map_quadrature_data_to_fields_tensor_impl_1d(
|
||||
y, fi, get<i>(fops), dtqmaps[i], scratch_mem);
|
||||
}
|
||||
else if (dimension == 2)
|
||||
{
|
||||
map_quadrature_data_to_fields_tensor_impl_2d(
|
||||
y, fi, get<i>(fops), dtqmaps[i], scratch_mem);
|
||||
}
|
||||
else if (dimension == 3)
|
||||
{
|
||||
map_quadrature_data_to_fields_tensor_impl_3d(
|
||||
y, fi, get<i>(fops), dtqmaps[i], scratch_mem);
|
||||
}
|
||||
else { MFEM_ABORT_KERNEL("dimension not supported"); }
|
||||
}
|
||||
else
|
||||
{
|
||||
map_quadrature_data_to_fields_impl(y, fi, get<i>(fops), dtqmaps[i]);
|
||||
}
|
||||
offset += L;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mfem::future
|
||||
|
||||
+17
-12
@@ -505,13 +505,13 @@ void map_field_to_quadrature_data(
|
||||
}
|
||||
}
|
||||
|
||||
template <typename field_operator_ts, size_t N, size_t M>
|
||||
template <typename field_operator_ts, size_t num_inputs, size_t num_fields>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void map_fields_to_quadrature_data(
|
||||
std::array<DeviceTensor<2>, N> &fields_qp,
|
||||
const std::array<DeviceTensor<1>, M> &fields_e,
|
||||
const std::array<DofToQuadMap, N> &dtqmaps,
|
||||
const std::array<size_t, N> &input_to_field,
|
||||
std::array<DeviceTensor<2>, num_inputs> &fields_qp,
|
||||
const std::array<DeviceTensor<1>, num_fields> &fields_e,
|
||||
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
|
||||
const std::array<size_t, num_inputs> &input_to_field,
|
||||
const field_operator_ts &fops,
|
||||
const DeviceTensor<1, const real_t> &integration_weights,
|
||||
const std::array<DeviceTensor<1>, 6> &scratch_mem,
|
||||
@@ -523,7 +523,7 @@ void map_fields_to_quadrature_data(
|
||||
// attached to them and we create a dummy field which is not accessed
|
||||
// inside the functions it is passed to.
|
||||
const auto dummy_field_weight = DeviceTensor<1>(nullptr, 0);
|
||||
for_constexpr<N>([&](auto i)
|
||||
for_constexpr<num_inputs>([&](auto i)
|
||||
{
|
||||
const DeviceTensor<1> &field_e =
|
||||
(input_to_field[i] == SIZE_MAX) ? dummy_field_weight :
|
||||
@@ -549,7 +549,12 @@ void map_fields_to_quadrature_data(
|
||||
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
|
||||
integration_weights, scratch_mem);
|
||||
}
|
||||
else { MFEM_ABORT_KERNEL("unsupported dimension"); }
|
||||
else
|
||||
{
|
||||
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
|
||||
MFEM_ABORT("unsupported dimension");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -622,20 +627,20 @@ void map_fields_to_quadrature_data_conditional(
|
||||
});
|
||||
}
|
||||
|
||||
template <size_t N, typename field_operator_ts>
|
||||
template <size_t num_inputs, typename field_operator_ts>
|
||||
MFEM_HOST_DEVICE
|
||||
void map_direction_to_quadrature_data_conditional(
|
||||
std::array<DeviceTensor<2>, N> &directions_qp,
|
||||
std::array<DeviceTensor<2>, num_inputs> &directions_qp,
|
||||
const DeviceTensor<1> &direction_e,
|
||||
const std::array<DofToQuadMap, N> &dtqmaps,
|
||||
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
|
||||
field_operator_ts fops,
|
||||
const DeviceTensor<1, const real_t> &integration_weights,
|
||||
const std::array<DeviceTensor<1>, 6> &scratch_mem,
|
||||
const std::array<bool, N> &conditions,
|
||||
const std::array<bool, num_inputs> &conditions,
|
||||
const int &dimension,
|
||||
const bool &use_sum_factorization)
|
||||
{
|
||||
for_constexpr<N>([&](auto i)
|
||||
for_constexpr<num_inputs>([&](auto i)
|
||||
{
|
||||
if (conditions[i])
|
||||
{
|
||||
|
||||
@@ -379,102 +379,54 @@ namespace detail
|
||||
/// @param shadow_shmem the shadow shared memory.
|
||||
/// @param qpdc the quadrature point data cache holding the resulting
|
||||
/// Jacobians on each quadrature point.
|
||||
/// @param op_dims operator dimensions.
|
||||
/// If an operator is dependent, the value corresponds to the spatial dimension.
|
||||
/// Otherwise a zero indicates indepence on the variable.
|
||||
/// @param itod inputs trial operator dimension.
|
||||
/// If input is dependent the value corresponds to the spatial dimension, otherwise
|
||||
/// a zero indicates non-dependence on the variable.
|
||||
/// @param q the current quadrature point index.
|
||||
/// @param transpose switch to use transpose action.
|
||||
template <size_t N>
|
||||
template <size_t num_fields>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void apply_qpdc(
|
||||
DeviceTensor<3> &fhat,
|
||||
const std::array<DeviceTensor<2>, N> &shadow_shmem,
|
||||
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
|
||||
const DeviceTensor<5, const real_t> &qpdc,
|
||||
const DeviceTensor<1, const real_t> &op_dims,
|
||||
const int &q,
|
||||
bool transpose)
|
||||
const DeviceTensor<1, const real_t> &itod,
|
||||
const int &q)
|
||||
{
|
||||
const size_t num_ops = op_dims.GetShape()[0];
|
||||
|
||||
const int test_vdim = qpdc.GetShape()[0];
|
||||
const int test_op_dim = qpdc.GetShape()[1];
|
||||
const int trial_vdim = qpdc.GetShape()[2];
|
||||
const int total_trial_op_dim = qpdc.GetShape()[3];
|
||||
|
||||
const int num_qp = qpdc.GetShape()[4];
|
||||
const size_t num_inputs = itod.GetShape()[0];
|
||||
|
||||
if (transpose)
|
||||
for (int i = 0; i < test_vdim; i++)
|
||||
{
|
||||
for (int j = 0; j < trial_vdim; j++)
|
||||
for (int k = 0; k < test_op_dim; k++)
|
||||
{
|
||||
for (int m = 0; m < total_trial_op_dim; m++)
|
||||
real_t sum = 0.0;
|
||||
int m_offset = 0;
|
||||
for (size_t s = 0; s < num_inputs; s++)
|
||||
{
|
||||
fhat(j, m, q) = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Since we don't support more than output space right now
|
||||
// shadow_shmem will always be of size 1.
|
||||
constexpr int shadow_idx_tr = 0;
|
||||
auto d_qp = Reshape(&(shadow_shmem[shadow_idx_tr])[0], test_vdim, test_op_dim,
|
||||
num_qp);
|
||||
|
||||
int m_offset = 0;
|
||||
for (size_t s = 0; s < num_ops; s++)
|
||||
{
|
||||
const int trial_op_dim = static_cast<int>(op_dims(s));
|
||||
if (trial_op_dim == 0) { continue; }
|
||||
|
||||
for (int j = 0; j < trial_vdim; j++)
|
||||
{
|
||||
for (int m = 0; m < trial_op_dim; m++)
|
||||
const int trial_op_dim = static_cast<int>(itod(s));
|
||||
if (trial_op_dim == 0)
|
||||
{
|
||||
real_t sum = 0.0;
|
||||
for (int i = 0; i < test_vdim; i++)
|
||||
{
|
||||
for (int k = 0; k < test_op_dim; k++)
|
||||
{
|
||||
const real_t contrib = qpdc(i, k, j, m + m_offset, q) * d_qp(i, k, q);
|
||||
sum += contrib;
|
||||
}
|
||||
}
|
||||
fhat(j, m + m_offset, q) += sum;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
m_offset += trial_op_dim;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < test_vdim; i++)
|
||||
{
|
||||
for (int k = 0; k < test_op_dim; k++)
|
||||
{
|
||||
real_t sum = 0.0;
|
||||
int m_offset = 0;
|
||||
for (size_t s = 0; s < num_ops; s++)
|
||||
const auto d_qp =
|
||||
Reshape(&(shadow_shmem[s])[0], trial_vdim, trial_op_dim, num_qp);
|
||||
for (int j = 0; j < trial_vdim; j++)
|
||||
{
|
||||
const int trial_op_dim = static_cast<int>(op_dims(s));
|
||||
if (trial_op_dim == 0) { continue; }
|
||||
|
||||
const auto d_qp =
|
||||
Reshape(&(shadow_shmem[s])[0], trial_vdim, trial_op_dim, num_qp);
|
||||
for (int j = 0; j < trial_vdim; j++)
|
||||
for (int m = 0; m < trial_op_dim; m++)
|
||||
{
|
||||
for (int m = 0; m < trial_op_dim; m++)
|
||||
{
|
||||
sum += qpdc(i, k, j, m + m_offset, q) * d_qp(j, m, q);
|
||||
}
|
||||
sum += qpdc(i, k, j, m + m_offset, q) * d_qp(j, m, q);
|
||||
}
|
||||
m_offset += trial_op_dim;
|
||||
}
|
||||
fhat(i, k, q) = sum;
|
||||
m_offset += trial_op_dim;
|
||||
}
|
||||
fhat(i, k, q) = sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
}
|
||||
|
||||
/// @brief Apply the quadrature point data cache (qpdc) to a vector
|
||||
/// (usually a direction).
|
||||
@@ -493,18 +445,16 @@ void apply_qpdc(
|
||||
/// @param q1d number of quadrature points in 1D.
|
||||
/// @param dimension spatial dimension.
|
||||
/// @param use_sum_factorization whether to use sum factorization.
|
||||
/// @param T switch to use transpose application.
|
||||
template <size_t N>
|
||||
template <size_t num_fields>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void apply_qpdc(
|
||||
DeviceTensor<3> &fhat,
|
||||
const std::array<DeviceTensor<2>, N> &shadow_shmem,
|
||||
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
|
||||
const DeviceTensor<5, const real_t> &qpdc,
|
||||
const DeviceTensor<1, const real_t> &itod,
|
||||
const int &q1d,
|
||||
const int &dimension,
|
||||
const bool &use_sum_factorization,
|
||||
const bool T = false)
|
||||
const bool &use_sum_factorization)
|
||||
{
|
||||
if (use_sum_factorization)
|
||||
{
|
||||
@@ -512,7 +462,7 @@ void apply_qpdc(
|
||||
{
|
||||
MFEM_FOREACH_THREAD_DIRECT(q, x, q1d)
|
||||
{
|
||||
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q, T);
|
||||
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
|
||||
}
|
||||
}
|
||||
else if (dimension == 2)
|
||||
@@ -522,7 +472,7 @@ void apply_qpdc(
|
||||
MFEM_FOREACH_THREAD_DIRECT(qy, y, q1d)
|
||||
{
|
||||
const int q = qx + q1d * qy;
|
||||
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q, T);
|
||||
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -535,7 +485,7 @@ void apply_qpdc(
|
||||
MFEM_FOREACH_THREAD_DIRECT(qz, z, q1d)
|
||||
{
|
||||
const int q = qx + q1d * (qy + q1d * qz);
|
||||
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q, T);
|
||||
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -550,7 +500,7 @@ void apply_qpdc(
|
||||
const int num_qp = qpdc.GetShape()[4];
|
||||
MFEM_FOREACH_THREAD_DIRECT(q, x, num_qp)
|
||||
{
|
||||
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q, T);
|
||||
detail::apply_qpdc(fhat, shadow_shmem, qpdc, itod, q);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,35 +243,6 @@ void process_qf_arg(
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, int n>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void process_qf_arg(
|
||||
const DeviceTensor<1, T> &u,
|
||||
const DeviceTensor<1, T> &v,
|
||||
tensor<T, n> &arg)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
arg(i) = u(i);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, int n, int m>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void process_qf_arg(
|
||||
const DeviceTensor<1, T> &u,
|
||||
const DeviceTensor<1, T> &v,
|
||||
tensor<T, n, m> &arg)
|
||||
{
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
arg(j, i) = u((i * n) + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename arg_type>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void process_qf_arg(const DeviceTensor<2> &u, arg_type &arg, int qp)
|
||||
@@ -356,4 +327,20 @@ void process_qf_result(
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, int n, int m>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void process_qf_arg(
|
||||
const DeviceTensor<1, T> &u,
|
||||
const DeviceTensor<1, T> &v,
|
||||
tensor<T, n, m> &arg)
|
||||
{
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
arg(j, i) = u((i * n) + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mfem::future
|
||||
|
||||
+3
-9
@@ -1202,14 +1202,7 @@ std::function<void(const Vector&, Vector&)> get_prolongation_transpose(
|
||||
const Operator *P = get_prolongation(f);
|
||||
auto PT = [=](const Vector &r_local, Vector &y)
|
||||
{
|
||||
if (P)
|
||||
{
|
||||
P->MultTranspose(r_local, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
y = r_local;
|
||||
}
|
||||
P->MultTranspose(r_local, y);
|
||||
};
|
||||
return PT;
|
||||
}
|
||||
@@ -1587,13 +1580,14 @@ struct SharedMemoryInfo
|
||||
std::array<int, 6> temp_sizes;
|
||||
};
|
||||
|
||||
template <typename entity_t, std::size_t num_fields, std::size_t num_inputs, std::size_t num_outputs>
|
||||
template <typename entity_t, std::size_t num_fields, std::size_t num_inputs, std::size_t num_outputs, typename input_t>
|
||||
SharedMemoryInfo<num_fields, num_inputs, num_outputs>
|
||||
get_shmem_info(
|
||||
const std::array<DofToQuadMap, num_inputs> &input_dtq_maps,
|
||||
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps,
|
||||
const std::vector<FieldDescriptor> &fields,
|
||||
const int &num_entities,
|
||||
const input_t &inputs,
|
||||
const int &num_qp,
|
||||
const std::vector<int> &input_size_on_qp,
|
||||
const int &residual_size_on_qp,
|
||||
|
||||
+3
-3
@@ -349,7 +349,7 @@ public:
|
||||
vector-valued finite elements, which is also the width of the
|
||||
DenseMatrix argument in
|
||||
CalcPhysVShape(ElementTransformation &Trans, DenseMatrix &shape). */
|
||||
int GetPhysRangeDim(int /* space_dim */) const { return vdim; }
|
||||
virtual int GetPhysRangeDim(int /* space_dim */) const { return vdim; }
|
||||
|
||||
/** Returns the dimension of the curl for vector-valued finite elements,
|
||||
which is also the width of the DenseMatrix argument in
|
||||
@@ -360,7 +360,7 @@ public:
|
||||
finite elements, which is also the width of the DenseMatrix argument in
|
||||
CalcPhysCurlShape(ElementTransformation &Trans, DenseMatrix &curl_shape).
|
||||
*/
|
||||
int GetPhysCurlDim(int /* space_dim */) const { return cdim; }
|
||||
virtual int GetPhysCurlDim(int /* space_dim */) const { return cdim; }
|
||||
|
||||
/// Returns the Geometry::Type of the reference element.
|
||||
Geometry::Type GetGeomType() const { return geom_type; }
|
||||
@@ -1017,7 +1017,7 @@ public:
|
||||
VectorFiniteElement(int D, Geometry::Type G, int Do, int O, int M,
|
||||
int F = FunctionSpace::Pk);
|
||||
|
||||
int GetPhysRangeDim(int space_dim) const { return space_dim; }
|
||||
int GetPhysRangeDim(int space_dim) const override { return space_dim; }
|
||||
};
|
||||
|
||||
/// @brief Class for computing 1D special polynomials and their associated basis
|
||||
|
||||
+4
-4
@@ -663,8 +663,8 @@ public:
|
||||
const int cb_type = BasisType::GaussLobatto,
|
||||
const int ob_type = BasisType::GaussLegendre);
|
||||
|
||||
int GetPhysRangeDim(int space_dim) const { return 2; }
|
||||
int GetPhysCurlDim(int space_dim) const { return 1; }
|
||||
int GetPhysRangeDim(int space_dim) const override { return 2; }
|
||||
int GetPhysCurlDim(int space_dim) const override { return 1; }
|
||||
|
||||
void CalcVShape(const IntegrationPoint &ip,
|
||||
DenseMatrix &shape) const override;
|
||||
@@ -708,8 +708,8 @@ private:
|
||||
DenseMatrix &I) const;
|
||||
|
||||
public:
|
||||
int GetPhysRangeDim(int space_dim) const { return 3; }
|
||||
int GetPhysCurlDim(int space_dim) const { return 3; }
|
||||
int GetPhysRangeDim(int space_dim) const override { return 3; }
|
||||
int GetPhysCurlDim(int space_dim) const override { return 3; }
|
||||
|
||||
using FiniteElement::CalcVShape;
|
||||
using FiniteElement::CalcPhysCurlShape;
|
||||
|
||||
+4
-4
@@ -510,8 +510,8 @@ public:
|
||||
RT_R2D_SegmentElement(const int p,
|
||||
const int ob_type = BasisType::GaussLegendre);
|
||||
|
||||
int GetPhysRangeDim(int space_dim) const { return 2; }
|
||||
int GetPhysCurlDim(int space_dim) const { return 0; }
|
||||
int GetPhysRangeDim(int space_dim) const override { return 2; }
|
||||
int GetPhysCurlDim(int space_dim) const override { return 0; }
|
||||
|
||||
void CalcVShape(const IntegrationPoint &ip,
|
||||
DenseMatrix &shape) const override;
|
||||
@@ -550,8 +550,8 @@ private:
|
||||
DenseMatrix &I) const;
|
||||
|
||||
public:
|
||||
int GetPhysRangeDim(int space_dim) const { return 3; }
|
||||
int GetPhysCurlDim(int space_dim) const { return 0; }
|
||||
int GetPhysRangeDim(int space_dim) const override { return 3; }
|
||||
int GetPhysCurlDim(int space_dim) const override { return 0; }
|
||||
|
||||
using FiniteElement::CalcVShape;
|
||||
|
||||
|
||||
@@ -62,10 +62,6 @@
|
||||
#include "pnonlinearform.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
#include "sidredatacollection.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_CONDUIT
|
||||
#include "conduitdatacollection.hpp"
|
||||
#endif
|
||||
|
||||
@@ -3934,6 +3934,16 @@ const FiniteElement *FiniteElementSpace::GetBE(int i) const
|
||||
return BE;
|
||||
}
|
||||
|
||||
const FiniteElement *FiniteElementSpace::GetTypicalBE() const
|
||||
{
|
||||
if (mesh->GetNBE() > 0) { return GetBE(0); }
|
||||
|
||||
Geometry::Type geom = mesh->GetTypicalFaceGeometry();
|
||||
const FiniteElement *be = fec->FiniteElementForGeometry(geom);
|
||||
MFEM_VERIFY(be != nullptr, "Could not determine a typical BE!");
|
||||
return be;
|
||||
}
|
||||
|
||||
const FiniteElement *FiniteElementSpace::GetFaceElement(int i) const
|
||||
{
|
||||
MFEM_VERIFY(!IsVariableOrder(), "not implemented");
|
||||
@@ -3964,6 +3974,11 @@ const FiniteElement *FiniteElementSpace::GetFaceElement(int i) const
|
||||
return fe;
|
||||
}
|
||||
|
||||
const FiniteElement *FiniteElementSpace::GetTypicalFaceElement() const
|
||||
{
|
||||
return fec->FiniteElementForGeometry(mesh->GetTypicalFaceGeometry());
|
||||
}
|
||||
|
||||
const FiniteElement *FiniteElementSpace::GetEdgeElement(int i,
|
||||
int variant) const
|
||||
{
|
||||
|
||||
+13
-1
@@ -839,7 +839,7 @@ public:
|
||||
Note: For vector-valued elements, the results pads up the range dimension
|
||||
to the spatial dimension. E.g., consider a stack of 5 vector-valued
|
||||
elements each representing 2D vectors, living in a 3 dimensional space.
|
||||
Then this fucntion would give 15, not 10.
|
||||
Then this function would give 15, not 10.
|
||||
*/
|
||||
int GetVectorDim() const;
|
||||
|
||||
@@ -1323,12 +1323,24 @@ public:
|
||||
associated with i'th boundary face in the mesh object. */
|
||||
const FiniteElement *GetBE(int i) const;
|
||||
|
||||
/// @brief Return a "typical" boundary element.
|
||||
///
|
||||
/// This can be used in situations where the local mesh partition may be
|
||||
/// empty.
|
||||
const FiniteElement *GetTypicalBE() const;
|
||||
|
||||
/** @brief Returns pointer to the FiniteElement in the FiniteElementCollection
|
||||
associated with i'th face in the mesh object. Faces in this case refer
|
||||
to the MESHDIM-1 primitive so in 2D they are segments and in 1D they are
|
||||
points.*/
|
||||
const FiniteElement *GetFaceElement(int i) const;
|
||||
|
||||
/// @brief Return a "typical" face element.
|
||||
///
|
||||
/// This can be used in situations where the local mesh partition may be
|
||||
/// empty.
|
||||
const FiniteElement *GetTypicalFaceElement() const;
|
||||
|
||||
/** @brief Returns pointer to the FiniteElement in the FiniteElementCollection
|
||||
associated with i'th edge in the mesh object. */
|
||||
const FiniteElement *GetEdgeElement(int i, int variant = 0) const;
|
||||
|
||||
+75
-65
@@ -345,27 +345,6 @@ void GridFunction::ComputeFlux(BilinearFormIntegrator &blfi,
|
||||
}
|
||||
}
|
||||
|
||||
int GridFunction::VectorDim() const
|
||||
{
|
||||
const FiniteElement *fe = fes->GetTypicalFE();
|
||||
if (!fe || fe->GetRangeType() == FiniteElement::SCALAR)
|
||||
{
|
||||
return fes->GetVDim();
|
||||
}
|
||||
return fes->GetVDim()*std::max(fes->GetMesh()->SpaceDimension(),
|
||||
fe->GetRangeDim());
|
||||
}
|
||||
|
||||
int GridFunction::CurlDim() const
|
||||
{
|
||||
const FiniteElement *fe = fes->GetTypicalFE();
|
||||
if (!fe || fe->GetRangeType() == FiniteElement::SCALAR)
|
||||
{
|
||||
return 2 * fes->GetMesh()->SpaceDimension() - 3;
|
||||
}
|
||||
return fes->GetVDim()*fe->GetCurlDim();
|
||||
}
|
||||
|
||||
void GridFunction::GetTrueDofs(Vector &tv) const
|
||||
{
|
||||
const SparseMatrix *R = fes->GetRestrictionMatrix();
|
||||
@@ -2050,6 +2029,18 @@ void GridFunction::AccumulateAndCountBdrValues(
|
||||
Coefficient *coeff[], VectorCoefficient *vcoeff, const Array<int> &attr,
|
||||
Array<int> &values_counter)
|
||||
{
|
||||
if (vcoeff)
|
||||
{
|
||||
MFEM_VERIFY(fes->GetVDim() == vcoeff->GetVDim(),
|
||||
"vcoeff vdim != fes VDim");
|
||||
MFEM_VERIFY(fes->GetTypicalBE()->GetMapType() == FiniteElement::VALUE &&
|
||||
fes->GetTypicalBE()->GetRangeType() ==
|
||||
FiniteElement::SCALAR,
|
||||
"Can only call ProjectBdrCoefficient on scalar value-type "
|
||||
"boundary elements. "
|
||||
"Did you intended to call ProjectBdrCoefficientNormal or "
|
||||
"ProjectBdrCoefficientTangent for vector finite elements?");
|
||||
}
|
||||
Array<int> vdofs;
|
||||
Vector vc;
|
||||
|
||||
@@ -2202,6 +2193,9 @@ void GridFunction::AccumulateAndCountBdrTangentValues(
|
||||
VectorCoefficient &vcoeff, const Array<int> &bdr_attr,
|
||||
Array<int> &values_counter)
|
||||
{
|
||||
MFEM_VERIFY(fes->GetTypicalBE()->GetPhysRangeDim(
|
||||
fes->GetMesh()->SpaceDimension()) == vcoeff.GetVDim(),
|
||||
"vcoeff vdim != PhysRangeDim");
|
||||
const FiniteElement *fe;
|
||||
ElementTransformation *T;
|
||||
Array<int> dofs;
|
||||
@@ -2355,6 +2349,9 @@ void GridFunction::ProjectDeltaCoefficient(DeltaCoefficient &delta_coeff,
|
||||
|
||||
void GridFunction::ProjectCoefficient(Coefficient &coeff, ProjectType type)
|
||||
{
|
||||
MFEM_VERIFY(
|
||||
VectorDim() == 1,
|
||||
"Cannot project scalar Coefficient onto vector GridFunction");
|
||||
DeltaCoefficient *delta_c = dynamic_cast<DeltaCoefficient *>(&coeff);
|
||||
DofTransformation doftrans;
|
||||
Array<int> vdofs;
|
||||
@@ -2630,6 +2627,7 @@ void GridFunction::ProjectCoefficient(
|
||||
void GridFunction::ProjectCoefficient(VectorCoefficient &vcoeff,
|
||||
ProjectType type)
|
||||
{
|
||||
MFEM_VERIFY(VectorDim() == vcoeff.GetVDim(), "vcoeff vdim != VectorDim()");
|
||||
Array<int> vdofs;
|
||||
Vector vals;
|
||||
DofTransformation doftrans;
|
||||
@@ -2945,6 +2943,7 @@ void GridFunction::ProjectCoefficientElementL2(VectorCoefficient &vcoeff)
|
||||
void GridFunction::ProjectCoefficient(
|
||||
VectorCoefficient &vcoeff, Array<int> &dofs)
|
||||
{
|
||||
MFEM_VERIFY(VectorDim() == vcoeff.GetVDim(), "vcoeff vdim != VectorDim()");
|
||||
int el = -1;
|
||||
ElementTransformation *T = NULL;
|
||||
const FiniteElement *fe = NULL;
|
||||
@@ -2974,6 +2973,7 @@ void GridFunction::ProjectCoefficient(
|
||||
|
||||
void GridFunction::ProjectCoefficient(VectorCoefficient &vcoeff, int attribute)
|
||||
{
|
||||
MFEM_VERIFY(VectorDim() == vcoeff.GetVDim(), "vcoeff vdim != VectorDim()");
|
||||
int i;
|
||||
Array<int> vdofs;
|
||||
Vector vals;
|
||||
@@ -3033,6 +3033,7 @@ void GridFunction::ProjectCoefficient(Coefficient *coeff[])
|
||||
void GridFunction::ProjectDiscCoefficient(VectorCoefficient &coeff,
|
||||
Array<int> &dof_attr)
|
||||
{
|
||||
MFEM_VERIFY(VectorDim() == coeff.GetVDim(), "coeff vdim != VectorDim()");
|
||||
Array<int> vdofs;
|
||||
Vector vals;
|
||||
|
||||
@@ -3064,6 +3065,7 @@ void GridFunction::ProjectDiscCoefficient(VectorCoefficient &coeff,
|
||||
|
||||
void GridFunction::ProjectDiscCoefficient(VectorCoefficient &coeff)
|
||||
{
|
||||
MFEM_VERIFY(VectorDim() == coeff.GetVDim(), "coeff vdim != VectorDim()");
|
||||
Array<int> dof_attr;
|
||||
ProjectDiscCoefficient(coeff, dof_attr);
|
||||
}
|
||||
@@ -3073,6 +3075,10 @@ void GridFunction::ProjectDiscCoefficient(Coefficient &coeff, AvgType type)
|
||||
// Harmonic (x1 ... xn) = [ (1/x1 + ... + 1/xn) / n ]^-1.
|
||||
// Arithmetic(x1 ... xn) = (x1 + ... + xn) / n.
|
||||
|
||||
MFEM_VERIFY(
|
||||
VectorDim() == 1,
|
||||
"Cannot project a scalar coefficient onto a vector GridFunction");
|
||||
|
||||
Array<int> zones_per_vdof;
|
||||
AccumulateAndCountZones(coeff, type, zones_per_vdof);
|
||||
|
||||
@@ -3082,6 +3088,7 @@ void GridFunction::ProjectDiscCoefficient(Coefficient &coeff, AvgType type)
|
||||
void GridFunction::ProjectDiscCoefficient(VectorCoefficient &coeff,
|
||||
AvgType type)
|
||||
{
|
||||
MFEM_VERIFY(VectorDim() == coeff.GetVDim(), "coeff vdim != VectorDim()");
|
||||
Array<int> zones_per_vdof;
|
||||
AccumulateAndCountZones(coeff, type, zones_per_vdof);
|
||||
|
||||
@@ -3137,52 +3144,33 @@ void GridFunction::ProjectBdrCoefficient(Coefficient *coeff[],
|
||||
}
|
||||
|
||||
void GridFunction::ProjectBdrCoefficientNormal(
|
||||
VectorCoefficient &vcoeff, const Array<int> &bdr_attr)
|
||||
Coefficient *coeff, VectorCoefficient *vcoeff, const Array<int> &bdr_attr)
|
||||
{
|
||||
#if 0
|
||||
// implementation for the case when the face dofs are integrals of the
|
||||
// normal component.
|
||||
const FiniteElement *fe;
|
||||
ElementTransformation *T;
|
||||
Array<int> dofs;
|
||||
int dim = vcoeff.GetVDim();
|
||||
Vector vc(dim), nor(dim), lvec, shape;
|
||||
|
||||
for (int i = 0; i < fes->GetNBE(); i++)
|
||||
MFEM_VERIFY(fes->GetVDim() == 1, "fespace VDim != 1");
|
||||
MFEM_VERIFY(fes->GetTypicalBE()->GetRangeType() == FiniteElement::SCALAR &&
|
||||
fes->GetTypicalBE()->GetMapType() == FiniteElement::INTEGRAL,
|
||||
"Not an RT FE space!");
|
||||
if (vcoeff)
|
||||
{
|
||||
if (bdr_attr[fes->GetBdrAttribute(i)-1] == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
fe = fes->GetBE(i);
|
||||
T = fes->GetBdrElementTransformation(i);
|
||||
int intorder = 2*fe->GetOrder(); // !!!
|
||||
const IntegrationRule &ir = IntRules.Get(fe->GetGeomType(), intorder);
|
||||
int nd = fe->GetDof();
|
||||
lvec.SetSize(nd);
|
||||
shape.SetSize(nd);
|
||||
lvec = 0.0;
|
||||
for (int j = 0; j < ir.GetNPoints(); j++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(j);
|
||||
T->SetIntPoint(&ip);
|
||||
vcoeff.Eval(vc, *T, ip);
|
||||
CalcOrtho(T->Jacobian(), nor);
|
||||
fe->CalcShape(ip, shape);
|
||||
lvec.Add(ip.weight * (vc * nor), shape);
|
||||
}
|
||||
fes->GetBdrElementDofs(i, dofs);
|
||||
SetSubVector(dofs, lvec);
|
||||
MFEM_VERIFY(vcoeff->GetVDim() == fes->GetMesh()->SpaceDimension(),
|
||||
"vcoeff vdim (" << vcoeff->GetVDim()
|
||||
<< ") != SpaceDimension ("
|
||||
<< fes->GetMesh()->SpaceDimension() << ")");
|
||||
}
|
||||
#else
|
||||
|
||||
// implementation for the case when the face dofs are scaled point
|
||||
// values of the normal component.
|
||||
const FiniteElement *fe;
|
||||
ElementTransformation *T;
|
||||
Array<int> dofs;
|
||||
int dim = vcoeff.GetVDim();
|
||||
Vector vc(dim), nor(dim), lvec;
|
||||
Vector vc, nor, lvec;
|
||||
DofTransformation doftrans;
|
||||
if (vcoeff)
|
||||
{
|
||||
const int dim = vcoeff->GetVDim();
|
||||
vc.SetSize(dim);
|
||||
nor.SetSize(dim);
|
||||
}
|
||||
|
||||
for (int i = 0; i < fes->GetNBE(); i++)
|
||||
{
|
||||
@@ -3198,15 +3186,22 @@ void GridFunction::ProjectBdrCoefficientNormal(
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(j);
|
||||
T->SetIntPoint(&ip);
|
||||
vcoeff.Eval(vc, *T, ip);
|
||||
CalcOrtho(T->Jacobian(), nor);
|
||||
lvec(j) = (vc * nor);
|
||||
if (coeff)
|
||||
{
|
||||
const real_t c = coeff->Eval(*T, ip);
|
||||
lvec(j) = c * T->Weight();
|
||||
}
|
||||
else if (vcoeff)
|
||||
{
|
||||
vcoeff->Eval(vc, *T, ip);
|
||||
CalcOrtho(T->Jacobian(), nor);
|
||||
lvec(j) = (vc * nor);
|
||||
}
|
||||
}
|
||||
fes->GetBdrElementDofs(i, dofs, doftrans);
|
||||
doftrans.TransformPrimal(lvec);
|
||||
SetSubVector(dofs, lvec);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GridFunction::ProjectBdrCoefficientTangent(
|
||||
@@ -5007,6 +5002,14 @@ real_t ExtrudeCoefficient::Eval(ElementTransformation &T,
|
||||
return sol_in.Eval(*T_in, ip);
|
||||
}
|
||||
|
||||
void VectorExtrudeCoefficient::Eval(Vector &v, ElementTransformation &T,
|
||||
const IntegrationPoint &ip)
|
||||
{
|
||||
ElementTransformation *T_in =
|
||||
mesh_in->GetElementTransformation(T.ElementNo / n);
|
||||
T_in->SetIntPoint(&ip);
|
||||
sol_in.Eval(v, *T_in, ip);
|
||||
}
|
||||
|
||||
GridFunction *Extrude1DGridFunction(Mesh *mesh, Mesh *mesh2d,
|
||||
GridFunction *sol, const int ny)
|
||||
@@ -5057,10 +5060,17 @@ GridFunction *Extrude1DGridFunction(Mesh *mesh, Mesh *mesh2d,
|
||||
return NULL;
|
||||
}
|
||||
FiniteElementSpace *solfes2d;
|
||||
// assuming sol is scalar
|
||||
solfes2d = new FiniteElementSpace(mesh2d, solfec2d);
|
||||
const int vdim = sol->FESpace()->GetVDim();
|
||||
solfes2d = new FiniteElementSpace(mesh2d, solfec2d, vdim);
|
||||
sol2d = new GridFunction(solfes2d);
|
||||
sol2d->MakeOwner(solfec2d);
|
||||
if (vdim > 1)
|
||||
{
|
||||
VectorGridFunctionCoefficient vcsol(sol);
|
||||
VectorExtrudeCoefficient vc2d(mesh, vcsol, ny);
|
||||
sol2d->ProjectCoefficient(vc2d);
|
||||
}
|
||||
else
|
||||
{
|
||||
GridFunctionCoefficient csol(sol);
|
||||
ExtrudeCoefficient c2d(mesh, csol, ny);
|
||||
@@ -5758,4 +5768,4 @@ std::pair<real_t, real_t> GridFunction::EstimateFunctionMaximum(
|
||||
return std::make_pair(global_max_lower, global_max_upper);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+69
-13
@@ -150,11 +150,13 @@ public:
|
||||
|
||||
FiniteElementCollection *OwnFEC() { return fec_owned; }
|
||||
|
||||
/// Shortcut for calling FiniteElementSpace::GetVectorDim() on the underlying #fes
|
||||
int VectorDim() const;
|
||||
/** @brief Shortcut for calling FiniteElementSpace::GetVectorDim() on the
|
||||
underlying #fes */
|
||||
int VectorDim() const { return fes->GetVectorDim(); }
|
||||
|
||||
/// Shortcut for calling FiniteElementSpace::GetCurlDim() on the underlying #fes
|
||||
int CurlDim() const;
|
||||
/** @brief Shortcut for calling FiniteElementSpace::GetCurlDim() on the
|
||||
underlying #fes */
|
||||
int CurlDim() const { return fes->GetCurlDim(); }
|
||||
|
||||
/// Read only access to the (optional) internal true-dof Vector.
|
||||
const Vector &GetTrueVector() const
|
||||
@@ -532,6 +534,9 @@ public:
|
||||
std::unique_ptr<GridFunction> ProlongateToMaxOrder() const;
|
||||
|
||||
protected:
|
||||
void ProjectBdrCoefficientNormal(Coefficient *coeff, VectorCoefficient *vcoeff,
|
||||
const Array<int> &attr);
|
||||
|
||||
/** @brief Accumulates (depending on @a type) the values of @a coeff at all
|
||||
shared vdofs and counts in how many zones each vdof appears. */
|
||||
void AccumulateAndCountZones(Coefficient &coeff, AvgType type,
|
||||
@@ -656,15 +661,26 @@ public:
|
||||
virtual void ProjectBdrCoefficient(Coefficient *coeff[],
|
||||
const Array<int> &attr);
|
||||
|
||||
/** Project the normal component of the given VectorCoefficient on
|
||||
the boundary. Only boundary attributes that are marked in
|
||||
'bdr_attr' are projected. Assumes RT-type VectorFE GridFunction. */
|
||||
/** @brief Project the normal component of the given VectorCoefficient on
|
||||
the boundary. */
|
||||
/** Only boundary attributes that are marked in @a bdr_attr are
|
||||
projected. Assumes RT-type vector finite element GridFunction. */
|
||||
void ProjectBdrCoefficientNormal(VectorCoefficient &vcoeff,
|
||||
const Array<int> &bdr_attr);
|
||||
const Array<int> &bdr_attr)
|
||||
{ ProjectBdrCoefficientNormal(NULL, &vcoeff, bdr_attr); }
|
||||
|
||||
/** @brief Project the given Coefficient in the normal direction on the
|
||||
boundary. */
|
||||
/** Only boundary attributes that are marked in @a bdr_attr are projected.
|
||||
Assumes RT-type vector finite element GridFunction. */
|
||||
void ProjectBdrCoefficientNormal(Coefficient &coeff,
|
||||
const Array<int> &bdr_attr)
|
||||
{ ProjectBdrCoefficientNormal(&coeff, NULL, bdr_attr); }
|
||||
|
||||
/** @brief Project the tangential components of the given VectorCoefficient
|
||||
on the boundary. Only boundary attributes that are marked in @a bdr_attr
|
||||
are projected. Assumes ND-type VectorFE GridFunction. */
|
||||
on the boundary. */
|
||||
/** Only boundary attributes that are marked in @a bdr_attr
|
||||
are projected. Assumes ND-type vector finite element GridFunction. */
|
||||
virtual void ProjectBdrCoefficientTangent(VectorCoefficient &vcoeff,
|
||||
const Array<int> &bdr_attr);
|
||||
|
||||
@@ -1914,7 +1930,7 @@ real_t ComputeElementLpDistance(real_t p, int i,
|
||||
GridFunction& gf1, GridFunction& gf2);
|
||||
|
||||
|
||||
/// Class used for extruding scalar GridFunctions
|
||||
/// Class used for extruding a scalar coefficient
|
||||
class ExtrudeCoefficient : public Coefficient
|
||||
{
|
||||
private:
|
||||
@@ -1922,13 +1938,53 @@ private:
|
||||
Mesh *mesh_in;
|
||||
Coefficient &sol_in;
|
||||
public:
|
||||
/// Constructs an instance of VectorExtrudeCoefficient
|
||||
/**
|
||||
* @param m 1D mesh
|
||||
* @param s 1D vector coefficient
|
||||
* @param n_ number of transverse elements of the extruded mesh
|
||||
*/
|
||||
ExtrudeCoefficient(Mesh *m, Coefficient &s, int n_)
|
||||
: n(n_), mesh_in(m), sol_in(s) { }
|
||||
: n(n_), mesh_in(m), sol_in(s)
|
||||
{ MFEM_VERIFY(n > 0, "Number of transverse elements must be positive!"); }
|
||||
|
||||
real_t Eval(ElementTransformation &T, const IntegrationPoint &ip) override;
|
||||
|
||||
virtual ~ExtrudeCoefficient() { }
|
||||
};
|
||||
|
||||
/// Extrude a scalar 1D GridFunction, after extruding the mesh with Extrude1D.
|
||||
/// Class used for extruding a vector coefficient
|
||||
class VectorExtrudeCoefficient : public VectorCoefficient
|
||||
{
|
||||
private:
|
||||
int n;
|
||||
Mesh *mesh_in;
|
||||
VectorCoefficient &sol_in;
|
||||
public:
|
||||
/// Constructs an instance of VectorExtrudeCoefficient
|
||||
/**
|
||||
* @param m 1D mesh
|
||||
* @param s 1D vector coefficient
|
||||
* @param n_ number of transverse elements of the extruded mesh
|
||||
*/
|
||||
VectorExtrudeCoefficient(Mesh *m, VectorCoefficient &s, int n_)
|
||||
: VectorCoefficient(s.GetVDim()), n(n_), mesh_in(m), sol_in(s)
|
||||
{ MFEM_VERIFY(n > 0, "Number of transverse elements must be positive!"); }
|
||||
|
||||
void Eval(Vector &v, ElementTransformation &T,
|
||||
const IntegrationPoint &ip) override;
|
||||
using VectorCoefficient::Eval;
|
||||
|
||||
virtual ~VectorExtrudeCoefficient() { }
|
||||
};
|
||||
|
||||
/// Extrude a 1D GridFunction, after extruding the mesh with Extrude1D()
|
||||
/**
|
||||
* @param mesh 1D mesh
|
||||
* @param mesh2d extruded mesh
|
||||
* @param sol grid function
|
||||
* @param ny number of transverse elements of the extruded mesh
|
||||
*/
|
||||
GridFunction *Extrude1DGridFunction(Mesh *mesh, Mesh *mesh2d,
|
||||
GridFunction *sol, const int ny);
|
||||
|
||||
|
||||
@@ -197,15 +197,21 @@ static void EAHdivAssemble3D(const int NE,
|
||||
// Assemble (one row per thread)
|
||||
MFEM_FOREACH_THREAD(idx_i, x, NDOF)
|
||||
{
|
||||
// NOTE: due to an llvm backend bug, usage of the modulus operator
|
||||
// has been removed from this foreach section.
|
||||
const int ic = idx_i / NDOF_C;
|
||||
const int idx_ii = idx_i % NDOF_C;
|
||||
const int idx_ii = idx_i - ic * NDOF_C; // idx_i % NDOF_C
|
||||
|
||||
const int nx_i = (ic == 0) ? D1D : D1D-1;
|
||||
const int ny_i = (ic == 1) ? D1D : D1D-1;
|
||||
|
||||
const int ix = idx_ii % nx_i;
|
||||
const int iy = (idx_ii / nx_i) % ny_i;
|
||||
const int iz = (idx_ii / nx_i) / ny_i;
|
||||
const int qx_i = idx_ii / nx_i;
|
||||
const int ix = idx_ii - qx_i * nx_i; // idx_ii % nx_i
|
||||
|
||||
const int qy_i = qx_i / ny_i;
|
||||
const int iy = qx_i - qy_i * ny_i; // (idx_ii / nx_i) % ny_i
|
||||
|
||||
const int iz = qy_i; // (idx_ii / nx_i) / ny_i
|
||||
|
||||
const real_t (&Bi1)[MQ1][MD1] = (ic == 0) ? r_Bc : r_Bo;
|
||||
const real_t (&Bi2)[MQ1][MD1] = (ic == 1) ? r_Bc : r_Bo;
|
||||
@@ -214,14 +220,18 @@ static void EAHdivAssemble3D(const int NE,
|
||||
for (int idx_j = 0; idx_j < NDOF; ++idx_j)
|
||||
{
|
||||
const int jc = idx_j / NDOF_C;
|
||||
const int idx_jj = idx_j % NDOF_C;
|
||||
const int idx_jj = idx_j - jc * NDOF_C; // idx_j % NDOF_C
|
||||
|
||||
const int nx_j = (jc == 0) ? D1D : D1D-1;
|
||||
const int ny_j = (jc == 1) ? D1D : D1D-1;
|
||||
|
||||
const int jx = idx_jj % nx_j;
|
||||
const int jy = (idx_jj / nx_j) % ny_j;
|
||||
const int jz = (idx_jj / nx_j) / ny_j;
|
||||
const int qx_j = idx_jj / nx_j;
|
||||
const int jx = idx_jj - qx_j * nx_j; // idx_jj % nx_j
|
||||
|
||||
const int qy_j = qx_j / ny_j;
|
||||
const int jy = qx_j - qy_j * ny_j; // (idx_jj / nx_j) % ny_j
|
||||
|
||||
const int jz = qy_j; // (idx_jj / nx_j) / ny_j
|
||||
|
||||
const real_t (&Bj1)[MQ1][MD1] = (jc == 0) ? r_Bc : r_Bo;
|
||||
const real_t (&Bj2)[MQ1][MD1] = (jc == 1) ? r_Bc : r_Bo;
|
||||
|
||||
+811
-327
File diff suppressed because it is too large
Load Diff
+30
-27
@@ -125,18 +125,6 @@ private:
|
||||
void AddTriPoints3b(const int off, const real_t b, const real_t weight)
|
||||
{ AddTriPoints3(off, (1. - b)/2., b, weight); }
|
||||
|
||||
void AddTriPoints3R(const int off, const real_t a, const real_t b,
|
||||
const real_t c, const real_t weight)
|
||||
{
|
||||
IntPoint(off + 0).Set2w(a, b, weight);
|
||||
IntPoint(off + 1).Set2w(c, a, weight);
|
||||
IntPoint(off + 2).Set2w(b, c, weight);
|
||||
}
|
||||
|
||||
void AddTriPoints3R(const int off, const real_t a, const real_t b,
|
||||
const real_t weight)
|
||||
{ AddTriPoints3R(off, a, b, 1. - a - b, weight); }
|
||||
|
||||
void AddTriPoints6(const int off, const real_t a, const real_t b,
|
||||
const real_t c, const real_t weight)
|
||||
{
|
||||
@@ -183,14 +171,6 @@ private:
|
||||
AddTetPoints3(off + 1, a, 1. - 3.*a, weight);
|
||||
}
|
||||
|
||||
// given b, add the permutations of (a,a,a,b), where 3*a + b = 1
|
||||
void AddTetPoints4b(const int off, const real_t b, const real_t weight)
|
||||
{
|
||||
const real_t a = (1. - b)/3.;
|
||||
IntPoint(off).Set(a, a, a, weight);
|
||||
AddTetPoints3(off + 1, a, b, weight);
|
||||
}
|
||||
|
||||
// add the permutations of (a,a,b,b), 2*(a + b) = 1
|
||||
void AddTetPoints6(const int off, const real_t a, const real_t weight)
|
||||
{
|
||||
@@ -209,14 +189,37 @@ private:
|
||||
AddTetPoints6(off + 6, a, bc, cb, weight);
|
||||
}
|
||||
|
||||
// given (b,c), add the permutations of (a,a,b,c), 2*a + b + c = 1
|
||||
void AddTetPoints12bc(const int off, const real_t b, const real_t c,
|
||||
const real_t weight)
|
||||
// add all 24 permutations of (a,b,c,d) where a+b+c+d = 1, all distinct
|
||||
void AddTetPoints24(const int off, const real_t a, const real_t b,
|
||||
const real_t c, const real_t weight)
|
||||
{
|
||||
const real_t a = (1. - b - c)/2.;
|
||||
AddTetPoints3(off, a, b, weight);
|
||||
AddTetPoints3(off + 3, a, c, weight);
|
||||
AddTetPoints6(off + 6, a, b, c, weight);
|
||||
const real_t d = 1. - a - b - c;
|
||||
// all 24 permutations of 4 distinct barycentric coordinates
|
||||
// permuting which coordinate goes to x, y, z (4th is 1-x-y-z)
|
||||
IntPoint(off + 0).Set(a, b, c, weight);
|
||||
IntPoint(off + 1).Set(a, b, d, weight);
|
||||
IntPoint(off + 2).Set(a, c, b, weight);
|
||||
IntPoint(off + 3).Set(a, c, d, weight);
|
||||
IntPoint(off + 4).Set(a, d, b, weight);
|
||||
IntPoint(off + 5).Set(a, d, c, weight);
|
||||
IntPoint(off + 6).Set(b, a, c, weight);
|
||||
IntPoint(off + 7).Set(b, a, d, weight);
|
||||
IntPoint(off + 8).Set(b, c, a, weight);
|
||||
IntPoint(off + 9).Set(b, c, d, weight);
|
||||
IntPoint(off + 10).Set(b, d, a, weight);
|
||||
IntPoint(off + 11).Set(b, d, c, weight);
|
||||
IntPoint(off + 12).Set(c, a, b, weight);
|
||||
IntPoint(off + 13).Set(c, a, d, weight);
|
||||
IntPoint(off + 14).Set(c, b, a, weight);
|
||||
IntPoint(off + 15).Set(c, b, d, weight);
|
||||
IntPoint(off + 16).Set(c, d, a, weight);
|
||||
IntPoint(off + 17).Set(c, d, b, weight);
|
||||
IntPoint(off + 18).Set(d, a, b, weight);
|
||||
IntPoint(off + 19).Set(d, a, c, weight);
|
||||
IntPoint(off + 20).Set(d, b, a, weight);
|
||||
IntPoint(off + 21).Set(d, b, c, weight);
|
||||
IntPoint(off + 22).Set(d, c, a, weight);
|
||||
IntPoint(off + 23).Set(d, c, b, weight);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
+3
-1
@@ -297,7 +297,8 @@ void LinearForm::Assemble()
|
||||
tr = mesh->GetBdrFaceTransformations(i);
|
||||
if (tr != NULL)
|
||||
{
|
||||
fes -> GetElementVDofs (tr -> Elem1No, vdofs);
|
||||
mfem::DofTransformation doftrans;
|
||||
fes -> GetElementVDofs (tr -> Elem1No, vdofs, doftrans);
|
||||
for (int k = 0; k < boundary_face_integs.Size(); k++)
|
||||
{
|
||||
if (boundary_face_integs_marker[k] &&
|
||||
@@ -307,6 +308,7 @@ void LinearForm::Assemble()
|
||||
boundary_face_integs[k]->
|
||||
AssembleRHSElementVect(*fes->GetFE(tr->Elem1No),
|
||||
*tr, elemvect);
|
||||
doftrans.TransformDual(elemvect);
|
||||
AddElementVector (vdofs, elemvect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +545,8 @@ void ParGridFunction::GetElementDofValues(int el, Vector &dof_vals) const
|
||||
|
||||
void ParGridFunction::ProjectCoefficient(Coefficient &coeff, ProjectType type)
|
||||
{
|
||||
MFEM_VERIFY(VectorDim() == 1,
|
||||
"Cannot project scalar coefficient onto vector ParGridFunction");
|
||||
DeltaCoefficient *delta_c = dynamic_cast<DeltaCoefficient *>(&coeff);
|
||||
|
||||
if (delta_c == NULL)
|
||||
@@ -717,6 +719,7 @@ void ParGridFunction::ProjectCoefficientElementL2(VectorCoefficient &vcoeff)
|
||||
|
||||
void ParGridFunction::ProjectDiscCoefficient(VectorCoefficient &coeff)
|
||||
{
|
||||
MFEM_VERIFY(VectorDim() == coeff.GetVDim(), "coeff vdim != VectorDim()");
|
||||
// local maximal element attribute for each dof
|
||||
Array<int> ldof_attr;
|
||||
|
||||
@@ -761,6 +764,9 @@ void ParGridFunction::ProjectDiscCoefficient(VectorCoefficient &coeff)
|
||||
|
||||
void ParGridFunction::ProjectDiscCoefficient(Coefficient &coeff, AvgType type)
|
||||
{
|
||||
MFEM_VERIFY(
|
||||
VectorDim() == 1,
|
||||
"Cannot project scalar coefficient onto a vector ParGridFunction");
|
||||
// Harmonic (x1 ... xn) = [ (1/x1 + ... + 1/xn) / n ]^-1.
|
||||
// Arithmetic(x1 ... xn) = (x1 + ... + xn) / n.
|
||||
|
||||
@@ -786,6 +792,8 @@ void ParGridFunction::ProjectDiscCoefficient(VectorCoefficient &vcoeff,
|
||||
// Harmonic (x1 ... xn) = [ (1/x1 + ... + 1/xn) / n ]^-1.
|
||||
// Arithmetic(x1 ... xn) = (x1 + ... + xn) / n.
|
||||
|
||||
MFEM_VERIFY(VectorDim() == vcoeff.GetVDim(), "vcoeff vdim != VectorDim()");
|
||||
|
||||
// Number of zones that contain a given dof.
|
||||
Array<int> zones_per_vdof;
|
||||
AccumulateAndCountZones(vcoeff, type, zones_per_vdof);
|
||||
@@ -858,6 +866,12 @@ void ParGridFunction::ProjectBdrCoefficient(
|
||||
#endif
|
||||
}
|
||||
|
||||
void ParGridFunction::ProjectBdrCoefficient(VectorCoefficient &vcoeff,
|
||||
const Array<int> &attr)
|
||||
{
|
||||
ProjectBdrCoefficient(NULL, &vcoeff, attr);
|
||||
}
|
||||
|
||||
void ParGridFunction::ProjectBdrCoefficientTangent(VectorCoefficient &vcoeff,
|
||||
const Array<int> &bdr_attr)
|
||||
{
|
||||
|
||||
+1
-2
@@ -280,8 +280,7 @@ public:
|
||||
using GridFunction::ProjectBdrCoefficient;
|
||||
|
||||
void ProjectBdrCoefficient(VectorCoefficient &vcoeff,
|
||||
const Array<int> &attr) override
|
||||
{ ProjectBdrCoefficient(NULL, &vcoeff, attr); }
|
||||
const Array<int> &attr) override;
|
||||
|
||||
void ProjectBdrCoefficient(Coefficient *coeff[],
|
||||
const Array<int> &attr) override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,539 +0,0 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#ifndef MFEM_SIDREDATACOLLECTION
|
||||
#define MFEM_SIDREDATACOLLECTION
|
||||
|
||||
#include "../config/config.hpp"
|
||||
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
|
||||
#include "datacollection.hpp"
|
||||
|
||||
// Ignore warnings from the axom/sidre header (GCC + Clang versions)
|
||||
#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
|
||||
# pragma GCC diagnostic push
|
||||
# if defined(__clang__)
|
||||
# pragma GCC diagnostic ignored "-Wextra-semi"
|
||||
# else // real GCC?
|
||||
# pragma GCC diagnostic ignored "-Wpedantic"
|
||||
# endif
|
||||
#endif
|
||||
#include <axom/sidre.hpp>
|
||||
#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
|
||||
# pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/** @brief Data collection with Sidre routines following the Conduit mesh
|
||||
blueprint specification. */
|
||||
/** SidreDataCollection provides an HDF5-based file format for visualization or
|
||||
restart capability. This functionality is aimed primarily at customers of
|
||||
LLNL's axom project that run problems at extreme scales.
|
||||
|
||||
For more information, see:
|
||||
- Sidre component of LLNL's axom project (to be open-sourced), http://goo.gl/cZyJdn
|
||||
- LLNL conduit/blueprint library, https://github.com/LLNL/conduit
|
||||
- HDF5 library, https://support.hdfgroup.org/HDF5
|
||||
|
||||
The layout created in the Sidre DataStore is: (`"──"` denote groups,
|
||||
`"─•"` denote views, `"─>"` denote links, i.e. shallow-copy view)
|
||||
|
||||
<root>
|
||||
├── <collection-name>_global (global group)
|
||||
│ └── blueprint_index
|
||||
│ └── <collection-name> (bp_index group)
|
||||
│ ├── state
|
||||
│ │ ├─• cycle
|
||||
│ │ ├─• time
|
||||
│ │ └─• number_of_domains = <mesh-mpi-comm-size>
|
||||
│ ├── coordsets
|
||||
│ │ └── coords
|
||||
│ │ ├─• path = "<bp-path>/coordsets/coords"
|
||||
│ │ ├─• type ─> <bp-grp>/coordsets/coords/type = "explicit"
|
||||
│ │ └─• coord_system = "x"|"xy"|"xyz"
|
||||
│ ├── topologies
|
||||
│ │ ├── mesh
|
||||
│ │ │ ├─• path = "<bp-path>/topologies/mesh"
|
||||
│ │ │ ├─• type ─> <bp-grp>/topologies/mesh/type = "unstructured"
|
||||
│ │ │ ├─• coordset ─> <bp-grp>/topologies/mesh/coordset = "coords"
|
||||
│ │ │ ├─• grid_function ─> <bp-grp>/topologies/mesh/grid_function = "<nodes-field-name>"
|
||||
│ │ │ └─• boundary_topology ─> <bp-grp>/topologies/mesh/boundary_topology = "boundary"
|
||||
│ │ └── boundary
|
||||
│ │ ├─• path = "<bp-path>/topologies/mesh"
|
||||
│ │ ├─• type ─> <bp-grp>/topologies/boundary/type = "unstructured"
|
||||
│ │ └─• coordset ─> <bp-grp>/topologies/boundary/coordset = "coords"
|
||||
│ └── fields
|
||||
│ ├── mesh_material_attribute
|
||||
│ │ ├─• path = "<bp-path>/fields/mesh_material_attribute"
|
||||
│ │ ├─• association ─> <bp-grp>/fields/mesh_material_attribute/association = "element"
|
||||
│ │ ├─• topology ─> <bp-grp>/fields/mesh_material_attribute/topology = "mesh"
|
||||
│ │ └─• number_of_components = 1
|
||||
│ ├── boundary_material_attribute
|
||||
│ │ ├─• path = "<bp-path>/fields/boundary_material_attribute"
|
||||
│ │ ├─• association ─> <bp-grp>/fields/boundary_material_attribute/association = "element"
|
||||
│ │ ├─• topology ─> <bp-grp>/fields/boundary_material_attribute/topology = "boundary"
|
||||
│ │ └─• number_of_components = 1
|
||||
│ ├── grid-function-1
|
||||
│ │ ├─• path = "<bp-path>/fields/grid-function-1"
|
||||
│ │ ├─• basis ─> <bp-grp>/fields/grid-function-1/basis = "<fe-coll-name>"
|
||||
│ │ ├─• topology ─> <bp-grp>/fields/grid-function-1/topology = "mesh"
|
||||
│ │ └─• number_of_components = gf1->VectorDim()
|
||||
│ ├── grid-function-2
|
||||
│ │ ├─• path = "<bp-path>/fields/grid-function-2"
|
||||
│ │ ├─• basis ─> <bp-grp>/fields/grid-function-2/basis = "<fe-coll-name>"
|
||||
│ │ ├─• topology ─> <bp-grp>/fields/grid-function-2/topology = "mesh"
|
||||
│ │ └─• number_of_components = gf2->VectorDim()
|
||||
│ ├── ...
|
||||
│ ...
|
||||
└── <collection-name> (domain group)
|
||||
├── blueprint (blueprint group)
|
||||
│ ├── state
|
||||
│ │ ├─• cycle
|
||||
│ │ ├─• time
|
||||
│ │ ├─• domain = <mesh-mpi-rank>
|
||||
│ │ └─• time_step
|
||||
│ ├── coordsets
|
||||
│ │ └── coords
|
||||
│ │ ├─• type = "explicit"
|
||||
│ │ └── values
|
||||
│ │ ├─• x = view in <vertex-coords-buffer>/<ext-double-data>
|
||||
│ │ ├─• y = view in <vertex-coords-buffer>/<ext-double-data>
|
||||
│ │ └─• z = view in <vertex-coords-buffer>/<ext-double-data>
|
||||
│ ├── topologies
|
||||
│ │ ├── mesh
|
||||
│ │ │ ├─• type = "unstructured"
|
||||
│ │ │ ├── elements
|
||||
│ │ │ │ ├─• shape = "points"|"lines"|...
|
||||
│ │ │ │ └─• connectivity = <vert-idx-array>
|
||||
│ │ │ ├─• coordset = "coords"
|
||||
│ │ │ ├─• grid_function = "<nodes-field-name>"
|
||||
│ │ │ └─• boundary_topology = "boundary"
|
||||
│ │ └── boundary
|
||||
│ │ ├─• type = "unstructured"
|
||||
│ │ ├── elements
|
||||
│ │ │ ├─• shape = "points"|"lines"|...
|
||||
│ │ │ └─• connectivity = <vert-idx-array>
|
||||
│ │ └─• coordset = "coords"
|
||||
│ └── fields
|
||||
│ ├── mesh_material_attribute
|
||||
│ │ ├─• association = "element"
|
||||
│ │ ├─• topology = "mesh"
|
||||
│ │ └─• values = <attr-array>
|
||||
│ ├── boundary_material_attribute
|
||||
│ │ ├─• association = "element"
|
||||
│ │ ├─• topology = "boundary"
|
||||
│ │ └─• values = <attr-array>
|
||||
│ ├── grid-function-1 (name can include path)
|
||||
│ │ ├─• basis = "<fe-coll-name>"
|
||||
│ │ ├─• topology = "mesh"
|
||||
│ │ └─• values = <ext-double-array>/<named-buffer> (vdim == 1)
|
||||
│ ├── grid-function-2 (name can include path)
|
||||
│ │ ├─• basis = "<fe-coll-name>"
|
||||
│ │ ├─• topology = "mesh"
|
||||
│ │ └── values (vdim > 1)
|
||||
│ │ ├─• x0 = view into <ext-double-array>/<named-buffer>
|
||||
│ │ ├─• x1 = view into <ext-double-array>/<named-buffer>
|
||||
│ │ └─• x2 = view into <ext-double-array>/<named-buffer>
|
||||
│ ├── ...
|
||||
│ ...
|
||||
└── named_buffers (named_buffers group)
|
||||
├─• vertex_coords = <double-array>
|
||||
├─• grid-function-1 = <double-array>
|
||||
├─• grid-function-2 = <double-array>
|
||||
...
|
||||
|
||||
@note blueprint_index is used both in serial and in parallel. In parallel,
|
||||
only rank 0 will add entries to the blueprint index.
|
||||
|
||||
@note QuadratureFunction%s (q-fields) are not supported.
|
||||
|
||||
@note SidreDataCollection does not manage the FiniteElementSpace%s and
|
||||
FiniteElementCollection%s associated with registered GridFunction%s.
|
||||
Therefore, field registration is left to the user of SidreDataCollection and
|
||||
there are no methods that automatically register GridFunction%s using just
|
||||
the content of the Sidre DataStore. Such capabilities can be implemented in
|
||||
a derived class, adding any desired object management routines.
|
||||
|
||||
@warning This class is still _experimental_, meaning that in future
|
||||
releases, it may not be backward compatible, and the output files generated
|
||||
by the current version may become unreadable.
|
||||
*/
|
||||
class SidreDataCollection : public DataCollection
|
||||
{
|
||||
public:
|
||||
typedef NamedFieldsMap< Array<int> > AttributeFieldMap;
|
||||
AttributeFieldMap attr_map;
|
||||
|
||||
public:
|
||||
|
||||
/// Constructor that allocates and initializes a Sidre DataStore.
|
||||
/**
|
||||
@param[in] collection_name Name of the collection used as a file name
|
||||
when saving
|
||||
@param[in] the_mesh Mesh shared by all grid functions in the
|
||||
collection (can be NULL)
|
||||
@param[in] owns_mesh_data Does the SidreDC own the mesh vertices?
|
||||
|
||||
With this constructor, the SidreDataCollection owns the allocated Sidre
|
||||
DataStore.
|
||||
*/
|
||||
SidreDataCollection(const std::string& collection_name,
|
||||
Mesh *the_mesh = NULL,
|
||||
bool owns_mesh_data = false);
|
||||
|
||||
/// Constructor that links to an external Sidre DataStore.
|
||||
/** Specifically, the global and domain groups can be at arbitrary paths.
|
||||
|
||||
@param[in] collection_name Name of the collection used as a file name
|
||||
when saving
|
||||
@param[in] bp_index_grp Pointer to the blueprint index group in the
|
||||
datastore, see the above schematic
|
||||
@param[in] domain_grp Pointer to the domain group in the datastore,
|
||||
see the above schematic
|
||||
@param[in] owns_mesh_data Does the SidreDC own the mesh vertices?
|
||||
|
||||
With this constructor, the SidreDataCollection does not own the Sidre
|
||||
DataStore.
|
||||
@note No mesh or fields are read from the given Groups. The mesh has
|
||||
to be set with SetMesh() and fields registered with RegisterField().
|
||||
*/
|
||||
SidreDataCollection(const std::string& collection_name,
|
||||
axom::sidre::Group * bp_index_grp,
|
||||
axom::sidre::Group * domain_grp,
|
||||
bool owns_mesh_data = false);
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
/// Associate an MPI communicator with the collection.
|
||||
/** If no mesh was associated with the collection, this method should be
|
||||
called before using any of the Load() methods to read parallel data. */
|
||||
void SetComm(MPI_Comm comm);
|
||||
#endif
|
||||
|
||||
/// Register a GridFunction in the Sidre DataStore.
|
||||
/** This method is a shortcut for the call
|
||||
`RegisterField(field_name, gf, field_name, 0)`.
|
||||
*/
|
||||
virtual void RegisterField(const std::string &field_name, GridFunction *gf)
|
||||
{
|
||||
RegisterField(field_name, gf, field_name, 0);
|
||||
}
|
||||
|
||||
/// Register a GridFunction in the Sidre DataStore.
|
||||
/** The registration procedure is as follows:
|
||||
- if (@a gf's data is NULL), allocate named buffer with the name
|
||||
@a buffer_name with size _offset + gf->FESpace()->GetVSize()_ and use
|
||||
its data (plus the given @a offset) to set @a gf's data;
|
||||
- else, if (DataStore has a named buffer @a buffer_name), replace @a gf's
|
||||
data array with that named buffer plus the given @a offset;
|
||||
- else, use @a gf's data as external data associated with @a field_name
|
||||
in the DataStore;
|
||||
- register @a field_name in #field_map.
|
||||
|
||||
Both the @a field_name and @a buffer_name can contain a path prefix.
|
||||
@note If @a field_name or @a buffer_name is empty, the method does
|
||||
nothing.
|
||||
@note If the GridFunction pointer @a gf or it's FiniteElementSpace
|
||||
pointer are NULL, the method does nothing.
|
||||
*/
|
||||
void RegisterField(const std::string &field_name, GridFunction *gf,
|
||||
const std::string &buffer_name,
|
||||
axom::sidre::IndexType offset);
|
||||
|
||||
/// Registers an attribute field in the Sidre DataStore
|
||||
/** The registration process is similar to that of RegisterField()
|
||||
The attribute field is associated with the elements of the mesh
|
||||
when @a is_bdry is false, and with the boundary elements, when
|
||||
@a is_bdry is true.
|
||||
@sa RegisterField() */
|
||||
void RegisterAttributeField(const std::string& name, bool is_bdry);
|
||||
void DeregisterAttributeField(const std::string& name);
|
||||
|
||||
/** Returns a pointer to the attribute field associated with
|
||||
@a field_name, or NULL when there is no associated field */
|
||||
Array<int>* GetAttributeField(const std::string& field_name) const
|
||||
{ return attr_map.Get(field_name); }
|
||||
|
||||
/** Checks if there is an attribute field associated with @a field_name */
|
||||
bool HasAttributeField(const std::string& field_name) const
|
||||
{ return attr_map.Has(field_name); }
|
||||
|
||||
/** Checks if any rank in the mesh has boundary elements */
|
||||
bool HasBoundaryMesh() const;
|
||||
|
||||
/// Set the name of the mesh nodes field.
|
||||
/** This name will be used by SetMesh() to register the mesh nodes, if not
|
||||
already registered. Also, this method should be called if the mesh nodes
|
||||
GridFunction was or will be registered directly by the user. The default
|
||||
value for the name is "mesh_nodes". */
|
||||
void SetMeshNodesName(const std::string &nodes_name)
|
||||
{
|
||||
if (!nodes_name.empty()) { m_meshNodesGFName = nodes_name; }
|
||||
}
|
||||
|
||||
/// De-register @a field_name from the SidreDataCollection.
|
||||
/** The field is removed from the #field_map and the DataStore, including
|
||||
deleting it from the named_buffers group, if allocated. */
|
||||
virtual void DeregisterField(const std::string& field_name);
|
||||
|
||||
/// Delete all owned data.
|
||||
virtual ~SidreDataCollection();
|
||||
|
||||
/// Set/change the mesh associated with the collection
|
||||
/** Uses the field name "mesh_nodes" or the value set by SetMeshNodesName()
|
||||
to register the mesh nodes GridFunction, if the mesh uses nodes. */
|
||||
virtual void SetMesh(Mesh *new_mesh);
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
/// Set/change the mesh associated with the collection
|
||||
/** Uses the field name "mesh_nodes" or the value set by SetMeshNodesName()
|
||||
to register the mesh nodes GridFunction, if the mesh uses nodes. */
|
||||
virtual void SetMesh(MPI_Comm comm, Mesh *new_mesh);
|
||||
#endif
|
||||
|
||||
/// Reset the domain and global datastore group pointers.
|
||||
/** These are set in the constructor, but if a host code changes the
|
||||
datastore contents ( such as wiping out the datastore and loading in new
|
||||
contents from a file, i.e. a restart ) these pointers will need to be
|
||||
reset to valid groups in the datastore.
|
||||
@sa Load(const std::string &path, const std::string &protocol).
|
||||
*/
|
||||
void SetGroupPointers(axom::sidre::Group * global_grp,
|
||||
axom::sidre::Group * domain_grp);
|
||||
|
||||
axom::sidre::Group * GetBPGroup() { return m_bp_grp; }
|
||||
axom::sidre::Group * GetBPIndexGroup() { return m_bp_index_grp; }
|
||||
|
||||
/// Prepare the DataStore for writing
|
||||
virtual void PrepareToSave();
|
||||
|
||||
/// Save the collection to file.
|
||||
/** This method calls `Save(collection_name, "sidre_hdf5")`. */
|
||||
virtual void Save();
|
||||
|
||||
/// Save the collection to @a filename.
|
||||
/** The collection path prefix is prepended to the @a filename and the
|
||||
current cycle is appended, if cycle >= 0. */
|
||||
void Save(const std::string& filename, const std::string& protocol);
|
||||
|
||||
/// Load the Sidre DataStore from file.
|
||||
/** No mesh or fields are read from the loaded DataStore.
|
||||
|
||||
If the data collection created the datastore, it knows the layout of
|
||||
where the domain and global groups are, and can restore them after the
|
||||
Load().
|
||||
|
||||
If, however, the data collection does not own the datastore (e.g. it did
|
||||
not create the datastore), the host code must reset these pointers after
|
||||
the load operation, using SetGroupPointers(), and also reset the state
|
||||
variables, using UpdateStateFromDS().
|
||||
*/
|
||||
void Load(const std::string& path, const std::string& protocol);
|
||||
|
||||
/// Load SidreDataCollection from file.
|
||||
/** The used file path is based on the current prefix path, collection name,
|
||||
and the given @a cycle_. The protocol is "sidre_hdf5".
|
||||
@sa Load(const std::string &path, const std::string &protocol).
|
||||
*/
|
||||
virtual void Load(int cycle_ = 0)
|
||||
{
|
||||
SetCycle(cycle_);
|
||||
Load(get_file_path(name), "sidre_hdf5");
|
||||
}
|
||||
|
||||
/// Load external data after registering externally owned fields.
|
||||
void LoadExternalData(const std::string& path);
|
||||
|
||||
/** @brief Updates the DataCollection's cycle, time, and time-step variables
|
||||
with the values from the data store. */
|
||||
void UpdateStateFromDS();
|
||||
|
||||
/** @brief Updates the data store's cycle, time, and time-step variables with
|
||||
the values from the SidreDataCollection. */
|
||||
void UpdateStateToDS();
|
||||
|
||||
/** @name Methods for named buffer access and manipulation. */
|
||||
///@{
|
||||
|
||||
/** @brief Get a pointer to the sidre::View holding the named buffer for
|
||||
@a buffer_name. */
|
||||
/** If such named buffer is not allocated, the method returns NULL.
|
||||
@note To access the underlying pointer, use View::getData().
|
||||
@note To query the size of the buffer, use View::getNumElements().
|
||||
*/
|
||||
axom::sidre::View *
|
||||
GetNamedBuffer(const std::string& buffer_name) const
|
||||
{
|
||||
return named_buffers_grp()->hasView(buffer_name)
|
||||
? named_buffers_grp()->getView(buffer_name)
|
||||
: NULL;
|
||||
}
|
||||
|
||||
/// Return newly allocated or existing named buffer for @a buffer_name.
|
||||
/** The buffer is stored in the named_buffers group. If the currently
|
||||
allocated buffer size is smaller than @a sz, then the buffer is
|
||||
reallocated with size @a sz, destroying its contents.
|
||||
@note To access the underlying pointer, use View::getData().
|
||||
*/
|
||||
axom::sidre::View *
|
||||
AllocNamedBuffer(const std::string& buffer_name,
|
||||
axom::sidre::IndexType sz,
|
||||
axom::sidre::TypeID type =
|
||||
axom::sidre::DOUBLE_ID);
|
||||
|
||||
/// Deallocate the named buffer @a buffer_name.
|
||||
void FreeNamedBuffer(const std::string& buffer_name)
|
||||
{ named_buffers_grp()->destroyViewAndData(buffer_name); }
|
||||
|
||||
///@}
|
||||
|
||||
private:
|
||||
// Used if the Sidre data collection is providing the datastore itself.
|
||||
const bool m_owns_datastore;
|
||||
|
||||
// TODO - Need to evaluate if this bool member can be combined with own_data
|
||||
// in parent data collection class. m_owns_mesh_data indicates whether the
|
||||
// Sidre dc owns the mesh element data and node positions gf. The DC base
|
||||
// class own_data indicates if the dc owns the mesh object pointer itself and
|
||||
// GF objects. Can we use one flag and just have DC own all objects vs none?
|
||||
const bool m_owns_mesh_data;
|
||||
|
||||
// Name to be used for registering the mesh nodes in the SidreDataCollection.
|
||||
// This name is used by SetMesh() and can be overwritten by the method
|
||||
// SetMeshNodesName().
|
||||
// Default value: "mesh_nodes".
|
||||
std::string m_meshNodesGFName;
|
||||
|
||||
// If the data collection owns the datastore, it will store a pointer to it.
|
||||
// Otherwise, this pointer is NULL.
|
||||
axom::sidre::DataStore * m_datastore_ptr;
|
||||
|
||||
protected:
|
||||
axom::sidre::Group *named_buffers_grp() const;
|
||||
|
||||
axom::sidre::View *
|
||||
alloc_view(axom::sidre::Group *grp,
|
||||
const std::string &view_name);
|
||||
|
||||
axom::sidre::View *
|
||||
alloc_view(axom::sidre::Group *grp,
|
||||
const std::string &view_name,
|
||||
const axom::sidre::DataType &dtype);
|
||||
|
||||
axom::sidre::Group *
|
||||
alloc_group(axom::sidre::Group *grp,
|
||||
const std::string &group_name);
|
||||
|
||||
// return the filename based on prefix_path, collection name and cycle.
|
||||
std::string get_file_path(const std::string &filename) const;
|
||||
|
||||
private:
|
||||
// If the data collection does not own the datastore, it will need pointers
|
||||
// to the blueprint and blueprint index group to use.
|
||||
axom::sidre::Group * m_bp_grp;
|
||||
axom::sidre::Group * m_bp_index_grp;
|
||||
|
||||
// This is stored for convenience.
|
||||
axom::sidre::Group * m_named_bufs_grp;
|
||||
|
||||
// Private helper functions
|
||||
|
||||
void RegisterFieldInBPIndex(const std::string& field_name,
|
||||
GridFunction *gf);
|
||||
void DeregisterFieldInBPIndex(const std::string & field_name);
|
||||
|
||||
void RegisterAttributeFieldInBPIndex(const std::string& attr_name);
|
||||
void DeregisterAttributeFieldInBPIndex(const std::string& attr_name);
|
||||
|
||||
/** @brief Return a string with the conduit blueprint name for the given
|
||||
Element::Type. */
|
||||
std::string getElementName( Element::Type elementEnum );
|
||||
|
||||
/**
|
||||
* \brief A private helper function to set up the views associated with the
|
||||
data of a scalar valued grid function in the blueprint style.
|
||||
* \pre gf is not null
|
||||
* \note This function is expected to be called by RegisterField()
|
||||
* \note Handles cases where hierarchy is already set up,
|
||||
* where the data was allocated by this data collection
|
||||
* and where the grid function data is external to Sidre
|
||||
*/
|
||||
void addScalarBasedGridFunction(const std::string& field_name,
|
||||
GridFunction* gf,
|
||||
const std::string &buffer_name,
|
||||
axom::sidre::IndexType offset);
|
||||
|
||||
/**
|
||||
* \brief A private helper function to set up the views associated with the
|
||||
data of a vector valued grid function in the blueprint style.
|
||||
* \pre gf is not null
|
||||
* \note This function is expected to be called by RegisterField()
|
||||
* \note Handles cases where hierarchy is already set up,
|
||||
* where the data was allocated by this data collection
|
||||
* and where the grid function data is external to Sidre
|
||||
*/
|
||||
void addVectorBasedGridFunction(const std::string& field_name,
|
||||
GridFunction* gf,
|
||||
const std::string &buffer_name,
|
||||
axom::sidre::IndexType offset);
|
||||
|
||||
/** @brief A private helper function to set up the Views associated with
|
||||
attribute field named @a field_name */
|
||||
void addIntegerAttributeField(const std::string& field_name, bool is_bdry);
|
||||
|
||||
/// Sets up the four main mesh blueprint groups.
|
||||
/**
|
||||
* \param hasBP Indicates whether the blueprint has already been set up.
|
||||
*/
|
||||
void createMeshBlueprintStubs(bool hasBP);
|
||||
|
||||
/// Sets up the mesh blueprint 'state' group.
|
||||
/**
|
||||
* \param hasBP Indicates whether the blueprint has already been set up.
|
||||
*/
|
||||
void createMeshBlueprintState(bool hasBP);
|
||||
|
||||
/// Sets up the mesh blueprint 'coordsets' group.
|
||||
/**
|
||||
* \param hasBP Indicates whether the blueprint has already been set up.
|
||||
*/
|
||||
void createMeshBlueprintCoordset(bool hasBP);
|
||||
|
||||
/// Sets up the mesh blueprint 'topologies' group.
|
||||
/**
|
||||
* This method is called from SetMesh().
|
||||
* \param hasBP Indicates whether the blueprint has already been set up.
|
||||
* \param mesh_name The name of the topology.
|
||||
* \note Valid values for @a mesh_name are "mesh" and "boundary" and the
|
||||
former has to be created with this method before the latter.
|
||||
*/
|
||||
void createMeshBlueprintTopologies(bool hasBP, const std::string& mesh_name);
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
/// Sets up the mesh blueprint 'adjacencies' group.
|
||||
/**
|
||||
* \param hasBP Indicates whether the blueprint has already been set up.
|
||||
* \note Only valid when using parallel meshes
|
||||
*/
|
||||
void createMeshBlueprintAdjacencies(bool hasBP);
|
||||
#endif
|
||||
|
||||
/// Verifies that the contents of the mesh blueprint data is valid.
|
||||
void verifyMeshBlueprint();
|
||||
};
|
||||
|
||||
} // end namespace mfem
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -278,9 +278,18 @@ void ArraysByName<T>::Load(std::istream &in)
|
||||
q1 = ArrayLine.find(' ');
|
||||
ArrayName = ArrayLine.substr(0,q1-1);
|
||||
}
|
||||
|
||||
// Ignore the remainder of the line which may contain explanatory comments
|
||||
data[ArrayName].Load(in, 0);
|
||||
if (q1+2 < ArrayLine.size())
|
||||
{
|
||||
// Read the remainder of the line which contains the array data
|
||||
std::istringstream ArrayDataStream(ArrayLine.substr(q1+2,
|
||||
ArrayLine.size()));
|
||||
data[ArrayName].Load(ArrayDataStream, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read the array data starting on the next line
|
||||
data[ArrayName].Load(in, 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -726,16 +726,16 @@ std::string Device::GetUUID(const int device_id)
|
||||
MFEM_GPU_CHECK(cudaGetDeviceProperties(&prop, device_id));
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
res << std::setfill('0') << std::setw(2) << std::hex
|
||||
<< static_cast<unsigned>(prop.uuid.bytes[i]);
|
||||
const unsigned b = static_cast<unsigned char>(prop.uuid.bytes[i]);
|
||||
res << std::setfill('0') << std::setw(2) << std::hex << b;
|
||||
}
|
||||
#elif defined(MFEM_USE_HIP)
|
||||
hipUUID uuid;
|
||||
MFEM_GPU_CHECK(hipDeviceGetUuid(&uuid, device_id));
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
res << std::setfill('0') << std::setw(2) << std::hex
|
||||
<< static_cast<unsigned>(uuid.bytes[i]);
|
||||
const unsigned b = static_cast<unsigned char>(uuid.bytes[i]);
|
||||
res << std::setfill('0') << std::setw(2) << std::hex << b;
|
||||
}
|
||||
#endif
|
||||
return res.str();
|
||||
|
||||
@@ -200,10 +200,6 @@ void OptionsParser::Parse()
|
||||
isValid = isValidAsInt(argv[i]);
|
||||
*(int *)(options[j].var_ptr) = atoi(argv[i++]);
|
||||
break;
|
||||
case LONG:
|
||||
isValid = isValidAsInt(argv[i]);
|
||||
*(long long *)(options[j].var_ptr) = atoi(argv[i++]);
|
||||
break;
|
||||
case DOUBLE:
|
||||
isValid = isValidAsDouble(argv[i]);
|
||||
*(real_t *)(options[j].var_ptr) = atof(argv[i++]);
|
||||
@@ -282,10 +278,6 @@ void OptionsParser::WriteValue(const Option &opt, std::ostream &os)
|
||||
case INT:
|
||||
os << *(int *)(opt.var_ptr);
|
||||
break;
|
||||
|
||||
case LONG:
|
||||
os << *(long long *)(opt.var_ptr);
|
||||
break;
|
||||
|
||||
case DOUBLE:
|
||||
os << *(real_t *)(opt.var_ptr);
|
||||
|
||||
@@ -31,7 +31,7 @@ class Vector;
|
||||
class OptionsParser
|
||||
{
|
||||
public:
|
||||
enum OptionType { INT, DOUBLE, STRING, STD_STRING, ENABLE, DISABLE, ARRAY, VECTOR , LONG };
|
||||
enum OptionType { INT, DOUBLE, STRING, STD_STRING, ENABLE, DISABLE, ARRAY, VECTOR };
|
||||
|
||||
private:
|
||||
struct Option
|
||||
@@ -98,14 +98,6 @@ 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)
|
||||
|
||||
@@ -160,9 +160,6 @@ const char *GetConfigStr()
|
||||
#ifdef MFEM_USE_RAJA
|
||||
"MFEM_USE_RAJA\n"
|
||||
#endif
|
||||
#ifdef MFEM_USE_SIDRE
|
||||
"MFEM_USE_SIDRE\n"
|
||||
#endif
|
||||
#ifdef MFEM_USE_SIMD
|
||||
"MFEM_USE_SIMD\n"
|
||||
#endif
|
||||
|
||||
@@ -317,6 +317,9 @@ void HypreParVector::WrapHypreParVector(hypre_ParVector *y, bool owner)
|
||||
|
||||
Vector * HypreParVector::GlobalVector() const
|
||||
{
|
||||
MFEM_VERIFY(size > 0,
|
||||
"GlobalVector method can only be called on vectors wherein each "
|
||||
"process owns one or more entries");
|
||||
hypre_Vector *hv = hypre_ParVectorToVectorAll(*this);
|
||||
Vector *v = new Vector(hv->data, internal::to_int(hv->size));
|
||||
v->MakeDataOwner();
|
||||
|
||||
+1
-62
@@ -284,7 +284,7 @@ void RK4Solver::Init(TimeDependentOperator &f_)
|
||||
int n = f->Width();
|
||||
y.SetSize(n, mem_type);
|
||||
k.SetSize(n, mem_type);
|
||||
z.SetSize(n, mem_type);
|
||||
z.SetSize(n, mem_type);
|
||||
}
|
||||
|
||||
void RK4Solver::Step(Vector &x, real_t &t, real_t &dt)
|
||||
@@ -316,67 +316,6 @@ void RK4Solver::Step(Vector &x, real_t &t, real_t &dt)
|
||||
t += dt;
|
||||
}
|
||||
|
||||
void RK4Solver::SetSolution(const Vector& x, real_t ts)
|
||||
{
|
||||
hist.clear(); //we need only the last time step at time ts
|
||||
StepRecord rec{ts,x};
|
||||
hist.push_back(rec);
|
||||
}
|
||||
|
||||
void RK4Solver::AdjointStep(Vector &lambda, real_t &t, real_t &dt)
|
||||
{
|
||||
MFEM_VERIFY(!hist.empty(), "No forward history.");
|
||||
MFEM_VERIFY(adj_mode == AdjointMode::Discrete, "Wrong adjoint mode.");
|
||||
|
||||
//Restore the solution history, i.e., perform a forward step.
|
||||
auto rec = hist.back();
|
||||
hist.pop_back();
|
||||
const real_t tn = rec.t;
|
||||
const real_t h = dt;
|
||||
const Vector &xn = rec.x;
|
||||
// Recompute forward stage states and stage RHS:
|
||||
f->SetTime(tn);
|
||||
f->Mult(xn, k1); // k1
|
||||
add(xn, h/2.0, k1, y2); // y2
|
||||
|
||||
f->SetTime(tn+h/2.0);
|
||||
f->Mult(y2, k); // k2
|
||||
add(xn, h/2.0, k, y3); // y3
|
||||
|
||||
f->Mult(y3, k); // k3
|
||||
add(xn, h, k, y4); // y4
|
||||
|
||||
|
||||
//set the initial values for a1,a2,a3,a4
|
||||
a1.Set(h/6.0,lambda);
|
||||
a2.Set(h/3.0,lambda);
|
||||
a3.Set(h/3.0,lambda);
|
||||
a4.Set(h/6.0,lambda);
|
||||
|
||||
//stage 4
|
||||
f->SetTime(tn+h);
|
||||
f->JacobianMultTranspose(y4, a4, k);
|
||||
lambda.Add(1.0,k);
|
||||
a3.Add(h,k);
|
||||
|
||||
//stage 3
|
||||
f->SetTime(tn+h/2.0);
|
||||
f->JacobianMultTranspose(y3, a3, k);
|
||||
lambda.Add(1.0,k);
|
||||
a2.Add(h/2.0,k);
|
||||
|
||||
//stage 2
|
||||
f->SetTime(tn+h/2.0);
|
||||
f->JacobianMultTranspose(y2, a2, k);
|
||||
lambda.Add(1.0,k);
|
||||
a1.Add(h/2.0,k);
|
||||
|
||||
//stage 1
|
||||
f->SetTime(tn);
|
||||
f->JacobianMultTranspose(xn, a1, k);
|
||||
lambda.Add(1.0,k);
|
||||
}
|
||||
|
||||
ExplicitRKSolver::ExplicitRKSolver(int s_, const real_t *a_, const real_t *b_,
|
||||
const real_t *c_)
|
||||
{
|
||||
|
||||
-135
@@ -190,78 +190,6 @@ public:
|
||||
while (t < tf) { Step(x, t, dt); }
|
||||
}
|
||||
|
||||
/// @brief The enum defines the supported adjoint modes.
|
||||
enum class AdjointMode { None, Continuous, Discrete };
|
||||
|
||||
AdjointMode adj_mode= AdjointMode::None;
|
||||
|
||||
/// Sets the solution at time ts. The solution will be used by the adjoint step
|
||||
/// to recompute the internal stages/steps between ts and the time t used in the
|
||||
/// AdjointStep operator.
|
||||
/// SetSolution(x,ts); and then calling AdjointStep(lambda, t, dt), will
|
||||
/// first restore the solution/parametrization between ts and t (if required by
|
||||
/// the time integration scheme) and then execute the adjoint step.
|
||||
virtual void SetSolution(const Vector& x, real_t ts){}
|
||||
|
||||
|
||||
/// Return primal state x(t) for t in the recorded time interval.
|
||||
/// Default false => solver does not provide primal interpolation access.
|
||||
virtual bool GetForwardSolution(real_t t, Vector &x) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Perform one backward step for the adjoint variable lambda.
|
||||
/// dt > 0 requests stepping from t to t - dt.
|
||||
/// The solver may adjust dt (adaptive / grid snapping) and returns dt used.
|
||||
/** @param[in,out] lambda Approximate solution.
|
||||
@param[in,out] t Time associated with the approximate solution @a x.
|
||||
@param[in,out] dt Time step size.
|
||||
*/
|
||||
virtual void AdjointStep(Vector &lambda, real_t &t, real_t &dt)
|
||||
{
|
||||
MFEM_ABORT("Adjoint stepping is not supported by this ODESolver.");
|
||||
}
|
||||
|
||||
/// Perform adjoint integration from time @a t [in] to time @a tf [in].
|
||||
/** @param[in,out] lambda Approximate adjoint solution.
|
||||
@param[in,out] t Time associated with the approximate solution @a lambda.
|
||||
@param[in,out] dt Time step size.
|
||||
@param[in] t0 Requested final time. (t0<t)
|
||||
*/
|
||||
virtual void AdjointRun(Vector &lambda, real_t &t, real_t &dt, real_t t0)
|
||||
{
|
||||
MFEM_VERIFY(t0 <= t, "AdjointRun expects t0 <= t.");
|
||||
while (t > t0) {
|
||||
real_t d = std::min(dt, t - t0);
|
||||
AdjointStep(lambda, t, d);
|
||||
dt = d;
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable adjoint functionality and tell the solver
|
||||
/// what to record during forward stepping.
|
||||
/// Default: abort (adjoint mode is not supported).
|
||||
virtual void EnableAdjoint(AdjointMode mod)
|
||||
{
|
||||
if(AdjointMode::None!=mod)
|
||||
{
|
||||
MFEM_ABORT("Adjoint is not supported by the ODESolver.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true of the ODESolver supports the AdjointMode
|
||||
/// and false if not.
|
||||
virtual bool SupportsAdjoint(AdjointMode mod) const
|
||||
{
|
||||
if(AdjointMode::None==mod)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
//else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns how many State vectors the ODE requires
|
||||
virtual int GetStateSize() { return 0; };
|
||||
|
||||
@@ -381,73 +309,10 @@ class RK4Solver : public ODESolver
|
||||
private:
|
||||
Vector y, k, z;
|
||||
|
||||
//storage for the adjoint solver
|
||||
/// @brief stores the state x at time t
|
||||
struct StepRecord{
|
||||
real_t t;
|
||||
Vector x;
|
||||
};
|
||||
|
||||
/// @brief stores several steps
|
||||
std::vector<StepRecord> hist;
|
||||
|
||||
Vector k1,y2,y3,y4; //RK4 stages
|
||||
Vector a1,a2,a3,a4; //adjoint vectors
|
||||
|
||||
public:
|
||||
void Init(TimeDependentOperator &f_) override;
|
||||
|
||||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||||
|
||||
/// Return true of the ODESolver supports the AdjointMode and false
|
||||
/// if not.
|
||||
virtual bool SupportsAdjoint(AdjointMode mod) const override
|
||||
{
|
||||
if(AdjointMode::Discrete==mod)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
//else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Enable adjoint functionality and tell the solver
|
||||
/// what to record during forward stepping.
|
||||
/// Default: abort (adjoint mode is not supported).
|
||||
virtual void EnableAdjoint(AdjointMode mod) override
|
||||
{
|
||||
if(AdjointMode::Continuous==mod)
|
||||
{
|
||||
MFEM_ABORT("Continuous adjoint not supported by the RK4Solver.");
|
||||
}
|
||||
adj_mode=mod;
|
||||
|
||||
// allocate the storage memeory
|
||||
if(AdjointMode::Discrete==mod){
|
||||
int n = f->Width();
|
||||
k1.SetSize(n, mem_type);
|
||||
y2.SetSize(n, mem_type);
|
||||
y3.SetSize(n, mem_type);
|
||||
y4.SetSize(n, mem_type);
|
||||
|
||||
a1.SetSize(n, mem_type);
|
||||
a2.SetSize(n, mem_type);
|
||||
a3.SetSize(n, mem_type);
|
||||
a4.SetSize(n, mem_type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AdjointStep(Vector &lam, real_t &t, real_t &dt) override;
|
||||
|
||||
//Two posible ways to restore the histroy
|
||||
//1) Use Step(Vector &x, real_t &t, real_t &dt) in discrete adjoint mode and record the internal states
|
||||
//2) Use additional Method SetState(const Vector &x, real_t ts);
|
||||
// Calling AdjointStep(lam,t,dt) should restore the history between ts and t, and then execute the
|
||||
// adjoint step in reverse to time t-dt with the recorded discrete states between ts and t.
|
||||
// Here we will use SetSolution
|
||||
virtual void SetSolution(const Vector& x, real_t ts) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -640,15 +640,6 @@ public:
|
||||
virtual int SUNMassMult(const Vector &x, Vector &v);
|
||||
|
||||
virtual ~TimeDependentOperator() { }
|
||||
|
||||
/// y = (df/dx(x,t))^T * w
|
||||
/// Default uses GetGradient(x).MultTranspose(w,y).
|
||||
virtual void JacobianMultTranspose(const Vector &x,
|
||||
const Vector &w,
|
||||
Vector &y) const
|
||||
{
|
||||
GetGradient(x).MultTranspose(w, y);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
+17
-6
@@ -4156,20 +4156,31 @@ void PetscNonlinearSolver::SetUpdate(void (*update)(Operator *,int,
|
||||
void PetscNonlinearSolver::Mult(const Vector &b, Vector &x) const
|
||||
{
|
||||
SNES snes = (SNES)obj;
|
||||
MPI_Comm comm = PetscObjectComm(obj);
|
||||
|
||||
bool b_nonempty = b.Size();
|
||||
if (!B) { B = new PetscParVector(PetscObjectComm(obj), *this, true); }
|
||||
if (!X) { X = new PetscParVector(PetscObjectComm(obj), *this, false, false); }
|
||||
// Reduction needed: some processes may have null local size while others don't,
|
||||
// and VecPlaceArray (used by PlaceMemory) is a logically collective operation.
|
||||
PetscBool b_nonempty = b.Size() ? PETSC_TRUE : PETSC_FALSE;
|
||||
#if PETSC_VERSION_LT(3,24,0)
|
||||
mpiierr = MPI_Allreduce(MPI_IN_PLACE,&b_nonempty,1,MPIU_BOOL,MPI_LOR,comm);
|
||||
#else
|
||||
mpiierr = MPI_Allreduce(MPI_IN_PLACE,&b_nonempty,1,MPI_C_BOOL,MPI_LOR,comm);
|
||||
#endif
|
||||
CCHKERRQ(comm,mpiierr);
|
||||
|
||||
// Always create B with allocate=false so that PlaceMemory can be called on
|
||||
// it regardless of whether b was empty on a previous call.
|
||||
if (!B) { B = new PetscParVector(comm, *this, true, false); }
|
||||
if (!X) { X = new PetscParVector(comm, *this, false, false); }
|
||||
X->PlaceMemory(x.GetMemory(),iterative_mode);
|
||||
if (b_nonempty) { B->PlaceMemory(b.GetMemory()); }
|
||||
else { *B = 0.0; }
|
||||
|
||||
Customize();
|
||||
|
||||
if (!iterative_mode) { *X = 0.; }
|
||||
|
||||
// Solve the system.
|
||||
ierr = SNESSolve(snes, B->x, X->x); PCHKERRQ(snes, ierr);
|
||||
// Solve the system. Pass nullptr for b when empty (PETSc treats it as zero RHS).
|
||||
ierr = SNESSolve(snes, b_nonempty ? B->x : nullptr, X->x); PCHKERRQ(snes, ierr);
|
||||
X->ResetMemory();
|
||||
if (b_nonempty) { B->ResetMemory(); }
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ ifeq ($(MFEM_USE_LEGACY_OPENMP),YES)
|
||||
endif
|
||||
|
||||
# List of MFEM dependencies, that require the *_LIB variable to be non-empty
|
||||
MFEM_REQ_LIB_DEPS = SUPERLU MUMPS METIS FMS CONDUIT SIDRE LAPACK SUNDIALS\
|
||||
MFEM_REQ_LIB_DEPS = SUPERLU MUMPS METIS FMS CONDUIT LAPACK SUNDIALS\
|
||||
SUITESPARSE STRUMPACK GINKGO GNUTLS HDF5 NETCDF SLEPC PETSC MPFR PUMI HIOP\
|
||||
GSLIB OCCA CEED RAJA UMPIRE MKL_CPARDISO MKL_PARDISO AMGX MAGMA CALIPER PARELAG\
|
||||
TRIBOL BENCHMARK MOONOLITH ALGOIM
|
||||
@@ -365,7 +365,7 @@ MFEM_DEFINES = MFEM_VERSION MFEM_VERSION_STRING MFEM_GIT_STRING MFEM_USE_MPI\
|
||||
MFEM_USE_LEGACY_OPENMP MFEM_USE_MEMALLOC MFEM_TIMER_TYPE MFEM_USE_SUNDIALS\
|
||||
MFEM_USE_SUITESPARSE MFEM_USE_GINKGO MFEM_USE_SUPERLU MFEM_USE_SUPERLU5\
|
||||
MFEM_USE_STRUMPACK MFEM_USE_GNUTLS MFEM_USE_HDF5 MFEM_USE_NETCDF MFEM_USE_PETSC\
|
||||
MFEM_USE_SLEPC MFEM_USE_MPFR MFEM_USE_SIDRE MFEM_USE_FMS MFEM_USE_CONDUIT\
|
||||
MFEM_USE_SLEPC MFEM_USE_MPFR MFEM_USE_FMS MFEM_USE_CONDUIT\
|
||||
MFEM_USE_PUMI MFEM_USE_HIOP MFEM_USE_GSLIB MFEM_USE_CUDA MFEM_USE_HIP\
|
||||
MFEM_USE_OCCA MFEM_USE_MOONOLITH MFEM_USE_CEED MFEM_USE_RAJA MFEM_USE_UMPIRE\
|
||||
MFEM_USE_SIMD MFEM_USE_ADIOS2 MFEM_USE_MKL_CPARDISO MFEM_USE_MKL_PARDISO MFEM_USE_AMGX\
|
||||
@@ -746,7 +746,6 @@ status info:
|
||||
$(info MFEM_USE_PETSC = $(MFEM_USE_PETSC))
|
||||
$(info MFEM_USE_SLEPC = $(MFEM_USE_SLEPC))
|
||||
$(info MFEM_USE_MPFR = $(MFEM_USE_MPFR))
|
||||
$(info MFEM_USE_SIDRE = $(MFEM_USE_SIDRE))
|
||||
$(info MFEM_USE_FMS = $(MFEM_USE_FMS))
|
||||
$(info MFEM_USE_CONDUIT = $(MFEM_USE_CONDUIT))
|
||||
$(info MFEM_USE_PUMI = $(MFEM_USE_PUMI))
|
||||
|
||||
@@ -3206,10 +3206,22 @@ public:
|
||||
|
||||
|
||||
/// Extrude a 1D mesh
|
||||
/**
|
||||
* @param mesh 1D mesh
|
||||
* @param ny number of transverse elements of the extruded mesh
|
||||
* @param sy physical size in the direction of extrusion
|
||||
* @param closed if false, only the original boundaries are extruded,
|
||||
* otherwise boundaries are generated all around the domain
|
||||
*/
|
||||
Mesh *Extrude1D(Mesh *mesh, const int ny, const real_t sy,
|
||||
const bool closed = false);
|
||||
|
||||
/// Extrude a 2D mesh
|
||||
/**
|
||||
* @param mesh 2D mesh
|
||||
* @param nz number of transverse elements of the extruded mesh
|
||||
* @param sz physical size in the direction of extrusion
|
||||
*/
|
||||
Mesh *Extrude2D(Mesh *mesh, const int nz, const real_t sz);
|
||||
|
||||
/** @brief Constructs the smallest possible [0,1]^dim serial mesh that can be
|
||||
|
||||
@@ -1516,12 +1516,15 @@ void Mesh::ReadInlineMesh(std::istream &input, bool generate_edges)
|
||||
void Mesh::ReadGmshMesh(std::istream &input, int &curved, int &read_gf)
|
||||
{
|
||||
string buff;
|
||||
real_t version;
|
||||
string version;
|
||||
int binary, dsize;
|
||||
input >> version >> binary >> dsize;
|
||||
if (version < 2.2)
|
||||
if (version != "2.2")
|
||||
{
|
||||
MFEM_ABORT("Gmsh file version < 2.2");
|
||||
MFEM_ABORT("Gmsh file version must be 2.2, found version "
|
||||
<< version << ".\n"
|
||||
"To convert your mesh to the required format, use:\n"
|
||||
" gmsh -format msh22 -save -o output.msh input.msh");
|
||||
}
|
||||
if (dsize != sizeof(double))
|
||||
{
|
||||
|
||||
@@ -5639,6 +5639,12 @@ Mesh ParMesh::GetSerialMesh(int save_rank) const
|
||||
}
|
||||
}
|
||||
|
||||
if (MyRank == save_rank)
|
||||
{
|
||||
attribute_sets.Copy(serialmesh.attribute_sets);
|
||||
bdr_attribute_sets.Copy(serialmesh.bdr_attribute_sets);
|
||||
}
|
||||
|
||||
MPI_Barrier(MyComm);
|
||||
return serialmesh;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@ add_subdirectory(hdiv-linear-solver)
|
||||
add_subdirectory(hooke)
|
||||
add_subdirectory(meshing)
|
||||
add_subdirectory(mtop)
|
||||
add_subdirectory(mtop/chpt)
|
||||
add_subdirectory(mtop/examples)
|
||||
add_subdirectory(multidomain)
|
||||
add_subdirectory(nurbs)
|
||||
add_subdirectory(parelag)
|
||||
|
||||
@@ -82,6 +82,43 @@ Although Tribol can be built automatically via **uberenv** and **Spack**,
|
||||
for this miniapp it is simpler to build **Axom** and **MFEM** manually and
|
||||
point Tribol to them. The steps are as follows:
|
||||
|
||||
### Using pre-built Tribol/Axom installs
|
||||
|
||||
If you already have compatible installs of Tribol and Axom, point MFEM to the install prefixes.
|
||||
|
||||
- Hypre install prefix: `<path/to/hypre>`
|
||||
- METIS install prefix: `<path/to/metis>`
|
||||
- Axom install prefix: `<path/to/axom>`
|
||||
- Tribol install prefix: `<path/to/tribol>`
|
||||
|
||||
**MFEM make build (configure):**
|
||||
```bash
|
||||
make config MFEM_USE_MPI=YES MFEM_USE_METIS=YES MFEM_USE_TRIBOL=YES \
|
||||
HYPRE_DIR=<path/to/hypre> METIS_DIR=<path/to/metis> \
|
||||
AXOM_DIR=<path/to/axom> TRIBOL_DIR=<path/to/tribol> ADIAK_DIR=<path/to/adiak> CAMP_DIR=<path/to/camp> RAJA_DIR=<path/to/raja> \
|
||||
UMPIRE_DIR=<path/to/umpire> FMT_DIR=<path/to/fmt> CALIPER_DIR=<path/to/caliper>
|
||||
```
|
||||
|
||||
**MFEM CMake build (configure):**
|
||||
```bash
|
||||
cmake -S . -B <mfem-build-dir> -DMFEM_USE_MPI=YES -DMFEM_USE_METIS=YES -DMFEM_USE_TRIBOL=YES \
|
||||
HYPRE_DIR=<path/to/hypre> METIS_DIR=<path/to/metis> \
|
||||
AXOM_DIR=<path/to/axom> TRIBOL_DIR=<path/to/tribol> ADIAK_DIR=<path/to/adiak> CAMP_DIR=<path/to/camp> RAJA_DIR=<path/to/raja> \
|
||||
UMPIRE_DIR=<path/to/umpire> FMT_DIR=<path/to/fmt> CALIPER_DIR=<path/to/caliper>
|
||||
```
|
||||
|
||||
Note: RAJA/UMPIRE/CALIPER are optional for MFEM itself, but many Tribol builds
|
||||
enable them. If your Tribol install does not depend on them, you can omit the
|
||||
corresponding `*_DIR` entries above.
|
||||
|
||||
Note: `FMT_DIR` only needs to be added for the make-based build (and only when
|
||||
the Umpire install uses `fmt`). If `FMT_DIR` is not set and a sibling `fmt-*`
|
||||
directory exists next to your `UMPIRE_DIR`, MFEM's make configuration will try
|
||||
to pick it up automatically.
|
||||
|
||||
Note: when using pre-built Tribol/Axom, you typically need to use a compatible
|
||||
compiler/MPI wrapper (same C++ standard library ABI).
|
||||
|
||||
### Manual Build Steps
|
||||
|
||||
1. Pull axom and tribol (starting from the mfem folder):
|
||||
@@ -99,7 +136,7 @@ point Tribol to them. The steps are as follows:
|
||||
TRIBOL_DIR = @MFEM_DIR@/../tribol-repo/tribol
|
||||
TRIBOL_OPT = -I$(TRIBOL_DIR)/include -I$(AXOM_DIR)/include
|
||||
TRIBOL_LIB = -L$(TRIBOL_DIR)/lib -ltribol -lredecomp -L$(AXOM_DIR)/lib \
|
||||
-laxom_mint -laxom_slam -laxom_slic -laxom_core
|
||||
-laxom_quest -laxom_mint -laxom_slam -laxom_slic -laxom_lumberjack -laxom_core
|
||||
```
|
||||
3. [**Axom:**](https://github.com/LLNL/axom.git) Starting from the MFEM root
|
||||
directory (we assume this directory is named mfem):
|
||||
|
||||
@@ -26,10 +26,5 @@ if(MFEM_USE_MPI AND MFEM_USE_DOUBLE)
|
||||
MAIN mtop_test_iso_elasticity.cpp
|
||||
${PAR_MTOP_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
add_mfem_miniapp(tst_rk4_adj
|
||||
MAIN tst_rk4_adj.cpp
|
||||
${PAR_MTOP_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
|
||||
endif (MFEM_USE_MPI AND MFEM_USE_DOUBLE)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,65 +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.
|
||||
|
||||
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_test_scalar_chpt
|
||||
MAIN tst_scalar_field.cpp
|
||||
${CHPT_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(mtop_test_vector_chpt
|
||||
MAIN tst_vector_field.cpp
|
||||
${CHPT_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(mtop_test_fixedslot
|
||||
MAIN tst_fixedslot_scalar_vector.cpp
|
||||
${CHPT_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(mtop_test_segmentio
|
||||
MAIN tst_segmentio_scalar_vector_mfem.cpp
|
||||
${CHPT_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(mtop_test_file
|
||||
MAIN tst_fileio_scalar_vector_mfem.cpp
|
||||
${CHPT_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
|
||||
add_mfem_miniapp(mtop_test_revolve
|
||||
MAIN revolve.cpp
|
||||
${CHPT_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(mtop_test_dyn
|
||||
MAIN tst_dynamic_chpt.cpp
|
||||
${CHPT_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(mtop_test_rev_fixedslot
|
||||
MAIN tst_revolve_chpt_fixedslot.cpp
|
||||
${CHPT_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
#ifndef MFEM_DYNAMIC_CHECKPOINTING_HPP
|
||||
#define MFEM_DYNAMIC_CHECKPOINTING_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
#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();
|
||||
|
||||
if (slots_[id])
|
||||
{
|
||||
// reuse existing allocation
|
||||
*slots_[id] = std::move(snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
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 (Wang–Moin–Iaccarino 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> out;
|
||||
out.reserve(cps_.size());
|
||||
for (const auto &kv : cps_)
|
||||
{
|
||||
out.push_back({kv.first, kv.second.level, storage_->IsValid(kv.second.h)});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
#ifndef MFEM_FILE_CHECKPOINT_STORAGE_HPP
|
||||
#define MFEM_FILE_CHECKPOINT_STORAGE_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
#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.GetData();
|
||||
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.GetData();
|
||||
is.read(reinterpret_cast<char*>(data),
|
||||
(std::streamsize)(n * (std::int64_t)sizeof(mfem::real_t)));
|
||||
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, "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
|
||||
|
||||
@@ -1,385 +0,0 @@
|
||||
#ifndef MFEM_FIXED_SLOT_CHECKPOINT_STORAGE_HPP
|
||||
#define MFEM_FIXED_SLOT_CHECKPOINT_STORAGE_HPP
|
||||
|
||||
#include "mfem.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 &out) const
|
||||
{
|
||||
std::memcpy(&out, 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.GetData(), SlotBytes());
|
||||
}
|
||||
|
||||
void Unpack(const void *src, mfem::Vector &out) const
|
||||
{
|
||||
out.SetSize(n_);
|
||||
std::memcpy(out.GetData(), 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
|
||||
|
||||
@@ -1,694 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
using mfem::out;
|
||||
|
||||
// -------------------------------
|
||||
// Fixed-slot storage: Memory
|
||||
// -------------------------------
|
||||
class FixedSlotMemoryStorage
|
||||
{
|
||||
private:
|
||||
int max_slots_ = 0;
|
||||
size_t slot_bytes_ = 0;
|
||||
std::vector<uint8_t> data_;
|
||||
|
||||
public:
|
||||
FixedSlotMemoryStorage(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 FixedSlotFileStorage
|
||||
{
|
||||
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:
|
||||
FixedSlotFileStorage(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");
|
||||
}
|
||||
|
||||
~FixedSlotFileStorage()
|
||||
{
|
||||
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;
|
||||
int oldsnaps_ = 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), oldsnaps_(snaps), 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;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 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)
|
||||
{
|
||||
out << "\n--- Scalar REVOLVE example ---\n";
|
||||
out << "Nsteps=" << Nsteps << ", Ncheckpoints=" << Ncheck << ", factor=" << factor << "\n";
|
||||
|
||||
const size_t snap_bytes = sizeof(double);
|
||||
FixedSlotMemoryStorage storage(Ncheck, snap_bytes);
|
||||
FixedStepRevolveCheckpointing<FixedSlotMemoryStorage> 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);
|
||||
|
||||
out << "uN = " << uN << "\n";
|
||||
out << "lambda0 (computed) = " << lambda << "\n";
|
||||
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)
|
||||
{
|
||||
out << "\n--- mfem::Vector REVOLVE example ---\n";
|
||||
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);
|
||||
FixedSlotMemoryStorage storage(Ncheck, snap_bytes);
|
||||
FixedStepRevolveCheckpointing<FixedSlotMemoryStorage> 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);
|
||||
}
|
||||
|
||||
out << "||uN||_2 = " << uN.Norml2() << "\n";
|
||||
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(out);
|
||||
return 1;
|
||||
}
|
||||
args.PrintOptions(out);
|
||||
|
||||
RunScalarExample(Nsteps, Ncheck, factor);
|
||||
RunVectorExample(Nsteps, Ncheck, dim, factor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,535 +0,0 @@
|
||||
#ifndef MFEM_REVOLVE_CHECKPOINTING_HPP
|
||||
#define MFEM_REVOLVE_CHECKPOINTING_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
using mfem::out;
|
||||
|
||||
// -------------------------------
|
||||
// Fixed-slot storage: Memory
|
||||
// -------------------------------
|
||||
class FixedSlotMemoryStorage
|
||||
{
|
||||
private:
|
||||
int max_slots_ = 0;
|
||||
size_t slot_bytes_ = 0;
|
||||
std::vector<uint8_t> data_;
|
||||
|
||||
public:
|
||||
FixedSlotMemoryStorage(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 FixedSlotFileStorage
|
||||
{
|
||||
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:
|
||||
FixedSlotFileStorage(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");
|
||||
}
|
||||
|
||||
~FixedSlotFileStorage()
|
||||
{
|
||||
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;
|
||||
int oldsnaps_ = 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), oldsnaps_(snaps), 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
|
||||
@@ -1,357 +0,0 @@
|
||||
#ifndef MFEM_REVOLVE_SEPARATE_FILE_STORAGE_HPP
|
||||
#define MFEM_REVOLVE_SEPARATE_FILE_STORAGE_HPP
|
||||
|
||||
#include "mfem.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
|
||||
@@ -1,273 +0,0 @@
|
||||
#ifndef MFEM_SEGMENT_CHECKPOINT_STORAGE_HPP
|
||||
#define MFEM_SEGMENT_CHECKPOINT_STORAGE_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "file_checkpoint_storage.hpp" // reuses DefaultCheckpointBinaryIO
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <set>
|
||||
|
||||
#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 Segment/range file storage: one file per handle-id range + in-file offsets.
|
||||
*
|
||||
* Segment file = <dir>/<prefix><segment_id><ext>
|
||||
* Where segment_id = handle / records_per_file.
|
||||
*
|
||||
* Pros:
|
||||
* - Much fewer files than file-per-snapshot
|
||||
* - No per-snapshot file create/delete
|
||||
*
|
||||
* Cons:
|
||||
* - Append-only: Erase() does not reclaim file space (records remain)
|
||||
* - Index is in-memory (not restartable across processes unless you persist it)
|
||||
*/
|
||||
template <typename Snapshot,
|
||||
typename SnapshotIO = DefaultCheckpointBinaryIO<Snapshot> >
|
||||
class SegmentedFileCheckpointStorage
|
||||
{
|
||||
public:
|
||||
using Handle = std::int64_t;
|
||||
|
||||
SegmentedFileCheckpointStorage(const std::string &directory,
|
||||
std::int64_t records_per_file = 4096,
|
||||
const std::string &prefix = "seg_",
|
||||
const std::string &extension = ".bin",
|
||||
bool create_dir = true,
|
||||
bool keep_files = false)
|
||||
: dir_(directory),
|
||||
prefix_(prefix),
|
||||
ext_(extension),
|
||||
keep_files_(keep_files),
|
||||
records_per_file_(records_per_file)
|
||||
{
|
||||
MFEM_VERIFY(records_per_file_ > 0, "SegmentedFileCheckpointStorage: records_per_file must be > 0.");
|
||||
MFEM_VERIFY(!dir_.empty(), "SegmentedFileCheckpointStorage: empty directory.");
|
||||
|
||||
if (create_dir)
|
||||
{
|
||||
#if MFEM_HAVE_FILESYSTEM
|
||||
std::error_code ec;
|
||||
mfem_fs::create_directories(mfem_fs::path(dir_), ec);
|
||||
MFEM_VERIFY(!ec, "SegmentedFileCheckpointStorage: failed to create directory.");
|
||||
#else
|
||||
MFEM_ABORT("SegmentedFileCheckpointStorage: create_dir=true requires <filesystem> support.");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Handle InvalidHandle() const { return (Handle)-1; }
|
||||
bool IsValid(const Handle &h) const { return h >= 0; }
|
||||
|
||||
Handle Store(Snapshot &&snap)
|
||||
{
|
||||
const Handle id = AllocateId_();
|
||||
EnsureMetaSize_(id);
|
||||
|
||||
const std::int64_t seg = SegmentId_(id);
|
||||
const std::string path = SegmentPath_(seg);
|
||||
|
||||
const std::uint64_t offset = AppendRecord_(path, snap);
|
||||
const std::uint64_t bytes = last_payload_bytes_;
|
||||
|
||||
meta_[id].valid = true;
|
||||
meta_[id].seg_id = seg;
|
||||
meta_[id].offset = offset;
|
||||
meta_[id].bytes = bytes;
|
||||
|
||||
touched_segments_.insert(seg);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void Read(const Handle &h, Func &&f) const
|
||||
{
|
||||
MFEM_VERIFY(IsValid(h), "SegmentedFileCheckpointStorage: Read invalid handle.");
|
||||
MFEM_VERIFY(h < (Handle)meta_.size(), "SegmentedFileCheckpointStorage: Read out-of-range handle.");
|
||||
MFEM_VERIFY(meta_[h].valid, "SegmentedFileCheckpointStorage: Read on erased handle.");
|
||||
|
||||
const std::string path = SegmentPath_(meta_[h].seg_id);
|
||||
|
||||
std::ifstream is(path, std::ios::binary);
|
||||
MFEM_VERIFY(is.is_open(), "SegmentedFileCheckpointStorage: open-for-read failed.");
|
||||
|
||||
// Seek to record start
|
||||
is.seekg((std::streamoff)meta_[h].offset, std::ios::beg);
|
||||
MFEM_VERIFY(is.good(), "SegmentedFileCheckpointStorage: seekg failed.");
|
||||
|
||||
std::uint64_t payload_bytes = 0;
|
||||
is.read(reinterpret_cast<char*>(&payload_bytes), sizeof(payload_bytes));
|
||||
MFEM_VERIFY(is.good(), "SegmentedFileCheckpointStorage: read header failed.");
|
||||
MFEM_VERIFY(payload_bytes == meta_[h].bytes, "SegmentedFileCheckpointStorage: payload size mismatch.");
|
||||
|
||||
Snapshot snap = SnapshotIO::Read(is);
|
||||
MFEM_VERIFY(is.good(), "SegmentedFileCheckpointStorage: payload read failed.");
|
||||
|
||||
f(snap);
|
||||
}
|
||||
|
||||
void Erase(Handle &h)
|
||||
{
|
||||
if (!IsValid(h)) { h = InvalidHandle(); return; }
|
||||
MFEM_VERIFY(h < (Handle)meta_.size(), "SegmentedFileCheckpointStorage: Erase out-of-range handle.");
|
||||
|
||||
meta_[h].valid = false;
|
||||
|
||||
if (!keep_files_)
|
||||
{
|
||||
free_.push_back(h); // allow handle reuse
|
||||
}
|
||||
|
||||
h = InvalidHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Optional cleanup helper (NOT part of the required storage interface).
|
||||
*
|
||||
* Deletes all segment files touched by this storage object.
|
||||
*/
|
||||
void PurgeAllFiles()
|
||||
{
|
||||
#if MFEM_HAVE_FILESYSTEM
|
||||
for (auto seg : touched_segments_)
|
||||
{
|
||||
const std::string path = SegmentPath_(seg);
|
||||
std::error_code ec;
|
||||
mfem_fs::remove(mfem_fs::path(path), ec);
|
||||
// Best-effort cleanup:
|
||||
MFEM_VERIFY(!ec, "SegmentedFileCheckpointStorage: failed to remove segment file.");
|
||||
}
|
||||
touched_segments_.clear();
|
||||
#else
|
||||
MFEM_ABORT("SegmentedFileCheckpointStorage: PurgeAllFiles requires <filesystem> support.");
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
struct Meta
|
||||
{
|
||||
bool valid = false;
|
||||
std::int64_t seg_id = 0;
|
||||
std::uint64_t offset = 0;
|
||||
std::uint64_t bytes = 0;
|
||||
};
|
||||
|
||||
std::string dir_, prefix_, ext_;
|
||||
bool keep_files_ = false;
|
||||
std::int64_t records_per_file_ = 4096;
|
||||
|
||||
mutable Handle next_id_ = 0;
|
||||
mutable std::vector<Handle> free_;
|
||||
std::vector<Meta> meta_;
|
||||
|
||||
mutable std::uint64_t last_payload_bytes_ = 0;
|
||||
|
||||
std::set<std::int64_t> touched_segments_;
|
||||
|
||||
void EnsureMetaSize_(Handle id)
|
||||
{
|
||||
if ((std::size_t)id >= meta_.size())
|
||||
{
|
||||
meta_.resize((std::size_t)id + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Handle AllocateId_()
|
||||
{
|
||||
if (!keep_files_ && !free_.empty())
|
||||
{
|
||||
const Handle id = free_.back();
|
||||
free_.pop_back();
|
||||
return id;
|
||||
}
|
||||
return next_id_++;
|
||||
}
|
||||
|
||||
std::int64_t SegmentId_(Handle id) const
|
||||
{
|
||||
return (std::int64_t)(id / records_per_file_);
|
||||
}
|
||||
|
||||
std::string SegmentPath_(std::int64_t seg) const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << prefix_ << std::setw(8) << std::setfill('0') << seg << ext_;
|
||||
|
||||
#if MFEM_HAVE_FILESYSTEM
|
||||
mfem_fs::path p = mfem_fs::path(dir_) / mfem_fs::path(oss.str());
|
||||
return p.string();
|
||||
#else
|
||||
return dir_ + "/" + oss.str();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void EnsureFileExists_(const std::string &path)
|
||||
{
|
||||
// Try open for read/write; if missing, create.
|
||||
std::fstream fs(path, std::ios::binary | std::ios::in | std::ios::out);
|
||||
if (!fs.is_open())
|
||||
{
|
||||
std::ofstream create(path, std::ios::binary | std::ios::out);
|
||||
MFEM_VERIFY(create.is_open(), "SegmentedFileCheckpointStorage: file create failed.");
|
||||
}
|
||||
}
|
||||
|
||||
// Append framed record to file. Returns offset of record start. Updates last_payload_bytes_.
|
||||
std::uint64_t AppendRecord_(const std::string &path, const Snapshot &snap)
|
||||
{
|
||||
EnsureFileExists_(path);
|
||||
|
||||
std::fstream fs(path, std::ios::binary | std::ios::in | std::ios::out);
|
||||
MFEM_VERIFY(fs.is_open(), "SegmentedFileCheckpointStorage: open-for-append failed.");
|
||||
|
||||
fs.seekp(0, std::ios::end);
|
||||
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: seekp(end) failed.");
|
||||
|
||||
const std::streamoff begin = (std::streamoff)fs.tellp();
|
||||
MFEM_VERIFY(begin >= 0, "SegmentedFileCheckpointStorage: tellp failed.");
|
||||
|
||||
// placeholder for payload bytes
|
||||
std::uint64_t payload_bytes = 0;
|
||||
fs.write(reinterpret_cast<const char*>(&payload_bytes), sizeof(payload_bytes));
|
||||
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: write header failed.");
|
||||
|
||||
// payload
|
||||
SnapshotIO::Write(fs, snap);
|
||||
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: write payload failed.");
|
||||
|
||||
const std::streamoff end = (std::streamoff)fs.tellp();
|
||||
MFEM_VERIFY(end >= begin, "SegmentedFileCheckpointStorage: tellp end failed.");
|
||||
|
||||
payload_bytes = (std::uint64_t)(end - begin - (std::streamoff)sizeof(std::uint64_t));
|
||||
last_payload_bytes_ = payload_bytes;
|
||||
|
||||
// backpatch payload size
|
||||
fs.seekp(begin, std::ios::beg);
|
||||
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: seekp(begin) failed.");
|
||||
fs.write(reinterpret_cast<const char*>(&payload_bytes), sizeof(payload_bytes));
|
||||
MFEM_VERIFY(fs.good(), "SegmentedFileCheckpointStorage: backpatch failed.");
|
||||
|
||||
return (std::uint64_t)begin;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif // MFEM_SEGMENT_CHECKPOINT_STORAGE_HPP
|
||||
|
||||
@@ -1,701 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
|
||||
#include "dynamic_checkpointing.hpp"
|
||||
#include "fixed_slot_checkpoint_storage.hpp"
|
||||
#include "file_checkpoint_storage.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* The forward state consists of vector and several additional values.
|
||||
* The goal is to demontrate 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 &out) const
|
||||
{
|
||||
MFEM_VERIFY(src != nullptr, "Unpack: src is null.");
|
||||
|
||||
const unsigned char *b = static_cast<const unsigned char*>(src);
|
||||
|
||||
std::memcpy(&out.time, b + 0*sizeof(mfem::real_t), sizeof(mfem::real_t));
|
||||
std::memcpy(&out.obj, b + 1*sizeof(mfem::real_t), sizeof(mfem::real_t));
|
||||
out.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)
|
||||
std::cout<<"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)
|
||||
std::cout<<"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-1<<"\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 storage’s 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)
|
||||
std::cout<<"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)
|
||||
std::cout<<"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 that’s 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)
|
||||
std::cout<<"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)
|
||||
std::cout<<"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)
|
||||
std::cout<<"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)
|
||||
std::cout<<"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;
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
#include "dynamic_checkpointing.hpp"
|
||||
#include "file_checkpoint_storage.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 &out) { out = 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 &out) { out = 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;
|
||||
}
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
|
||||
#include "dynamic_checkpointing.hpp"
|
||||
#include "fixed_slot_checkpoint_storage.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 &out) { out = 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 &out) { out = 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;
|
||||
}
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
|
||||
#include "revolve_checkpointing.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 &out) const
|
||||
{
|
||||
MFEM_VERIFY(src != nullptr, "Unpack: src is null.");
|
||||
MFEM_VERIFY(bytes == Bytes(), "Unpack: snapshot byte size mismatch.");
|
||||
|
||||
if (out.v.Size() != n_) { out.v.SetSize(n_); }
|
||||
mfem::real_t *vh = out.v.HostWrite();
|
||||
|
||||
std::memcpy(&out.time, src + 0*sizeof(mfem::real_t), sizeof(mfem::real_t));
|
||||
std::memcpy(&out.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[])
|
||||
{
|
||||
// Backend selection:
|
||||
// 0 = fixed-slot memory (single RAM block)
|
||||
// 1 = fixed-slot file (single file with fixed offsets)
|
||||
int backend = 0;
|
||||
|
||||
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) */;
|
||||
|
||||
// Memory backend (single block)
|
||||
// FixedSlotMemoryStorage storage(Ncheck, snapshot_bytes);
|
||||
// FixedStepRevolveCheckpointing<FixedSlotMemoryStorage>
|
||||
// cktp(Nsteps, Ncheck, snapshot_bytes, storage);
|
||||
|
||||
// or file backend (single file)
|
||||
FixedSlotFileStorage storage("revolve_ckpts.bin", Ncheck, snapshot_bytes);
|
||||
FixedStepRevolveCheckpointing<FixedSlotFileStorage>
|
||||
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;
|
||||
};
|
||||
@@ -1,163 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
#include "dynamic_checkpointing.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 &out) { out = 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;
|
||||
}
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
#include "dynamic_checkpointing.hpp"
|
||||
#include "segment_checkpoint_storage.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)
|
||||
{
|
||||
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[])
|
||||
{
|
||||
int s = 3;
|
||||
double dt0 = 0.02;
|
||||
double omega = 0.2;
|
||||
double Tfinal = 1.0;
|
||||
|
||||
// scalar params
|
||||
double alpha = 2.0;
|
||||
double u0 = 0.2;
|
||||
double target_s = 0.7;
|
||||
double eps = 1e-7;
|
||||
|
||||
// vector params
|
||||
int n = 16;
|
||||
double target_v = 0.7;
|
||||
|
||||
// segmented storage params
|
||||
std::string dir_scalar = "chk_scalar_segments";
|
||||
std::string dir_vector = "chk_vector_segments";
|
||||
long long records_per_file = 4096;
|
||||
bool keep_files = false;
|
||||
bool purge_dirs = true;
|
||||
|
||||
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(&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 segment files.");
|
||||
args.AddOption(&dir_vector, "-dv", "--dir-vector", "Directory for vector segment files.");
|
||||
args.AddOption(&records_per_file, "-rpf", "--records-per-file",
|
||||
"How many handles belong to one segment file (range size).");
|
||||
|
||||
args.AddOption(&keep_files, "-k", "--keep-files", "-nk", "--no-keep-files",
|
||||
"Keep segment files (debug).");
|
||||
args.AddOption(&purge_dirs, "-p", "--purge-dirs", "-np", "--no-purge-dirs",
|
||||
"Purge checkpoint directories at start (recommended).");
|
||||
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
args.PrintUsage(mfem::out);
|
||||
return 1;
|
||||
}
|
||||
args.PrintOptions(mfem::out);
|
||||
|
||||
MFEM_VERIFY(s > 0, "Need s > 0.");
|
||||
MFEM_VERIFY(records_per_file > 0, "Need records_per_file > 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::SegmentedFileCheckpointStorage<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 with segmented storage
|
||||
// ============================================================
|
||||
mfem::SegmentedFileCheckpointStorage<double> stor_s(dir_scalar, records_per_file,
|
||||
"seg_", ".bin", true, keep_files);
|
||||
{
|
||||
mfem::DynamicCheckpointing<double, mfem::SegmentedFileCheckpointStorage<double>> ckpt(s, stor_s);
|
||||
|
||||
auto make_snapshot = [](const double &u) { return u; };
|
||||
auto restore_snapshot = [](const double &snap, double &out) { out = snap; };
|
||||
|
||||
auto primal_step = [&](double &u, StepS i)
|
||||
{
|
||||
u = LogisticStep(u, alpha, dt_func(i));
|
||||
};
|
||||
|
||||
auto adjoint_step = [&](double &lambda, const double &u_i, StepS i)
|
||||
{
|
||||
lambda *= LogisticJac(u_i, alpha, dt_func(i));
|
||||
};
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
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[SegmentIO][Scalar]\n";
|
||||
mfem::out << " m steps = " << 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 with segmented storage
|
||||
// ============================================================
|
||||
mfem::SegmentedFileCheckpointStorage<mfem::Vector> stor_v(dir_vector, records_per_file,
|
||||
"seg_", ".bin", true, keep_files);
|
||||
{
|
||||
mfem::DynamicCheckpointing<mfem::Vector, mfem::SegmentedFileCheckpointStorage<mfem::Vector>>
|
||||
ckpt(s, stor_v);
|
||||
|
||||
using StepV = mfem::DynamicCheckpointing<
|
||||
mfem::Vector,
|
||||
mfem::SegmentedFileCheckpointStorage<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 &out) { out = 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;
|
||||
|
||||
mfem::Vector diff(u);
|
||||
diff -= ut;
|
||||
const double J = 0.5 * mfem::InnerProduct(diff, diff);
|
||||
|
||||
mfem::Vector lambda = diff;
|
||||
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;
|
||||
|
||||
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[SegmentIO][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 - u_target||2 = " << diff.Norml2() << "\n";
|
||||
}
|
||||
|
||||
// Optional cleanup: delete segment files this run touched
|
||||
if (!keep_files)
|
||||
{
|
||||
stor_s.PurgeAllFiles();
|
||||
stor_v.PurgeAllFiles();
|
||||
}
|
||||
|
||||
mfem::out << "\nDone.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user