diff --git a/.github/workflows/mfem-analysis.yml b/.github/workflows/mfem-analysis.yml index 2ff9283569..20e7c05dd3 100644 --- a/.github/workflows/mfem-analysis.yml +++ b/.github/workflows/mfem-analysis.yml @@ -82,9 +82,9 @@ jobs: uses: mfem/github-actions/build-mfem@v2.0 with: os: ${{ runner.os }} - target: optim + target: opt codecov: NO - mpi: parallel + mpi: par build-system: make hypre-dir: ${{ env.HYPRE_TOP_DIR }} metis-dir: ${{ env.METIS_TOP_DIR }} diff --git a/.gitignore b/.gitignore index 7cba8a61d4..6ef2f15725 100644 --- a/.gitignore +++ b/.gitignore @@ -51,7 +51,8 @@ examples/ex1[04-9] examples/ex1[0-9]p examples/ex2[0-9] examples/ex2[0-9]p -examples/ex2[0-9]p_proposed +examples/ex30 +examples/ex30p examples/ex3[0-9]_proposed examples/ex3[0-9]p_proposed diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 91bb21059d..c9cc74206e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,11 +27,36 @@ stages: - sub-pipelines +variables: + CUSTOM_CI_BUILDS_DIR: "/usr/workspace/mfem/gitlab-runner" + USER_CI_TOP_DIR: "${CUSTOM_CI_BUILDS_DIR}/${GITLAB_USER_LOGIN}" + SHARED_REPOS_DIR: "${USER_CI_TOP_DIR}/repos" + AUTOTEST_ROOT: "${SHARED_REPOS_DIR}" + # MFEM_DATA_DIR is setup in '.gitlab/configs/setup-build-and-test.yml' and + # used in '.gitlab/configs/-config.yml': + MFEM_DATA_DIR: "${SHARED_REPOS_DIR}/mfem-data" + +# Defines the default choice for updating the saved baseline results. By default +# the baseline can only be updated from the master branch. This variable offers +# the option to manually ask for rebaselining from another branch if necessary. + REBASELINE: "NO" + AUTOTEST: "NO" + # AUTOTEST_COMMIT: used only when AUTOTEST is set to YES. + # * If AUTOTEST_COMMIT is NOT set to NO, reporting jobs will commit their + # files to the MFEM/autotest repo. + # * If AUTOTEST_COMMIT is set to NO, reporting jobs will NOT commit their + # files to the MFEM/autotest repo. Instead they will just show the contents + # of the report files and remove them. + AUTOTEST_COMMIT: "YES" + # Trigger subpipelines: quartz-build-and-test: stage: sub-pipelines variables: - _AUTOTEST: $AUTOTEST + # Explicitly pass down values that we want to be able to set when triggering + # pipelines manually or using scheduling + AUTOTEST: "${AUTOTEST}" + AUTOTEST_COMMIT: "${AUTOTEST_COMMIT}" trigger: include: .gitlab/quartz-build-and-test.yml strategy: depend @@ -39,7 +64,11 @@ quartz-build-and-test: quartz-baseline: stage: sub-pipelines variables: - _AUTOTEST: $AUTOTEST + # Explicitly pass down values that we want to be able to set when triggering + # pipelines manually or using scheduling + REBASELINE: "${REBASELINE}" + AUTOTEST: "${AUTOTEST}" + AUTOTEST_COMMIT: "${AUTOTEST_COMMIT}" trigger: include: .gitlab/quartz-baseline.yml strategy: depend @@ -47,7 +76,10 @@ quartz-baseline: lassen-build-and-test: stage: sub-pipelines variables: - _AUTOTEST: $AUTOTEST + # Explicitly pass down values that we want to be able to set when triggering + # pipelines manually or using scheduling + AUTOTEST: "${AUTOTEST}" + AUTOTEST_COMMIT: "${AUTOTEST_COMMIT}" trigger: include: .gitlab/lassen-build-and-test.yml strategy: depend @@ -55,7 +87,10 @@ lassen-build-and-test: corona-build-and-test: stage: sub-pipelines variables: - _AUTOTEST: $AUTOTEST + # Explicitly pass down values that we want to be able to set when triggering + # pipelines manually or using scheduling + AUTOTEST: "${AUTOTEST}" + AUTOTEST_COMMIT: "${AUTOTEST_COMMIT}" trigger: include: .gitlab/corona-build-and-test.yml strategy: depend diff --git a/.gitlab/configs/common.yml b/.gitlab/configs/common.yml index b796125892..b8aab3ef75 100644 --- a/.gitlab/configs/common.yml +++ b/.gitlab/configs/common.yml @@ -18,19 +18,13 @@ variables: # the pipeline, preventing any form of concurrency with other pipelines. This # also means that the BUILD_ROOT directory will never be cleaned. # TODO: add a clean-up mechanism - BUILD_ROOT: ${CI_BUILDS_DIR}/MFEM_${MACHINE_NAME}/${CI_PROJECT_NAME}_${CI_COMMIT_REF_SLUG}_${CI_PIPELINE_ID} + BUILD_ROOT: ${USER_CI_TOP_DIR}/${CI_PROJECT_NAME}-${MACHINE_NAME}-pipeline-${CI_PIPELINE_ID} # On LLNL's quartz, there is only one allocation shared among jobs in order to # save time and resource. This allocation has to be uniquely named so that we # are sure to retrieve it. ALLOC_NAME: ${CI_PROJECT_NAME}_ci_${CI_PIPELINE_ID} -# Defines the default choice for updating the saved baseline results. By default -# the baseline can only be updated from the master branch. This variable offers -# the option to manually ask for rebaselining from another branch if necessary. - _REBASELINE: "NO" - _AUTOTEST: "NO" - # Git repositories used in the pipeline TPLS_REPO: ssh://git@mybitbucket.llnl.gov:7999/mfem/tpls.git TESTS_REPO: ssh://git@mybitbucket.llnl.gov:7999/mfem/tests.git @@ -40,5 +34,3 @@ variables: # Directory used to place artifacts. ARTIFACTS_DIR: artifacts SLURM_OVERLAP: 1 - - diff --git a/.gitlab/configs/corona-config.yml b/.gitlab/configs/corona-config.yml index f6362d7071..c39a270c63 100644 --- a/.gitlab/configs/corona-config.yml +++ b/.gitlab/configs/corona-config.yml @@ -26,17 +26,20 @@ variables: - if: '$CI_COMMIT_BRANCH =~ /_cnone/ || $ON_CORONA != "ON"' when: never # Don’t run autotest update if... - - if: '$CI_JOB_NAME =~ /report/ && $_AUTOTEST != "YES"' + - if: '$CI_JOB_NAME =~ /report/ && $AUTOTEST != "YES"' when: never # Report success on success status - - if: '$CI_JOB_NAME =~ /report_job_success/ && $_AUTOTEST == "YES"' + - if: '$CI_JOB_NAME =~ /report_job_success/ && $AUTOTEST == "YES"' when: on_success # Report failure on failure status - - if: '$CI_JOB_NAME =~ /report_job_failure/ && $_AUTOTEST == "YES"' + - if: '$CI_JOB_NAME =~ /report_job_failure/ && $AUTOTEST == "YES"' when: on_failure # Always release resource - if: '$CI_JOB_NAME =~ /release_resource/' when: always + # Always cleanup + - if: '$CI_JOB_NAME =~ /cleanup/' + when: always # Default is to run if previous stage succeeded - when: on_success @@ -46,9 +49,11 @@ variables: extends: [.on_corona] stage: build_and_test script: + # THREADS is used by 'tests/gitlab/build_and_test', run below - export THREADS=12 - echo ${ALLOC_NAME} - export JOBID=$(squeue -h --name=${ALLOC_NAME} --format=%A) - echo ${JOBID} - - srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) -t 15 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --build-root "${BUILD_ROOT}" --data - + - echo ${MFEM_DATA_DIR} + - echo ${SPEC} + - srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) -t 15 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data diff --git a/.gitlab/configs/lassen-config.yml b/.gitlab/configs/lassen-config.yml index bedc802470..e5adc94596 100644 --- a/.gitlab/configs/lassen-config.yml +++ b/.gitlab/configs/lassen-config.yml @@ -21,14 +21,17 @@ variables: - if: '$CI_COMMIT_BRANCH =~ /_lnone/ || $ON_LASSEN == "OFF"' #run except if ... when: never # Don't run autotest update if... - - if: '$CI_JOB_NAME =~ /report/ && $_AUTOTEST != "YES"' + - if: '$CI_JOB_NAME =~ /report/ && $AUTOTEST != "YES"' when: never # Report success on success status - - if: '$CI_JOB_NAME =~ /report_job_success/ && $_AUTOTEST == "YES"' + - if: '$CI_JOB_NAME =~ /report_job_success/ && $AUTOTEST == "YES"' when: on_success # Report failure on failure status - - if: '$CI_JOB_NAME =~ /report_job_failure/ && $_AUTOTEST == "YES"' + - if: '$CI_JOB_NAME =~ /report_job_failure/ && $AUTOTEST == "YES"' when: on_failure + # Always cleanup + - if: '$CI_JOB_NAME =~ /cleanup/' + when: always - when: on_success # Lassen uses a different job scheduler (spectrum lsf) that does not allow @@ -39,5 +42,8 @@ variables: extends: [.on_lassen] stage: build_and_test script: - - lalloc 1 -W 30 -q pdebug tests/gitlab/build_and_test --spec "${SPEC}" --build-root "${BUILD_ROOT}" --data + - echo ${MFEM_DATA_DIR} + - echo ${SPEC} + # Next script uses 'THREADS': leaving it empty --> it uses 'make all -j' + - lalloc 1 -W 30 -q pdebug tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data needs: [setup] diff --git a/.gitlab/configs/quartz-config.yml b/.gitlab/configs/quartz-config.yml index 774313c9f6..06a6d44170 100644 --- a/.gitlab/configs/quartz-config.yml +++ b/.gitlab/configs/quartz-config.yml @@ -22,17 +22,20 @@ variables: - if: '$CI_COMMIT_BRANCH =~ /_qnone/ || $ON_QUARTZ == "OFF"' when: never # Don't run autotest update if... - - if: '$CI_JOB_NAME =~ /report/ && $_AUTOTEST != "YES"' + - if: '$CI_JOB_NAME =~ /report/ && $AUTOTEST != "YES"' when: never # Report success on success status - - if: '$CI_JOB_NAME =~ /report_job_success/ && $_AUTOTEST == "YES"' + - if: '$CI_JOB_NAME =~ /report_job_success/ && $AUTOTEST == "YES"' when: on_success # Report failure on failure status - - if: '$CI_JOB_NAME =~ /report_job_failure/ && $_AUTOTEST == "YES"' + - if: '$CI_JOB_NAME =~ /report_job_failure/ && $AUTOTEST == "YES"' when: on_failure # Always release resource - if: '$CI_JOB_NAME =~ /release_resource/' when: always + # Always cleanup + - if: '$CI_JOB_NAME =~ /cleanup/' + when: always # Default is to run if previous stage succeeded - when: on_success @@ -42,9 +45,11 @@ variables: extends: [.on_quartz] stage: build_and_test script: + # THREADS is used by 'tests/gitlab/build_and_test', run below - export THREADS=12 - echo ${ALLOC_NAME} - export JOBID=$(squeue -h --name=${ALLOC_NAME} --format=%A) - echo ${JOBID} - - srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) -t 30 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --build-root "${BUILD_ROOT}" --data - + - echo ${MFEM_DATA_DIR} + - echo ${SPEC} + - srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) -t 30 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data diff --git a/.gitlab/configs/report-build-and-test.yml b/.gitlab/configs/report-build-and-test.yml new file mode 100644 index 0000000000..9df21939f2 --- /dev/null +++ b/.gitlab/configs/report-build-and-test.yml @@ -0,0 +1,81 @@ +# Copyright (c) 2010-2021, 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. + +# Jobs report +.report_job_success: + script: + - echo ${MACHINE_NAME} + - echo ${AUTOTEST} + - echo ${AUTOTEST_COMMIT} + - echo "AUTOTEST_ROOT ${AUTOTEST_ROOT}" + - cd ${AUTOTEST_ROOT} + - | + ( + date + echo "Waiting to aquire lock on '$PWD/autotest.lock' ..." + # try to get an excusive lock on fd 9 (autotest.lock) repeating the try + # every 5 seconds; simply using no timeout, i.e. 'flock 9', causes the + # command to hang indefinitely sometimes, so we use the timeout & retry + # as a workaround; we may want to add a counter for the number of + # retries to interrupt a potential infinite loop + while ! flock -w 5 9; do + true + done + echo "Aquired lock on '$PWD/autotest.lock'" + date + # Report SUCCESS while holding the file lock on 'autotest.lock'. + # The next script uses the following environment variables: + # - MACHINE_NAME, AUTOTEST_ROOT, AUTOTEST_COMMIT + # - CI_COMMIT_REF_SLUG, CI_PROJECT_DIR, CI_PIPELINE_URL + # It also calls the script '.gitlab/scripts/safe_create_rundir'. + ${CI_PROJECT_DIR}/.gitlab/scripts/report_build_and_test_success + err=$? + # sleep for a period to allow NFS to propagate the above changes; + # clearly, there is no guarantee that other NFS clients will see the + # changes even after the timeout + sleep 10 + exit $err + ) 9> autotest.lock + +.report_job_failure: + script: + - echo ${MACHINE_NAME} + - echo ${AUTOTEST} + - echo ${AUTOTEST_COMMIT} + - echo "AUTOTEST_ROOT ${AUTOTEST_ROOT}" + - cd ${AUTOTEST_ROOT} + - | + ( + date + echo "Waiting to aquire lock on '$PWD/autotest.lock' ..." + # try to get an excusive lock on fd 9 (autotest.lock) repeating the try + # every 5 seconds; simply using no timeout, i.e. 'flock 9', causes the + # command to hang indefinitely sometimes, so we use the timeout & retry + # as a workaround; we may want to add a counter for the number of + # retries to interrupt a potential infinite loop + while ! flock -w 5 9; do + true + done + echo "Aquired lock on '$PWD/autotest.lock'" + date + # Report FAILURE while holding the file lock on 'autotest.lock'. + # The next script uses the following environment variables: + # - MACHINE_NAME, AUTOTEST_ROOT, AUTOTEST_COMMIT + # - CI_COMMIT_REF_SLUG, CI_PROJECT_DIR, CI_PIPELINE_URL + # It also calls the script '.gitlab/scripts/safe_create_rundir'. + ${CI_PROJECT_DIR}/.gitlab/scripts/report_build_and_test_failure + err=$? + # sleep for a period to allow NFS to propagate the above changes; + # clearly, there is no guarantee that other NFS clients will see the + # changes even after the timeout + sleep 10 + exit $err + ) 9> autotest.lock diff --git a/.gitlab/configs/setup-baseline.yml b/.gitlab/configs/setup-baseline.yml index ee07dfd17f..2227b4cfe8 100644 --- a/.gitlab/configs/setup-baseline.yml +++ b/.gitlab/configs/setup-baseline.yml @@ -9,13 +9,6 @@ # terms of the BSD-3 license. We welcome feedback and contributions, see file # CONTRIBUTING.md for details. -# TPLS_DIR is used in .gitlab/scripts/baseline to provide the tpls location -# when call the runtest script in MFEM test repo. -# Note: the value must be consistent with what setup_baseline does. -variables: - TPLS_DIR: ${BUILD_ROOT}/tpls - AUTOTEST_ROOT: ${CI_BUILDS_DIR}/MFEM_${MACHINE_NAME}_baseline - # The setup_baseline job doesn't rely on MFEM git repo. It prepares a # pipeline-wide working directory downloading/updating external repos. # TODO: @@ -30,13 +23,50 @@ setup_baseline: variables: GIT_STRATEGY: none script: + # + # Setup ${BUILD_ROOT}/tpls and ${BUILD_ROOT}/tests: + # + - echo "MACHINE_NAME = ${MACHINE_NAME}" + - echo "REBASELINE = ${REBASELINE}" + - echo "AUTOTEST = ${AUTOTEST}" + - echo "AUTOTEST_COMMIT = ${AUTOTEST_COMMIT}" - echo "BUILD_ROOT ${BUILD_ROOT}" - mkdir -p ${BUILD_ROOT} && cd ${BUILD_ROOT} - if [ ! -d "tpls" ]; then git clone ${TPLS_REPO}; fi - if [ ! -d "tests" ]; then git clone ${TESTS_REPO}; fi - cd tpls && git pull && cd .. - cd tests && git pull origin && cd .. + # + # Setup ${AUTOTEST_ROOT}/autotest: + # - echo "AUTOTEST_ROOT ${AUTOTEST_ROOT}" - mkdir -p ${AUTOTEST_ROOT} && cd ${AUTOTEST_ROOT} - - if [ ! -d "autotest" ]; then git clone ${AUTOTEST_REPO}; fi - - cd autotest && git pull && cd .. + - command -v flock || echo "Required command 'flock' not found" + - | + ( + date + echo "Waiting to aquire lock on '$PWD/autotest.lock' ..." + # try to get an excusive lock on fd 9 (autotest.lock) repeating the try + # every 5 seconds; simply using no timeout, i.e. 'flock 9', causes the + # command to hang indefinitely sometimes, so we use the timeout & retry + # as a workaround; we may want to add a counter for the number of + # retries to interrupt a potential infinite loop + while ! flock -w 5 9; do + true + done + echo "Aquired lock on '$PWD/autotest.lock'" + date + # clone/update the autotest repo while holding the file lock on + # 'autotest.lock' + err=0 + if [[ ! -d "autotest" ]]; then + git clone ${AUTOTEST_REPO} + else + cd autotest && git pull && cd .. + fi || err=1 + # sleep for a period to allow NFS to propagate the above changes; + # clearly, there is no guarantee that other NFS clients will see the + # changes even after the timeout + sleep 10 + exit $err + ) 9> autotest.lock diff --git a/.gitlab/configs/setup-build-and-test.yml b/.gitlab/configs/setup-build-and-test.yml index 84838f36af..0ce79843e1 100644 --- a/.gitlab/configs/setup-build-and-test.yml +++ b/.gitlab/configs/setup-build-and-test.yml @@ -9,13 +9,10 @@ # terms of the BSD-3 license. We welcome feedback and contributions, see file # CONTRIBUTING.md for details. -variables: - AUTOTEST_ROOT: ${CI_BUILDS_DIR}/MFEM_${MACHINE_NAME}_build_and_test - -# setup clones the mfem/data repo in ${BUILD_ROOT}. The build_and_test script -# then symlinks the repo to the parent directory of the MFEM source directory. -# Unit tests that depend on the mfem/data repo will then detect that this -# directory is present and be enabled. +# Setup clones the mfem/data repo in ${SHARED_REPOS_DIR}. The build_and_test +# script then symlinks the repo to the parent directory of the MFEM source +# directory. Unit tests that depend on the mfem/data repo will then detect that +# this directory is present and be enabled. setup: tags: - shell @@ -24,11 +21,74 @@ setup: variables: GIT_STRATEGY: none script: - - echo "BUILD_ROOT ${BUILD_ROOT}" - - mkdir -p ${BUILD_ROOT} && cd ${BUILD_ROOT} - - if [ ! -d data ]; then git clone ${MFEM_DATA_REPO}; fi + # + # Setup MFEM_DATA_DIR=${SHARED_REPOS_DIR}/mfem-data, see '.gitlab-ci.yml' + # and '.gitlab/configs/-config.yml' + # + - echo "MACHINE_NAME = ${MACHINE_NAME}" + - echo "AUTOTEST = ${AUTOTEST}" + - echo "AUTOTEST_COMMIT = ${AUTOTEST_COMMIT}" + - echo "SHARED_REPOS_DIR ${SHARED_REPOS_DIR}" + - mkdir -p ${SHARED_REPOS_DIR} && cd ${SHARED_REPOS_DIR} + - command -v flock || echo "Required command 'flock' not found" + - | + ( + date + echo "Waiting to aquire lock on '$PWD/mfem-data.lock' ..." + # try to get an excusive lock on fd 9 (mfem-data.lock) repeating the try + # every 5 seconds; simply using no timeout, i.e. 'flock 9', causes the + # command to hang indefinitely sometimes, so we use the timeout & retry + # as a workaround; we may want to add a counter for the number of + # retries to interrupt a potential infinite loop + while ! flock -w 5 9; do + true + done + echo "Aquired lock on '$PWD/mfem-data.lock'" + date + # clone/update the mfem/data repo while holding the file lock on + # 'mfem-data.lock' + err=0 + if [[ ! -d "mfem-data" ]]; then + git clone ${MFEM_DATA_REPO} "mfem-data" + else + cd "mfem-data" && git pull && cd .. + fi || err=1 + # sleep for a period to allow NFS to propagate the above changes; + # clearly, there is no guarantee that other NFS clients will see the + # changes even after the timeout + sleep 10 + exit $err + ) 9> mfem-data.lock + # + # Setup ${AUTOTEST_ROOT}/autotest: + # - echo "AUTOTEST_ROOT ${AUTOTEST_ROOT}" - mkdir -p ${AUTOTEST_ROOT} && cd ${AUTOTEST_ROOT} - - if [ ! -d "autotest" ]; then git clone ${AUTOTEST_REPO}; fi - - cd autotest && git pull && cd .. - + - | + ( + date + echo "Waiting to aquire lock on '$PWD/autotest.lock' ..." + # try to get an excusive lock on fd 9 (autotest.lock) repeating the try + # every 5 seconds; simply using no timeout, i.e. 'flock 9', causes the + # command to hang indefinitely sometimes, so we use the timeout & retry + # as a workaround; we may want to add a counter for the number of + # retries to interrupt a potential infinite loop + while ! flock -w 5 9; do + true + done + echo "Aquired lock on '$PWD/autotest.lock'" + date + # clone/update the autotest repo while holding the file lock on + # 'autotest.lock' + err=0 + if [[ ! -d "autotest" ]]; then + git clone ${AUTOTEST_REPO} + else + cd autotest && git pull && cd .. + fi || err=1 + # sleep for a period to allow NFS to propagate the above changes; + # clearly, there is no guarantee that other NFS clients will see the + # changes even after the timeout + sleep 10 + exit $err + ) 9> autotest.lock diff --git a/.gitlab/corona-build-and-test.yml b/.gitlab/corona-build-and-test.yml index 6ee152d624..2e5c210b25 100644 --- a/.gitlab/corona-build-and-test.yml +++ b/.gitlab/corona-build-and-test.yml @@ -22,6 +22,7 @@ allocate_resource: extends: .on_corona stage: allocate_resource script: + - echo ${ALLOC_NAME} - salloc --exclusive --nodes=1 --partition=mi60 --time=30 --no-shell --job-name=${ALLOC_NAME} timeout: 6h needs: [setup] @@ -40,24 +41,27 @@ release_resource: extends: .on_corona stage: release_resource_and_report script: + - echo ${ALLOC_NAME} - export JOBID=$(squeue -h --name=${ALLOC_NAME} --format=%A) + - echo ${JOBID} - ([[ -n "${JOBID}" ]] && scancel ${JOBID}) needs: [rocm_gcc_8.3.1] # Jobs report report_job_success: - extends: .on_corona stage: release_resource_and_report - script: - - .gitlab/scripts/report_build_and_test_success + extends: + - .on_corona + - .report_job_success report_job_failure: - extends: .on_corona stage: release_resource_and_report - script: - - .gitlab/scripts/report_build_and_test_failure + extends: + - .on_corona + - .report_job_failure include: - local: .gitlab/configs/common.yml - local: .gitlab/configs/corona-config.yml - local: .gitlab/configs/setup-build-and-test.yml + - local: .gitlab/configs/report-build-and-test.yml diff --git a/.gitlab/lassen-build-and-test.yml b/.gitlab/lassen-build-and-test.yml index ce437828d0..7f7069ddda 100644 --- a/.gitlab/lassen-build-and-test.yml +++ b/.gitlab/lassen-build-and-test.yml @@ -21,18 +21,19 @@ opt_mpi_cuda_xl_16_1_1_8: # Jobs report report_job_success: - extends: .on_lassen stage: report - script: - - .gitlab/scripts/report_build_and_test_success + extends: + - .on_lassen + - .report_job_success report_job_failure: - extends: .on_lassen stage: report - script: - - .gitlab/scripts/report_build_and_test_failure + extends: + - .on_lassen + - .report_job_failure include: - local: .gitlab/configs/common.yml - local: .gitlab/configs/lassen-config.yml - local: .gitlab/configs/setup-build-and-test.yml + - local: .gitlab/configs/report-build-and-test.yml diff --git a/.gitlab/quartz-baseline.yml b/.gitlab/quartz-baseline.yml index c36680ec3a..fd1660357e 100644 --- a/.gitlab/quartz-baseline.yml +++ b/.gitlab/quartz-baseline.yml @@ -16,12 +16,26 @@ stages: - setup - baseline_check - baseline_report + - cleanup - baseline_publish baselinecheck_mfem_intel_quartz: extends: [.on_quartz] stage: baseline_check + variables: + # TPLS_DIR is used in .gitlab/scripts/baseline to provide the tpls location + # when call the runtest script in MFEM test repo. + # Note: the value must be consistent with the setup performed in + # .gitlab/configs/setup-baseline.yml. + TPLS_DIR: ${BUILD_ROOT}/tpls script: + - echo ${BUILD_ROOT} + - echo ${TPLS_DIR} + # Used by the tests in MFEM/tests: + - export MFEM_TEST_NP=32 + # The next script uses the following environment variables: + # * BASELINE_TEST, SYS_TYPE, CI_PROJECT_DIR, ARTIFACTS_DIR, + # * BUILD_ROOT, TPLS_DIR, MACHINE_NAME - .gitlab/scripts/baseline artifacts: when: always @@ -29,25 +43,75 @@ baselinecheck_mfem_intel_quartz: - ${ARTIFACTS_DIR} allow_failure: true +cleanup: + extends: .on_quartz + stage: cleanup + variables: + GIT_STRATEGY: none + script: + - echo "BUILD_ROOT=${BUILD_ROOT}" + - rm -rf "${BUILD_ROOT}" || true + report_baseline: extends: [.on_quartz] stage: baseline_report script: - - cd ${AUTOTEST_ROOT}/autotest && git pull - - mkdir -p ${MACHINE_NAME} - - rundir="${MACHINE_NAME}/$(date +%Y-%m-%d)-gitlab-${BASELINE_TEST}-${CI_COMMIT_REF_SLUG}" - - rundir=$(${CI_PROJECT_DIR}/.gitlab/scripts/safe_create_rundir ${rundir}) - - cp ${CI_PROJECT_DIR}/${ARTIFACTS_DIR}/* ${rundir} - # We create an autotest-email.html file, because that's how we signal that there was a diff (temporary). + - echo ${MACHINE_NAME} + - echo ${AUTOTEST} + - echo ${AUTOTEST_COMMIT} + - echo "AUTOTEST_ROOT ${AUTOTEST_ROOT}" + - cd ${AUTOTEST_ROOT} - | - if [[ -f ${rundir}/*.err ]] - then - echo "See the pipeline here -> $CI_PIPELINE_URL" >> ${rundir}/*.err - cp ${rundir}/*.err ${rundir}/autotest-email.html - fi - - git add ${rundir} - - git commit -am "GitLab CI log for ${BASELINE_TEST} on ${MACHINE_NAME} with intel ($(date +%Y-%m-%d))" - - git push origin master + ( + date + echo "Waiting to aquire lock on '$PWD/autotest.lock' ..." + # try to get an excusive lock on fd 9 (autotest.lock) repeating the try + # every 5 seconds; simply using no timeout, i.e. 'flock 9', causes the + # command to hang indefinitely sometimes, so we use the timeout & retry + # as a workaround; we may want to add a counter for the number of + # retries to interrupt a potential infinite loop + while ! flock -w 5 9; do + true + done + echo "Aquired lock on '$PWD/autotest.lock'" + date + # ---------------------- + cd ${AUTOTEST_ROOT}/autotest || \ + { echo "Invalid 'autotest' dir: ${AUTOTEST_ROOT}/autotest"; exit 1; } + mkdir -p ${MACHINE_NAME} + rundir="${MACHINE_NAME}/$(date +%Y-%m-%d)-gitlab-${BASELINE_TEST}-${CI_COMMIT_REF_SLUG}" + rundir=$(${CI_PROJECT_DIR}/.gitlab/scripts/safe_create_rundir ${rundir}) + cp ${CI_PROJECT_DIR}/${ARTIFACTS_DIR}/* ${rundir} + # We create an autotest-email.html file, because that's how we signal that there was a diff (temporary). + if [[ -f ${rundir}/${BASELINE_TEST}.err ]]; then + cp ${rundir}/${BASELINE_TEST}.err ${rundir}/autotest-email.html + fi + printf "%s\n" "" "Pipeline URL:" "$CI_PIPELINE_URL" \ + >> ${rundir}/pipeline.txt + msg="GitLab CI log for ${BASELINE_TEST} on ${MACHINE_NAME} ($(date +%Y-%m-%d))" + if [[ "$AUTOTEST_COMMIT" != "NO" ]]; then + git pull && \ + git add ${rundir} && \ + git commit -m "${msg}" && \ + git push origin master + else + for file in ${rundir}/*; do + echo "------------------------------" + echo "Content of '$file'" + echo "******************************" + cat $file + echo "******************************" + done + rm -rf ${rundir} || true + fi + err=$? + # ---------------------- + # sleep for a period to allow NFS to propagate the above changes; + # clearly, there is no guarantee that other NFS clients will see the + # changes even after the timeout + sleep 10 + exit $err + ) 9> autotest.lock baselinepublish_mfem_quartz: extends: [.on_quartz] @@ -56,6 +120,10 @@ baselinepublish_mfem_quartz: - if: '$CI_COMMIT_BRANCH == "master" || $REBASELINE == "YES"' when: manual script: + - echo ${BUILD_ROOT} + - echo ${PWD} + - echo ${ARTIFACTS_DIR} + - ls -lA ${ARTIFACTS_DIR} - .gitlab/scripts/rebaseline include: diff --git a/.gitlab/quartz-build-and-test.yml b/.gitlab/quartz-build-and-test.yml index ccf12fcc3b..74841d8266 100644 --- a/.gitlab/quartz-build-and-test.yml +++ b/.gitlab/quartz-build-and-test.yml @@ -22,6 +22,7 @@ allocate_resource: extends: .on_quartz stage: allocate_resource script: + - echo ${ALLOC_NAME} - salloc --exclusive --nodes=1 --partition=pdebug --time=30 --no-shell --job-name=${ALLOC_NAME} timeout: 6h @@ -73,23 +74,26 @@ release_resource: extends: .on_quartz stage: release_resource_and_report script: + - echo ${ALLOC_NAME} - export JOBID=$(squeue -h --name=${ALLOC_NAME} --format=%A) + - echo ${JOBID} - ([[ -n "${JOBID}" ]] && scancel ${JOBID}) # Jobs report report_job_success: - extends: .on_quartz stage: release_resource_and_report - script: - - .gitlab/scripts/report_build_and_test_success + extends: + - .on_quartz + - .report_job_success report_job_failure: - extends: .on_quartz stage: release_resource_and_report - script: - - .gitlab/scripts/report_build_and_test_failure + extends: + - .on_quartz + - .report_job_failure include: - local: .gitlab/configs/common.yml - local: .gitlab/configs/quartz-config.yml - local: .gitlab/configs/setup-build-and-test.yml + - local: .gitlab/configs/report-build-and-test.yml diff --git a/.gitlab/scripts/baseline b/.gitlab/scripts/baseline index 9991ed8a0b..6825426097 100755 --- a/.gitlab/scripts/baseline +++ b/.gitlab/scripts/baseline @@ -20,7 +20,8 @@ base_out=${base}.out artifacts_path=${CI_PROJECT_DIR}/${ARTIFACTS_DIR} # prepare -cd ${BUILD_ROOT} +cd ${BUILD_ROOT} || \ + { echo "Invalid BUILD_ROOT=$BUILD_ROOT"; exit 1; } ln -snf ${CI_PROJECT_DIR} mfem cd tests [[ -d _${BASELINE_TEST} ]] && rm -rf _${BASELINE_TEST} @@ -33,6 +34,9 @@ elif [[ ${MACHINE_NAME} == "corona" ]]; then srun --nodes=1 -t 60 -p mi60 ../runtest ../../mfem "${BASELINE_TEST} ${TPLS_DIR}" elif [[ ${MACHINE_NAME} == "lassen" ]]; then lalloc 1 -q pdebug ../runtest ../../mfem "${BASELINE_TEST} ${TPLS_DIR}" +else + echo "Unknown machine: MACHINE_NAME=$MACHINE_NAME" + exit 1 fi # post @@ -60,6 +64,10 @@ then cp ${base_out} ${artifacts_path}/${base_out} fi +if [[ -f ${BASELINE_TEST}.out ]]; then + cp ${BASELINE_TEST}.out ${artifacts_path} +fi + # base_diff won't even exist if there is no difference. if [[ -f ${base_diff} ]] then diff --git a/.gitlab/scripts/report_build_and_test_failure b/.gitlab/scripts/report_build_and_test_failure index 608a4aeb5b..fa03761d6c 100755 --- a/.gitlab/scripts/report_build_and_test_failure +++ b/.gitlab/scripts/report_build_and_test_failure @@ -13,20 +13,33 @@ echo "Runs if there was at least one failure on ${MACHINE_NAME}" -cd ${AUTOTEST_ROOT}/autotest && git pull +cd ${AUTOTEST_ROOT}/autotest || \ + { echo "Invalid 'autotest' dir: ${AUTOTEST_ROOT}/autotest"; exit 1; } mkdir -p ${MACHINE_NAME} rundir="${MACHINE_NAME}/$(date +%Y-%m-%d)-gitlab-ci-${CI_COMMIT_REF_SLUG}" rundir=$(${CI_PROJECT_DIR}/.gitlab/scripts/safe_create_rundir $rundir) -echo "There was an error while running CI on ${MACHINE_NAME}" > ${rundir}/gitlab.err -echo "See the pipeline here -> $CI_PIPELINE_URL" >> ${rundir}/gitlab.err +printf "%s\n" "Some 'build-and-test' jobs on ${MACHINE_NAME} FAILED." \ + "Pipeline URL:" "$CI_PIPELINE_URL" > ${rundir}/gitlab.err msg="GitLab CI log for build-and-test on ${MACHINE_NAME} ($(date +%Y-%m-%d))" +# Create 'autotest-email.html' to indicate failure: cp ${rundir}/gitlab.err ${rundir}/autotest-email.html -git pull -git add ${rundir} -git commit -am "${msg}" -git push origin master +if [[ "$AUTOTEST_COMMIT" != "NO" ]]; then + git pull && \ + git add ${rundir} && \ + git commit -m "${msg}" && \ + git push origin master +else + for file in ${rundir}/*; do + echo "------------------------------" + echo "Content of '$file'" + echo "******************************" + cat $file + echo "******************************" + done + rm -rf ${rundir} || true +fi diff --git a/.gitlab/scripts/report_build_and_test_success b/.gitlab/scripts/report_build_and_test_success index e714a08f5a..805ace0d19 100755 --- a/.gitlab/scripts/report_build_and_test_success +++ b/.gitlab/scripts/report_build_and_test_success @@ -13,18 +13,30 @@ echo "Can only run if all the ${MACHINE_NAME} jobs passed" -cd ${AUTOTEST_ROOT}/autotest && git pull +cd ${AUTOTEST_ROOT}/autotest || \ + { echo "Invalid 'autotest' dir: ${AUTOTEST_ROOT}/autotest"; exit 1; } mkdir -p ${MACHINE_NAME} rundir="${MACHINE_NAME}/$(date +%Y-%m-%d)-gitlab-ci-${CI_COMMIT_REF_SLUG}" rundir=$(${CI_PROJECT_DIR}/.gitlab/scripts/safe_create_rundir $rundir) -echo "The ${MACHINE_NAME} jobs were successful" > ${rundir}/gitlab.out -echo "See the pipeline here -> $CI_PIPELINE_URL" >> ${rundir}/gitlab.err +printf "%s\n" "The 'build-and-test' jobs on ${MACHINE_NAME} were SUCCESSFUL." \ + "Pipeline URL:" "$CI_PIPELINE_URL" > ${rundir}/gitlab.out msg="GitLab CI log for build-and-test on ${MACHINE_NAME} ($(date +%Y-%m-%d))" -git pull -git add ${rundir} -git commit -am "${msg}" -git push origin master +if [[ "$AUTOTEST_COMMIT" != "NO" ]]; then + git pull && \ + git add ${rundir} && \ + git commit -m "${msg}" && \ + git push origin master +else + for file in ${rundir}/*; do + echo "------------------------------" + echo "Content of '$file'" + echo "******************************" + cat $file + echo "******************************" + done + rm -rf ${rundir} || true +fi diff --git a/CHANGELOG b/CHANGELOG index cfbc95a619..689093e32b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,10 @@ Version 4.3.1 (development) =========================== +- Added support for mesh preprocessing to resolve fine scale problem data + before simulation. This feature uses adaptive mesh refinement to control the + associated data oscillation error. See the new Example 30/30p. + - Switched from Artistic Style (astyle) version 2.05.1 to version 3.1 for code formatting. See the "make style" target. @@ -18,6 +22,9 @@ Version 4.3.1 (development) - Added support for hr-adaptivity using TMOP-based error estimator. +- Coefficient::SetTime now propagates the new time into internally stored + Coefficient objects. + - Added initial support for google-benchmarks in the tests/benchmarks directory. It can be enabled with MFEM_USE_BENCHMARK=YES. @@ -47,11 +54,20 @@ Version 4.3.1 (development) output format if no physical groups are defined) are now successfully loaded, and elements are reassigned attribute number 1. +- Added new miniapps that use the ParELAG library, its hybrid smoothers, and the + hierarchy of spaces created by the element-based AMG (AMGe) methodology in + ParELAG to build multigrid solvers for H(curl) and H(div) forms. See the + miniapps/parelag directory for more details. + - Fixed several MinGW build issues on Windows. - Remove the 'u' flag in the ar command, to update all files in the archive, avoiding file name collisions from different subdirectories. +- Added initial TMOP-based capabilities for surface fitting and tangential + relaxation in the mesh-optimizer and pmesh-optimizer miniapps. + + Version 4.3, released on July 29, 2021 ====================================== @@ -320,11 +336,6 @@ Miscellaneous - Various other simplifications, extensions, and bugfixes in the code. -- Added new miniapps that use the ParELAG library, its hybrid smoothers, and the - hierarchy of spaces created by the element-based AMG (AMGe) methodology in - ParELAG to build multigrid solvers for H(curl) and H(div) forms. See the - miniapps/parelag directory for more details. - API changes ----------- - Added an abstract interface `mfem::FaceRestriction` for `H1FaceRestriction` diff --git a/CMakeLists.txt b/CMakeLists.txt index aadc475013..ae27a9b7fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -252,6 +252,11 @@ if (MFEM_USE_OPENMP OR MFEM_USE_LEGACY_OPENMP) endif() find_package(OpenMP REQUIRED) set(OPENMP_LIBRARIES ${OpenMP_CXX_LIBRARIES}) + if(APPLE) + # On macOS, the compiler needs additional help to find the header. + # See issue #2642 for more information. + include_directories(${OpenMP_CXX_INCLUDE_DIRS}) + endif(APPLE) endif() # SuiteSparse (before SUNDIALS which may depend on KLU) @@ -576,7 +581,11 @@ add_custom_target(${MFEM_EXEC_PREREQUISITES_TARGET_NAME}) # Create a target for all examples and, optionally, enable it. set(MFEM_ALL_EXAMPLES_TARGET_NAME examples) add_mfem_target(${MFEM_ALL_EXAMPLES_TARGET_NAME} ${MFEM_ENABLE_EXAMPLES}) -add_subdirectory(examples EXCLUDE_FROM_ALL) +if (MFEM_ENABLE_EXAMPLES) + add_subdirectory(examples) #install examples if enabled +else() + add_subdirectory(examples EXCLUDE_FROM_ALL) +endif() # Create a target for all miniapps and, optionally, enable it. set(MFEM_ALL_MINIAPPS_TARGET_NAME miniapps) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f5c691480c..67f66447e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,7 @@ back to them before issuing pull requests: - [New Feature Development](#new-feature-development) - [Developer Guidelines](#developer-guidelines) - [Pull Requests](#pull-requests) + - [MFEM PR Rules](#mfem-pr-rules) - [Pull Request Checklist](#pull-request-checklist) - [Master/Next Workflow](#masternext-workflow) - [Releases](#releases) @@ -67,8 +68,9 @@ Origin](#developers-certificate-of-origin-11) at the end of this file.* with regards to documentation and code styling. - Please do not commit large/binary files to the central repository (use a fork instead). -- Pull requests should be issued toward `mfem:master`. Make sure - to check the items off the [Pull Request Checklist](#pull-request-checklist). +- Pull requests should be issued toward `mfem:master`. Make sure + to check the items off the [Pull Request Checklist](#pull-request-checklist) and + follow the [MFEM PR Rules](#mfem-pr-rules). - When your contribution is fully working and ready to be reviewed, add the `ready-for-review` label. - PRs are treated similarly to journal submission with an "editor" assigning two @@ -326,8 +328,12 @@ Before you can start, you need a GitHub account, here are a few suggestions: change the code by default. - Code specifics - - All significant new classes, methods and functions have Doxygen-style - documentation in source comments. + - All new public, protected, and private classes, methods, data members, and + functions have Doxygen-style documentation in source comments. + - In addition to arguments and functionality, documentation should include the + current limitations of the code, any background information that is + implicitly assumed in the implementation, and the ownership and lifetime + of data. - Consistent code styling is enforced with `make style` in the top-level directory. This requires [Artistic Style](http://astyle.sourceforge.net) (we specifically use version 3.1). See also the file `config/mfem.astylerc`. @@ -335,6 +341,9 @@ Before you can start, you need a GitHub account, here are a few suggestions: internal library code. (You can use `std` in examples and miniapps.) - When manually resolving conflicts during a merge, make sure to mention the conflicted files in the commit message. + - All significant new features and changes should be documented in CHANGELOG. + - New examples and miniapps should have documentation on the MFEM webpage. + ### Pull Requests @@ -400,6 +409,83 @@ Before you can start, you need a GitHub account, here are a few suggestions: - If triggered, track the status of the LLNL GitLab tests. If failing, ask one of the _LLNL developers_ for details. + +### MFEM PR Rules + +The Pull Request (PR) approval process in MFEM is similar to the approval of papers in a peer-reviewed journal. In particular: + +1. There is an MFEM board of "editors" that evaluates new PRs and assigns "reviewers" for each PR. + +2. The assigned reviewers are responsible to carefully review and test the proposed PR. + +3. A PR can be (manually) merged in the *next* branch only if 2 of the assigned reviewers have approved it and it has passed internal testing. This merge can be performed by any of the assigned reviewers or by any of the editors. + +4. A PR can be merged in the *master* branch only if it has been tested successfully for a week in *next* and an editor has (optionally) taken a final look. This merge can be performed only by one of the editors. + +#### Responsibilities of Editors + +The current list of MFEM editors is: + +- @v-dobrev (Veselin Dobrev) +- @tzanio (Tzanio Kolev) +- @pazner (Will Pazner) +- @mlstowell (Mark Stowell) + +**The responsibilities of the editors are:** + +1. To assign appropriate milestone and labels for new PRs, e.g. *bugfix*, *minor*, *api-change*, *high-impact*, etc. + +2. To assign at least 2 reviewers for new PRs. An editor can also be a reviewer. The editor, reviewers, and author should be listed as "Assignees" on the GitHub PR page. After assignment, the `in-review` label should be added. + +3. To complete the initial PR evaluation and assignments in a timely manner: 1 week from submission. + +4. To assist reviewers when they need help with their reviews (but also to stay out of the way when they don't). + +5. To remind the reviewers about timely completion of their review. + +6. To take a final look and complete the PR merge in *master*. The final look step is optional and shouldn't take more than 3 days. + +7. The assignment of bugfixes should be expedited proportional to their importance, e.g. in some cases the editor can assign much shorter review window. + +#### Responsibilities of Reviewers + +Everyone on the MFEM team can be asked to serve as a reviewer on a PR in their area of expertise. + +**The responsibilities of the reviewers are:** + +1. To let the editors know if the proposed assignment is not a good match for them. + +2. To communicate with the PR author, provide feedback and work with them to resolve issues. + +3. To ensure the quality of the PR by making sure that the code adheres to the [Developer Guidelines](#developer-guidelines), e.g. all methods, data members, and functions have documentation, including data ownership and lifetime, new examples/miniapps have a corresponding PR in mfem/web, major features have `CHANGELOG` entries, etc. + +3. To seek help from the editors in case of difficulties. + +4. To complete the review in a timely manner: 3 weeks from assignment. + +5. To test the PR thoroughly before merging in *next*. The PR author is also encouraged to perform testing and inform the reviewers about the results. + +6. To monitor the PR impact on the testing in the *next* branch and alert the editors that the PR is ready for merging in *master*. + +7. The review of bugfixes should be expedited proportional to their importance. The review window can be much less than three weeks in such cases. + +#### Responsibilities of Authors + +Authors should clearly indicate when a PR is ready for review (before that the PR should be marked as `Draft` or `[WIP]`). + +**The responsibilities of the authors are:** + +1. To follow the instructions and PR checklist in the `CONTRIBUTING.md` document in the MFEM repository. + +2. To respond to reviewer feedback in a timely manner. + +3. Authors are encouraged to perform testing and inform the reviewers about the results. + +4. Authors can use the "Reviewers" section of the GitHub PR page to suggest reviewers, but the "Assignees" section will show who the editor has assigned to do the reviews. + +5. To indicate when the PR is ready for review by adding the `ready-for-review` label. + + ### Pull Request Checklist Before a PR can be merged, it should satisfy the following: @@ -453,7 +539,9 @@ Before a PR can be merged, it should satisfy the following: - [ ] The miniapps go at the end of the page, and are usually listed only under a specific "Application (PDE)" category. - [ ] Add a short description of the miniapp in the "Extensive Examples" section of `features.md`. - [ ] New capability: - - [ ] All significant new classes, methods and functions have Doxygen-style documentation in source comments. + - [ ] All new public, protected, and private classes, methods, data members, and functions have full Doxygen-style documentation in source comments. Documentation should include descriptions of member data, function arguments and return values, template parameters, and prerequisites for calling new functions. + - [ ] Pointer arguments and return values must specify whether ownership is being transferred or lent with the call. + - [ ] Any new functions should include descriptions of their intended use e.g. for internal use only, user-facing, etc., along with references to example code whenever possible/appropriate. - [ ] Consider adding new sample runs in existing examples to highlight the new capability. - [ ] Consider saving cool simulation pictures with the new capability in the Confluence gallery (LLNL only) or submitting them, via pull request, to the gallery section of the `mfem/web` repo. - [ ] If this is a major new feature, consider mentioning it in the short summary inside `README` *(rare)*. @@ -464,6 +552,7 @@ Before a PR can be merged, it should satisfy the following: - [ ] (LLNL only) After merging: - [ ] Update internal tests to include the new features. + ### Master/Next Workflow MFEM uses a `master`/`next`-branch workflow as described below: @@ -555,8 +644,10 @@ MFEM uses a `master`/`next`-branch workflow as described below: - Update version and shortlinks in `src/index.md` and `src/download.md`. - Use [cloc-1.62.pl](http://cloc.sourceforge.net/) and `ls -lh` to estimate the SLOC and the tarball size in `src/download.md`. + ## LLNL Workflow + ### Mirroring on Bitbucket - The GitHub `master` and `next` branches are mirrored to the LLNL institutional @@ -576,6 +667,7 @@ MFEM uses a `master`/`next`-branch workflow as described below: - `mfem:gh-next` -- Bleeding-edge development version, may be broken, use at your own risk. + ### Mirroring on GitLab - MFEM repository is also mirrored on the LLNL GitLab instance, in a @@ -598,6 +690,7 @@ In addition, developers can set local git hooks to run some quick checks on commit or push, see the [README](config/githooks/README.md) in the `config/githooks` directory. + ### Linux and Mac smoke tests We use GitHub Actions to drive the default tests on the `master` and `next` branches. See the `.github/workflows` files and the logs at @@ -609,6 +702,7 @@ constraint on jobs. Two virtual machines are configured - Mac (OS X) and Linux. - Tests on the `master` branch are triggered whenever a PR is issued on this branch. - Tests on the `next` branch are currently scheduled to run each night. + ### Windows smoke test We use Appveyor to test building with the MS Visual C++ compiler in a Windows environment, as well as to test the CMake build. See the `.appveyor` file and the @@ -618,6 +712,7 @@ build logs at CMake is used to generate the MSVC Project files and drive the build. A release and debug build is performed with a simple run of `ex1` to verify the executable. + ### Tests at LLNL - We mirror the `master` and `next` branches internally (to `gh-master` and diff --git a/INSTALL b/INSTALL index 6ca1a48b6c..bc8919020d 100644 --- a/INSTALL +++ b/INSTALL @@ -739,10 +739,10 @@ The specific libraries and their options are: Versions: libCEED >= 0.8. - RAJA (optional), used when MFEM_USE_RAJA = YES. - Beginning with MFEM v4.3, only RAJA v0.13.0+ is supported. + Beginning with MFEM v4.3, only RAJA v0.14.0+ is supported. URL: https://github.com/LLNL/RAJA Options: RAJA_DIR, RAJA_OPT, RAJA_LIB. - Versions: RAJA >= 0.13.0. + Versions: RAJA >= 0.14.0. - Caliper (optional), used when MFEM_USE_CALIPER = YES. URL: https://github.com/LLNL/Caliper @@ -753,7 +753,7 @@ The specific libraries and their options are: Umpire requires camp when the Umpire version is >= 3.0.0. URL: https://github.com/LLNL/Umpire Options: UMPIRE_DIR, UMPIRE_OPT, UMPIRE_LIB. - Versions: Umpire >= 2.0.0. + Versions: Umpire >= 3.0.0. - Benchmark, used when MFEM_USE_BENCHMARK = YES. URL: https://github.com/google/benchmark diff --git a/config/cmake/modules/MfemCmakeUtilities.cmake b/config/cmake/modules/MfemCmakeUtilities.cmake index c2c6173854..5e8b3d9b29 100644 --- a/config/cmake/modules/MfemCmakeUtilities.cmake +++ b/config/cmake/modules/MfemCmakeUtilities.cmake @@ -100,6 +100,8 @@ macro(add_mfem_examples EXE_SRCS) string(REPLACE ".cpp" "" EXE_NAME "${EXE_PREFIX}${SRC_FILENAME}") mfem_add_executable(${EXE_NAME} ${SRC_FILE}) + install(TARGETS ${EXE_NAME} + RUNTIME DESTINATION examples) add_dependencies(${MFEM_ALL_EXAMPLES_TARGET_NAME} ${EXE_NAME}) if (EXE_NEEDED_BY) add_dependencies(${EXE_NEEDED_BY} ${EXE_NAME}) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b5e0a79ab9..655cbafef5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -37,7 +37,8 @@ list(APPEND ALL_EXE_SRCS ex27.cpp ex28.cpp ex29.cpp - ex30_proposed.cpp + ex30.cpp + ex31_proposed.cpp ) if (MFEM_USE_MPI) @@ -71,8 +72,9 @@ if (MFEM_USE_MPI) ex27p.cpp ex28p.cpp ex29p.cpp - ex30p_proposed.cpp + ex30p.cpp ex31p_proposed.cpp + ex32p_proposed.cpp ) endif() diff --git a/examples/ex14.cpp b/examples/ex14.cpp index 1603704b7c..314138deb8 100644 --- a/examples/ex14.cpp +++ b/examples/ex14.cpp @@ -135,8 +135,8 @@ int main(int argc, char *argv[]) a->AddBdrFaceIntegrator(new DGDiffusionIntegrator(one, sigma, kappa)); if (eta > 0) { - a->AddInteriorFaceIntegrator(new DGDiffusionBR2Integrator(fespace, eta)); - a->AddBdrFaceIntegrator(new DGDiffusionBR2Integrator(fespace, eta)); + a->AddInteriorFaceIntegrator(new DGDiffusionBR2Integrator(*fespace, eta)); + a->AddBdrFaceIntegrator(new DGDiffusionBR2Integrator(*fespace, eta)); } a->Assemble(); a->Finalize(); diff --git a/examples/ex14p.cpp b/examples/ex14p.cpp index 2339c3253b..ee9cfbd4b7 100644 --- a/examples/ex14p.cpp +++ b/examples/ex14p.cpp @@ -199,8 +199,8 @@ int main(int argc, char *argv[]) a->AddBdrFaceIntegrator(new DGDiffusionIntegrator(one, sigma, kappa)); if (eta > 0) { - a->AddInteriorFaceIntegrator(new DGDiffusionBR2Integrator(fespace, eta)); - a->AddBdrFaceIntegrator(new DGDiffusionBR2Integrator(fespace, eta)); + a->AddInteriorFaceIntegrator(new DGDiffusionBR2Integrator(*fespace, eta)); + a->AddBdrFaceIntegrator(new DGDiffusionBR2Integrator(*fespace, eta)); } a->Assemble(); a->Finalize(); @@ -221,7 +221,7 @@ int main(int argc, char *argv[]) { HyprePCG pcg(*A); pcg.SetTol(1e-12); - pcg.SetMaxIter(200); + pcg.SetMaxIter(500); pcg.SetPrintLevel(2); pcg.SetPreconditioner(*amg); pcg.Mult(*B, *X); @@ -232,7 +232,7 @@ int main(int argc, char *argv[]) GMRESSolver gmres(MPI_COMM_WORLD); gmres.SetAbsTol(0.0); gmres.SetRelTol(1e-12); - gmres.SetMaxIter(200); + gmres.SetMaxIter(500); gmres.SetKDim(10); gmres.SetPrintLevel(1); gmres.SetOperator(*A); diff --git a/examples/ex30.cpp b/examples/ex30.cpp new file mode 100644 index 0000000000..ea1f37fa1d --- /dev/null +++ b/examples/ex30.cpp @@ -0,0 +1,195 @@ +// MFEM Example 30 +// +// Compile with: make ex30 +// +// Sample runs: ex30 -m ../data/square-disc.mesh -o 1 +// ex30 -m ../data/square-disc.mesh -o 2 +// ex30 -m ../data/square-disc.mesh -o 2 -me 1e3 +// ex30 -m ../data/square-disc-nurbs.mesh -o 2 +// ex30 -m ../data/star.mesh -o 2 -eo 4 +// ex30 -m ../data/fichera.mesh -o 2 -me 1e4 +// ex30 -m ../data/disc-nurbs.mesh -o 2 +// ex30 -m ../data/ball-nurbs.mesh -o 2 -eo 3 -e 1e-2 -me 1e4 +// ex30 -m ../data/star-surf.mesh -o 2 +// ex30 -m ../data/square-disc-surf.mesh -o 2 +// ex30 -m ../data/amr-quad.mesh -l 2 +// +// Description: This is an example of adaptive mesh refinement preprocessing +// which lowers the data oscillation [1] to a user-defined +// relative threshold. There is no PDE being solved. +// +// MFEM's capability to work with both conforming and +// nonconforming meshes is demonstrated in example 6. In some +// problems, the material data or loading data is not sufficiently +// resolved on the initial mesh. This missing fine scale data +// reduces the accuracy of the solution as well as the accuracy +// of some local error estimators. By preprocessing the mesh +// before solving the PDE, many issues can be avoided. +// +// [1] Morin, P., Nochetto, R. H., & Siebert, K. G. (2000). +// Data oscillation and convergence of adaptive FEM. SIAM +// Journal on Numerical Analysis, 38(2), 466-488. +// +// [2] Mitchell, W. F. (2013). A collection of 2D elliptic +// problems for testing adaptive grid refinement algorithms. +// Applied mathematics and computation, 220, 350-364. + +#include "mfem.hpp" +#include +#include + +using namespace std; +using namespace mfem; + +// Piecewise-affine function which is sometimes mesh-conforming +double affine_function(const Vector &p) +{ + double x = p(0), y = p(1); + if (x < 0.0) + { + return 1.0 + x + y; + } + else + { + return 1.0; + } +} + +// Piecewise-constant function which is never mesh-conforming +double jump_function(const Vector &p) +{ + if (p.Normlp(2.0) > 0.4 && p.Normlp(2.0) < 0.6) { return 1.0; } + return 5.0; +} + +// Singular function derived from the Laplacian of the "steep wavefront" +// problem in [2]. +double singular_function(const Vector &p) +{ + double x = p(0), y = p(1); + double alpha = 1000.0; + double xc = 0.75, yc = 0.5; + double r0 = 0.7; + double r = sqrt(pow(x - xc,2.0) + pow(y - yc,2.0)); + double num = - ( alpha - pow(alpha,3) * (pow(r,2) - pow(r0,2)) ); + double denom = pow(r * ( pow(alpha,2) * pow(r0,2) + pow(alpha,2) * pow(r,2) \ + - 2 * pow(alpha,2) * r0 * r + 1.0 ),2); + denom = max(denom,1e-8); + return num / denom; +} + +int main(int argc, char *argv[]) +{ + // 1. Parse command-line options. + const char *mesh_file = "../data/star.mesh"; + int order = 1; + int nc_limit = 1; + int max_elems = 1e5; + double double_max_elems = double(max_elems); + bool visualization = true; + double osc_threshold = 1e-3; + int enriched_order = 5; + + OptionsParser args(argc, argv); + args.AddOption(&mesh_file, "-m", "--mesh", + "Mesh file to use."); + args.AddOption(&order, "-o", "--order", + "Finite element order (polynomial degree)."); + args.AddOption(&nc_limit, "-l", "--nc-limit", + "Maximum level of hanging nodes."); + args.AddOption(&double_max_elems, "-me", "--max-elems", + "Stop after reaching this many elements."); + args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", + "--no-visualization", + "Enable or disable GLVis visualization."); + args.AddOption(&osc_threshold, "-e", "--error", + "relative data oscillation threshold."); + args.AddOption(&enriched_order, "-eo", "--enriched_order", + "Enriched quadrature order."); + + args.Parse(); + if (!args.Good()) + { + args.PrintUsage(cout); + return 1; + } + args.PrintOptions(cout); + + max_elems = int(double_max_elems); + Mesh mesh(mesh_file, 1, 1); + + // 2. Since a NURBS mesh can currently only be refined uniformly, we need to + // convert it to a piecewise-polynomial curved mesh. First we refine the + // NURBS mesh a bit more and then project the curvature to quadratic Nodes. + if (mesh.NURBSext) + { + for (int i = 0; i < 2; i++) + { + mesh.UniformRefinement(); + } + mesh.SetCurvature(2); + } + + // 3. Define functions and refiner. + FunctionCoefficient affine_coeff(affine_function); + FunctionCoefficient jump_coeff(jump_function); + FunctionCoefficient singular_coeff(singular_function); + CoefficientRefiner coeffrefiner(affine_coeff, order); + + // 4. Connect to GLVis. + char vishost[] = "localhost"; + int visport = 19916; + socketstream sol_sock; + if (visualization) + { + sol_sock.open(vishost, visport); + } + + // 5. Define custom integration rule (optional). + const IntegrationRule *irs[Geometry::NumGeom]; + int order_quad = 2*order + enriched_order; + for (int i = 0; i < Geometry::NumGeom; ++i) + { + irs[i] = &(IntRules.Get(i, order_quad)); + } + + // 6. Apply custom refiner settings. + coeffrefiner.SetIntRule(irs); + coeffrefiner.SetMaxElements(max_elems); + coeffrefiner.SetThreshold(osc_threshold); + coeffrefiner.SetNCLimit(nc_limit); + coeffrefiner.PrintWarnings(); + + // 7. Preprocess mesh to control osc (piecewise-affine function). + // This is mostly just a verification check. The oscillation should + // be zero if the function is mesh-conforming and order > 0. + coeffrefiner.PreprocessMesh(mesh); + + mfem::out << "\n"; + mfem::out << "Function 0 (affine) \n"; + mfem::out << "Number of Elements " << mesh.GetNE() << "\n"; + mfem::out << "Osc error " << coeffrefiner.GetOsc() << "\n"; + + // 8. Preprocess mesh to control osc (jump function). + coeffrefiner.ResetCoefficient(jump_coeff); + coeffrefiner.PreprocessMesh(mesh); + + mfem::out << "\n"; + mfem::out << "Function 1 (discontinuous) \n"; + mfem::out << "Number of Elements " << mesh.GetNE() << "\n"; + mfem::out << "Osc error " << coeffrefiner.GetOsc() << "\n"; + + // 9. Preprocess mesh to control osc (singular function). + coeffrefiner.ResetCoefficient(singular_coeff); + coeffrefiner.PreprocessMesh(mesh); + + mfem::out << "\n"; + mfem::out << "Function 2 (singular) \n"; + mfem::out << "Number of Elements " << mesh.GetNE() << "\n"; + mfem::out << "Osc error " << coeffrefiner.GetOsc() << "\n"; + + sol_sock.precision(8); + sol_sock << "mesh\n" << mesh << flush; + + return 0; +} diff --git a/examples/ex30p.cpp b/examples/ex30p.cpp new file mode 100644 index 0000000000..1f7d8456c5 --- /dev/null +++ b/examples/ex30p.cpp @@ -0,0 +1,241 @@ +// MFEM Example 30 - Parallel Version +// +// Compile with: make ex30p +// +// Sample runs: mpirun -np 4 ex30p -m ../data/square-disc.mesh -o 1 +// mpirun -np 4 ex30p -m ../data/square-disc.mesh -o 2 +// mpirun -np 4 ex30p -m ../data/square-disc.mesh -o 2 -me 1e3 +// mpirun -np 4 ex30p -m ../data/square-disc-nurbs.mesh -o 2 +// mpirun -np 4 ex30p -m ../data/star.mesh -o 2 -eo 4 +// mpirun -np 4 oscp -m ../data/fichera.mesh -o 2 -me 1e4 +// mpirun -np 4 ex30p -m ../data/disc-nurbs.mesh -o 2 +// mpirun -np 4 ex30p -m ../data/ball-nurbs.mesh -o 2 -eo 3 -e 1e-2 +// mpirun -np 4 ex30p -m ../data/star-surf.mesh -o 2 +// mpirun -np 4 ex30p -m ../data/square-disc-surf.mesh -o 2 +// mpirun -np 4 ex30p -m ../data/amr-quad.mesh -l 2 +// +// Description: This is an example of adaptive mesh refinement preprocessing +// which lowers the data oscillation [1] to a user-defined +// relative threshold. There is no PDE being solved. +// +// MFEM's capability to work with both conforming and +// nonconforming meshes is demonstrated in example 6. In some +// problems, the material data or loading data is not sufficiently +// resolved on the initial mesh. This missing fine scale data +// reduces the accuracy of the solution as well as the accuracy +// of some local error estimators. By preprocessing the mesh +// before solving the PDE, many issues can be avoided. +// +// [1] Morin, P., Nochetto, R. H., & Siebert, K. G. (2000). +// Data oscillation and convergence of adaptive FEM. SIAM +// Journal on Numerical Analysis, 38(2), 466-488. +// +// [2] Mitchell, W. F. (2013). A collection of 2D elliptic +// problems for testing adaptive grid refinement algorithms. +// Applied mathematics and computation, 220, 350-364. + +#include "mfem.hpp" +#include +#include + +using namespace std; +using namespace mfem; + +// Piecewise-affine function which is sometimes mesh-conforming +double affine_function(const Vector &p) +{ + double x = p(0), y = p(1); + if (x < 0.0) + { + return 1.0 + x + y; + } + else + { + return 1.0; + } +} + +// Piecewise-constant function which is never mesh-conforming +double jump_function(const Vector &p) +{ + if (p.Normlp(2.0) > 0.4 && p.Normlp(2.0) < 0.6) { return 1.0; } + return 5.0; +} + +// Singular function derived from the Laplacian of the "steep wavefront" +// problem in [2]. +double singular_function(const Vector &p) +{ + double x = p(0), y = p(1); + double alpha = 1000.0; + double xc = 0.75, yc = 0.5; + double r0 = 0.7; + double r = sqrt(pow(x - xc,2.0) + pow(y - yc,2.0)); + double num = - ( alpha - pow(alpha,3) * (pow(r,2) - pow(r0,2)) ); + double denom = pow(r * ( pow(alpha,2) * pow(r0,2) + pow(alpha,2) * pow(r,2) \ + - 2 * pow(alpha,2) * r0 * r + 1.0 ),2); + denom = max(denom,1e-8); + return num / denom; +} + +int main(int argc, char *argv[]) +{ + // 0. Initialize MPI. + int num_procs, myid; + MPI_Init(&argc, &argv); + MPI_Comm_size(MPI_COMM_WORLD, &num_procs); + MPI_Comm_rank(MPI_COMM_WORLD, &myid); + + // 1. Parse command-line options. + const char *mesh_file = "../data/star.mesh"; + int order = 1; + int nc_limit = 1; + int max_elems = 1e5; + double double_max_elems = double(max_elems); + bool visualization = true; + bool nc_simplices = true; + double osc_threshold = 1e-3; + int enriched_order = 5; + + OptionsParser args(argc, argv); + args.AddOption(&mesh_file, "-m", "--mesh", + "Mesh file to use."); + args.AddOption(&order, "-o", "--order", + "Finite element order (polynomial degree)."); + args.AddOption(&nc_limit, "-l", "--nc-limit", + "Maximum level of hanging nodes."); + args.AddOption(&double_max_elems, "-me", "--max-elems", + "Stop after reaching this many elements."); + args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", + "--no-visualization", + "Enable or disable GLVis visualization."); + args.AddOption(&osc_threshold, "-e", "--error", + "relative data oscillation threshold."); + args.AddOption(&enriched_order, "-eo", "--enriched_order", + "Enriched quadrature order."); + args.AddOption(&nc_simplices, "-ns", "--nonconforming-simplices", + "-cs", "--conforming-simplices", + "For simplicial meshes, enable/disable nonconforming" + " refinement"); + + args.Parse(); + if (!args.Good()) + { + if (myid == 0) + { + args.PrintUsage(cout); + } + MPI_Finalize(); + return 1; + } + if (myid == 0) + { + args.PrintOptions(cout); + } + + max_elems = int(double_max_elems); + Mesh mesh(mesh_file, 1, 1); + + // 2. Since a NURBS mesh can currently only be refined uniformly, we need to + // convert it to a piecewise-polynomial curved mesh. First we refine the + // NURBS mesh a bit more and then project the curvature to quadratic Nodes. + if (mesh.NURBSext) + { + for (int i = 0; i < 2; i++) + { + mesh.UniformRefinement(); + } + mesh.SetCurvature(2); + } + + // 3. Make sure the mesh is in the non-conforming mode to enable local + // refinement of quadrilaterals/hexahedra. Simplices can be refined + // either in conforming or in non-conforming mode. The conforming + // mode however does not support dynamic partitioning. + mesh.EnsureNCMesh(nc_simplices); + + // 4. Define a parallel mesh by partitioning the serial mesh. + // Once the parallel mesh is defined, the serial mesh can be deleted. + ParMesh pmesh(MPI_COMM_WORLD, mesh); + mesh.Clear(); + + // 5. Define functions and refiner. + FunctionCoefficient affine_coeff(affine_function); + FunctionCoefficient jump_coeff(jump_function); + FunctionCoefficient singular_coeff(singular_function); + CoefficientRefiner coeffrefiner(affine_coeff,order); + + // 6. Connect to GLVis. + char vishost[] = "localhost"; + int visport = 19916; + socketstream sol_sock; + if (visualization) + { + sol_sock.open(vishost, visport); + } + + // 7. Define custom integration rule (optional). + const IntegrationRule *irs[Geometry::NumGeom]; + int order_quad = 2*order + enriched_order; + for (int i=0; i < Geometry::NumGeom; ++i) + { + irs[i] = &(IntRules.Get(i, order_quad)); + } + + // 8. Apply custom refiner settings. + coeffrefiner.SetIntRule(irs); + coeffrefiner.SetMaxElements(max_elems); + coeffrefiner.SetThreshold(osc_threshold); + coeffrefiner.SetNCLimit(nc_limit); + coeffrefiner.PrintWarnings(); + + // 9. Preprocess mesh to control osc (piecewise-affine function). + // This is mostly just a verification check. The oscillation should + // be zero if the function is mesh-conforming and order > 0. + coeffrefiner.PreprocessMesh(pmesh); + + int globalNE = pmesh.GetGlobalNE(); + double osc = coeffrefiner.GetOsc(); + if (myid == 0) + { + mfem::out << "\n"; + mfem::out << "Function 0 (affine) \n"; + mfem::out << "Number of Elements " << globalNE << "\n"; + mfem::out << "Osc error " << osc << "\n"; + } + + // 10. Preprocess mesh to control osc (jump function). + coeffrefiner.ResetCoefficient(jump_coeff); + coeffrefiner.PreprocessMesh(pmesh); + + globalNE = pmesh.GetGlobalNE(); + osc = coeffrefiner.GetOsc(); + if (myid == 0) + { + mfem::out << "\n"; + mfem::out << "Function 1 (discontinuous) \n"; + mfem::out << "Number of Elements " << globalNE << "\n"; + mfem::out << "Osc error " << osc << "\n"; + } + + // 11. Preprocess mesh to control osc (singular function). + coeffrefiner.ResetCoefficient(singular_coeff); + coeffrefiner.PreprocessMesh(pmesh); + + globalNE = pmesh.GetGlobalNE(); + osc = coeffrefiner.GetOsc(); + if (myid == 0) + { + mfem::out << "\n"; + mfem::out << "Function 2 (singular) \n"; + mfem::out << "Number of Elements " << globalNE << "\n"; + mfem::out << "Osc error " << osc << "\n"; + } + + sol_sock.precision(8); + sol_sock << "parallel " << num_procs << " " << myid << "\n"; + sol_sock << "mesh\n" << pmesh << flush; + + MPI_Finalize(); + return 0; +} diff --git a/examples/makefile b/examples/makefile index a3544ad8f3..cbf64ca44f 100644 --- a/examples/makefile +++ b/examples/makefile @@ -22,10 +22,11 @@ MFEM_LIB_FILE = mfem_is_not_built -include $(CONFIG_MK) SEQ_EXAMPLES = ex0 ex1 ex2 ex3 ex4 ex5 ex6 ex7 ex8 ex9 ex10 ex14 ex15 ex16 \ - ex17 ex18 ex19 ex20 ex21 ex22 ex23 ex24 ex25 ex26 ex27 ex28 ex29 ex30_proposed + ex17 ex18 ex19 ex20 ex21 ex22 ex23 ex24 ex25 ex26 ex27 ex28 ex29 ex30 \ + ex31_proposed PAR_EXAMPLES = ex0p ex1p ex2p ex3p ex4p ex5p ex6p ex7p ex8p ex9p ex10p ex11p \ ex12p ex13p ex14p ex15p ex16p ex17p ex18p ex19p ex20p ex21p ex22p ex24p \ - ex25p ex26p ex27p ex28p ex29p ex30p_proposed ex31p_proposed + ex25p ex26p ex27p ex28p ex29p ex30p ex31p_proposed ex32p_proposed SEQ_DEVICE_EXAMPLES = ex1 ex3 ex4 ex5 ex6 ex9 ex22 ex24 ex25 ex26 PAR_DEVICE_EXAMPLES = ex1p ex2p ex3p ex4p ex5p ex6p ex7p ex9p ex13p ex22p \ ex24p ex25p ex26p diff --git a/fem/bilinearform.cpp b/fem/bilinearform.cpp index 1e4d1fbe5e..015c0a8ded 100644 --- a/fem/bilinearform.cpp +++ b/fem/bilinearform.cpp @@ -969,6 +969,7 @@ void BilinearForm::EliminateVDofs(const Array &vdofs, const Vector &sol, Vector &rhs, DiagonalPolicy dpolicy) { + vdofs.HostRead(); for (int i = 0; i < vdofs.Size(); i++) { int vdof = vdofs[i]; diff --git a/fem/bilininteg.cpp b/fem/bilininteg.cpp index f197963bb5..7cd61dc0b1 100644 --- a/fem/bilininteg.cpp +++ b/fem/bilininteg.cpp @@ -1048,6 +1048,8 @@ void DiffusionIntegrator::ComputeElementFlux "Unexpected height for MatrixCoefficient"); } + MFEM_VERIFY(!SMQ, "SymmetricMatrixCoefficient not supported here"); + #ifdef MFEM_THREAD_SAFE DenseMatrix dshape(nd,dim), invdfdx(dim, spaceDim); DenseMatrix M(MQ ? spaceDim : 0); @@ -1060,7 +1062,7 @@ void DiffusionIntegrator::ComputeElementFlux #endif vec.SetSize(dim); vecdxt.SetSize(spaceDim); - pointflux.SetSize(MQ ? spaceDim : 0); + pointflux.SetSize(MQ || VQ ? spaceDim : 0); const IntegrationRule &ir = fluxelem.GetNodes(); fnd = ir.GetNPoints(); @@ -1076,36 +1078,45 @@ void DiffusionIntegrator::ComputeElementFlux CalcInverse(Trans.Jacobian(), invdfdx); invdfdx.MultTranspose(vec, vecdxt); - if (!MQ && !VQ) + if (with_coef) { - if (Q && with_coef) + if (!MQ && !VQ) { - vecdxt *= Q->Eval(Trans,ip); + if (Q) + { + vecdxt *= Q->Eval(Trans,ip); + } + for (j = 0; j < spaceDim; j++) + { + flux(fnd*j+i) = vecdxt(j); + } } - for (j = 0; j < spaceDim; j++) + else { - flux(fnd*j+i) = vecdxt(j); + if (MQ) + { + MQ->Eval(M, Trans, ip); + M.Mult(vecdxt, pointflux); + } + else + { + VQ->Eval(D, Trans, ip); + for (int j=0; jEval(M, Trans, ip); - M.Mult(vecdxt, pointflux); - } - else - { - VQ->Eval(D, Trans, ip); - for (int j=0; jGetVDim() : 0); +#else + D.SetSize(VQ ? VQ->GetVDim() : 0); #endif + MFEM_VERIFY(!SMQ, "SymmetricMatrixCoefficient not supported here"); + shape.SetSize(nd); pointflux.SetSize(spaceDim); if (d_energy) { vec.SetSize(spaceDim); } @@ -1151,17 +1167,23 @@ double DiffusionIntegrator::ComputeFluxEnergy Trans.SetIntPoint(&ip); double w = Trans.Weight() * ip.weight; - if (!MQ) + if (MQ) + { + MQ->Eval(M, Trans, ip); + energy += w * M.InnerProduct(pointflux, pointflux); + } + else if (VQ) + { + VQ->Eval(D, Trans, ip); + D *= pointflux; + energy += w * (D * pointflux); + } + else { double e = (pointflux * pointflux); if (Q) { e *= Q->Eval(Trans, ip); } energy += w * e; } - else - { - MQ->Eval(M, Trans, ip); - energy += w * M.InnerProduct(pointflux, pointflux); - } if (d_energy) { @@ -1171,7 +1193,7 @@ double DiffusionIntegrator::ComputeFluxEnergy { (*d_energy)[k] += w * vec[k] * vec[k]; } - // TODO: Q, MQ + // TODO: Q, VQ, MQ } } diff --git a/fem/bilininteg.hpp b/fem/bilininteg.hpp index b9326ab37c..1af841c0a6 100644 --- a/fem/bilininteg.hpp +++ b/fem/bilininteg.hpp @@ -3023,10 +3023,11 @@ public: sum_e eta (r_e([u]), r_e([v])) - where r_e is the lifting operator defined on each edge e. The parameter eta - can be chosen to be one to obtain a stable discretization. The constructor - for this integrator requires the finite element space because the lifting - operator depends on the element-wise inverse mass matrix. + where r_e is the lifting operator defined on each edge e (potentially + weighted by a coefficient Q). The parameter eta can be chosen to be one to + obtain a stable discretization. The constructor for this integrator requires + the finite element space because the lifting operator depends on the + element-wise inverse mass matrix. BR2 stands for the second method of Bassi and Rebay: @@ -3049,14 +3050,28 @@ protected: Array ipiv; Array ipiv_offsets, Minv_offsets; + Coefficient *Q; + Vector shape1, shape2; DenseMatrix R11, R12, R21, R22; DenseMatrix MinvR11, MinvR12, MinvR21, MinvR22; DenseMatrix Re, MinvRe; + /// Precomputes the inverses (LU factorizations) of the local mass matrices. + /** @a fes must be a DG space, so the mass matrix is block diagonal, and its + inverse can be computed locally. This is required for the computation of + the lifting operators @a r_e. + */ + void PrecomputeMassInverse(class FiniteElementSpace &fes); + public: - DGDiffusionBR2Integrator(class FiniteElementSpace *fes, double e = 1.0); + DGDiffusionBR2Integrator(class FiniteElementSpace &fes, double e = 1.0); + DGDiffusionBR2Integrator(class FiniteElementSpace &fes, Coefficient &Q_, + double e = 1.0); + MFEM_DEPRECATED DGDiffusionBR2Integrator(class FiniteElementSpace *fes, + double e = 1.0); + using BilinearFormIntegrator::AssembleFaceMatrix; virtual void AssembleFaceMatrix(const FiniteElement &el1, const FiniteElement &el2, diff --git a/fem/bilininteg_br2.cpp b/fem/bilininteg_br2.cpp index c84263bd1b..0c430beaf9 100644 --- a/fem/bilininteg_br2.cpp +++ b/fem/bilininteg_br2.cpp @@ -16,20 +16,39 @@ namespace mfem { -DGDiffusionBR2Integrator::DGDiffusionBR2Integrator(FiniteElementSpace *fes, - double e) : eta(e) +DGDiffusionBR2Integrator::DGDiffusionBR2Integrator( + FiniteElementSpace &fes, double e) : eta(e), Q(NULL) { + PrecomputeMassInverse(fes); +} + +DGDiffusionBR2Integrator::DGDiffusionBR2Integrator( + FiniteElementSpace &fes, Coefficient &Q_, double e) : eta(e), Q(&Q_) +{ + PrecomputeMassInverse(fes); +} + +DGDiffusionBR2Integrator::DGDiffusionBR2Integrator( + FiniteElementSpace *fes, double e) : eta(e), Q(NULL) +{ + PrecomputeMassInverse(*fes); +} + +void DGDiffusionBR2Integrator::PrecomputeMassInverse(FiniteElementSpace &fes) +{ + MFEM_VERIFY(fes.IsDGSpace(), + "The BR2 integrator is only defined for DG spaces."); // Precompute local mass matrix inverses needed for the lifting operators // First compute offsets and total size needed (e.g. for mixed meshes or // p-refinement) - int nel = fes->GetNE(); + int nel = fes.GetNE(); Minv_offsets.SetSize(nel+1); ipiv_offsets.SetSize(nel+1); ipiv_offsets[0] = 0; Minv_offsets[0] = 0; for (int i=0; iGetFE(i)->GetDof(); + int dof = fes.GetFE(i)->GetDof(); ipiv_offsets[i+1] = ipiv_offsets[i] + dof; Minv_offsets[i+1] = Minv_offsets[i] + dof*dof; } @@ -37,7 +56,7 @@ DGDiffusionBR2Integrator::DGDiffusionBR2Integrator(FiniteElementSpace *fes, #ifdef MFEM_USE_MPI // When running in parallel, we also need to compute the local mass matrices // of face neighbor elements - ParFiniteElementSpace *pfes = dynamic_cast(fes); + ParFiniteElementSpace *pfes = dynamic_cast(&fes); if (pfes != NULL) { ParMesh *pmesh = pfes->GetParMesh(); @@ -64,15 +83,15 @@ DGDiffusionBR2Integrator::DGDiffusionBR2Integrator(FiniteElementSpace *fes, { const FiniteElement *fe = NULL; ElementTransformation *tr = NULL; - if (i < fes->GetNE()) + if (i < fes.GetNE()) { - fe = fes->GetFE(i); - tr = fes->GetElementTransformation(i); + fe = fes.GetFE(i); + tr = fes.GetElementTransformation(i); } else { #ifdef MFEM_USE_MPI - int inbr = i - fes->GetNE(); + int inbr = i - fes.GetNE(); fe = pfes->GetFaceNbrFE(inbr); tr = pfes->GetParMesh()->GetFaceNbrElementTransformation(inbr); #endif @@ -151,21 +170,24 @@ void DGDiffusionBR2Integrator::AssembleFaceMatrix( for (int p = 0; p < ir->GetNPoints(); p++) { const IntegrationPoint &ip = ir->IntPoint(p); - IntegrationPoint eip1, eip2; + Trans.SetAllIntPoints(&ip); - Trans.Loc1.Transform(ip, eip1); + const IntegrationPoint &eip1 = Trans.Elem1->GetIntPoint(); el1.CalcShape(eip1, shape1); + double q = Q ? Q->Eval(*Trans.Elem1, eip1) : 1.0; if (ndof2) { - Trans.Loc2.Transform(ip, eip2); + const IntegrationPoint &eip2 = Trans.Elem2->GetIntPoint(); el2.CalcShape(eip2, shape2); + // Set coefficient value q to the average of the values on either side + if (Q) { q = 0.5*(q + Q->Eval(*Trans.Elem2, eip2)); } } - - double w = factor*sqrt(eta)*ip.weight*Trans.Face->Weight(); - if (ndof2) - { - w /= 2; - } + // Take sqrt here because + // eta (r_e([u]), r_e([v])) = (sqrt(eta) r_e([u]), sqrt(eta) r_e([v])) + double w = sqrt((factor + 1)*eta*q)*ip.weight*Trans.Face->Weight(); + // r_e is defined by, (r_e([u]), tau) = <[u], {tau}>, so we pick up a + // factor of 0.5 on interior faces from the average term. + if (ndof2) { w *= 0.5; } for (int i = 0; i < ndof1; i++) { diff --git a/fem/bilininteg_diffusion_pa.cpp b/fem/bilininteg_diffusion_pa.cpp index 10a384db19..b117ce21d8 100644 --- a/fem/bilininteg_diffusion_pa.cpp +++ b/fem/bilininteg_diffusion_pa.cpp @@ -903,9 +903,11 @@ static void PADiffusionAssembleDiagonal(const int dim, { switch ((D1D << 4 ) | Q1D) { + case 0x22: return SmemPADiffusionDiagonal3D<2,2>(NE,symm,B,G,D,Y); case 0x23: return SmemPADiffusionDiagonal3D<2,3>(NE,symm,B,G,D,Y); case 0x34: return SmemPADiffusionDiagonal3D<3,4>(NE,symm,B,G,D,Y); case 0x45: return SmemPADiffusionDiagonal3D<4,5>(NE,symm,B,G,D,Y); + case 0x46: return SmemPADiffusionDiagonal3D<4,6>(NE,symm,B,G,D,Y); case 0x56: return SmemPADiffusionDiagonal3D<5,6>(NE,symm,B,G,D,Y); case 0x67: return SmemPADiffusionDiagonal3D<6,7>(NE,symm,B,G,D,Y); case 0x78: return SmemPADiffusionDiagonal3D<7,8>(NE,symm,B,G,D,Y); @@ -1877,6 +1879,7 @@ static void PADiffusionApply(const int dim, { switch (ID) { + case 0x22: return SmemPADiffusionApply3D<2,2>(NE,symm,B,G,D,X,Y); case 0x23: return SmemPADiffusionApply3D<2,3>(NE,symm,B,G,D,X,Y); case 0x34: return SmemPADiffusionApply3D<3,4>(NE,symm,B,G,D,X,Y); case 0x45: return SmemPADiffusionApply3D<4,5>(NE,symm,B,G,D,X,Y); diff --git a/fem/bilininteg_mass_pa.cpp b/fem/bilininteg_mass_pa.cpp index 5f5089919f..fdf519e9ae 100644 --- a/fem/bilininteg_mass_pa.cpp +++ b/fem/bilininteg_mass_pa.cpp @@ -1203,8 +1203,10 @@ static void PAMassApply(const int dim, { switch (id) { + case 0x22: return SmemPAMassApply3D<2,2>(NE,B,Bt,D,X,Y); case 0x23: return SmemPAMassApply3D<2,3>(NE,B,Bt,D,X,Y); case 0x24: return SmemPAMassApply3D<2,4>(NE,B,Bt,D,X,Y); + case 0x26: return SmemPAMassApply3D<2,6>(NE,B,Bt,D,X,Y); case 0x34: return SmemPAMassApply3D<3,4>(NE,B,Bt,D,X,Y); case 0x35: return SmemPAMassApply3D<3,5>(NE,B,Bt,D,X,Y); case 0x36: return SmemPAMassApply3D<3,6>(NE,B,Bt,D,X,Y); diff --git a/fem/ceed/util.cpp b/fem/ceed/util.cpp index 9b02f30d0e..c857d0aa02 100644 --- a/fem/ceed/util.cpp +++ b/fem/ceed/util.cpp @@ -186,14 +186,18 @@ static void InitTensorBasis(const mfem::FiniteElementSpace &fes, const int ndofs = maps.ndof; const int nqpts = maps.nqpt; mfem::Vector qX(nqpts), qW(nqpts); - const mfem::IntegrationRule &ir1d = - IntRules.Get(Geometry::SEGMENT, ir.GetOrder()); + // The x-coordinates of the first `nqpts` points of the integration rule are + // the points of the corresponding 1D rule. We also scale the weights + // accordingly. + double w_sum = 0.0; for (int i = 0; i < nqpts; i++) { - const mfem::IntegrationPoint &ip = ir1d.IntPoint(i); + const mfem::IntegrationPoint &ip = ir.IntPoint(i); qX(i) = ip.x; qW(i) = ip.weight; + w_sum += ip.weight; } + qW *= 1.0/w_sum; CeedBasisCreateTensorH1(ceed, mesh->Dimension(), fes.GetVDim(), ndofs, nqpts, maps.Bt.GetData(), maps.Gt.GetData(), qX.GetData(), diff --git a/fem/coefficient.cpp b/fem/coefficient.cpp index 9df2efa2ca..a3a4166b91 100644 --- a/fem/coefficient.cpp +++ b/fem/coefficient.cpp @@ -52,6 +52,13 @@ double GridFunctionCoefficient::Eval (ElementTransformation &T, return GridF -> GetValue (T, ip, Component); } +void TransformedCoefficient::SetTime(double t) +{ + if (Q1) { Q1->SetTime(t); } + if (Q2) { Q2->SetTime(t); } + this->Coefficient::SetTime(t); +} + double TransformedCoefficient::Eval(ElementTransformation &T, const IntegrationPoint &ip) { @@ -66,6 +73,12 @@ double TransformedCoefficient::Eval(ElementTransformation &T, } } +void DeltaCoefficient::SetTime(double t) +{ + if (weight) { weight->SetTime(t); } + this->Coefficient::SetTime(t); +} + void DeltaCoefficient::SetDeltaCenter(const Vector& vcenter) { MFEM_VERIFY(vcenter.Size() <= 3, @@ -87,6 +100,12 @@ double DeltaCoefficient::EvalDelta(ElementTransformation &T, return weight ? weight->Eval(T, ip, GetTime())*w : w; } +void RestrictedCoefficient::SetTime(double t) +{ + if (c) { c->SetTime(t); } + this->Coefficient::SetTime(t); +} + void VectorCoefficient::Eval(DenseMatrix &M, ElementTransformation &T, const IntegrationRule &ir) { @@ -134,6 +153,15 @@ VectorArrayCoefficient::VectorArrayCoefficient (int dim) } } +void VectorArrayCoefficient::SetTime(double t) +{ + for (int i = 0; i < vdim; i++) + { + if (Coeff[i]) { Coeff[i]->SetTime(t); } + } + this->VectorCoefficient::SetTime(t); +} + void VectorArrayCoefficient::Set(int i, Coefficient *c, bool own) { if (ownCoeff[i]) { delete Coeff[i]; } @@ -239,6 +267,12 @@ double DivergenceGridFunctionCoefficient::Eval(ElementTransformation &T, return GridFunc->GetDivergence(T); } +void VectorDeltaCoefficient::SetTime(double t) +{ + d.SetTime(t); + this->VectorCoefficient::SetTime(t); +} + void VectorDeltaCoefficient::SetDirection(const Vector &d_) { dir = d_; @@ -253,6 +287,12 @@ void VectorDeltaCoefficient::EvalDelta( V *= d.EvalDelta(T, ip); } +void VectorRestrictedCoefficient::SetTime(double t) +{ + if (c) { c->SetTime(t); } + this->VectorCoefficient::SetTime(t); +} + void VectorRestrictedCoefficient::Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip) { @@ -283,6 +323,12 @@ void VectorRestrictedCoefficient::Eval( } } +void MatrixFunctionCoefficient::SetTime(double t) +{ + if (Q) { Q->SetTime(t); } + this->MatrixCoefficient::SetTime(t); +} + void MatrixFunctionCoefficient::Eval(DenseMatrix &K, ElementTransformation &T, const IntegrationPoint &ip) { @@ -363,6 +409,12 @@ void MatrixFunctionCoefficient::EvalSymmetric(Vector &K, } } +void SymmetricMatrixFunctionCoefficient::SetTime(double t) +{ + if (Q) { Q->SetTime(t); } + this->SymmetricMatrixCoefficient::SetTime(t); +} + void SymmetricMatrixFunctionCoefficient::Eval(DenseSymmetricMatrix &K, ElementTransformation &T, const IntegrationPoint &ip) @@ -405,6 +457,15 @@ MatrixArrayCoefficient::MatrixArrayCoefficient (int dim) } } +void MatrixArrayCoefficient::SetTime(double t) +{ + for (int i=0; i < height*width; i++) + { + if (Coeff[i]) { Coeff[i]->SetTime(t); } + } + this->MatrixCoefficient::SetTime(t); +} + void MatrixArrayCoefficient::Set(int i, int j, Coefficient * c, bool own) { if (ownCoeff[i*width+j]) { delete Coeff[i*width+j]; } @@ -433,6 +494,12 @@ void MatrixArrayCoefficient::Eval(DenseMatrix &K, ElementTransformation &T, } } +void MatrixRestrictedCoefficient::SetTime(double t) +{ + if (c) { c->SetTime(t); } + this->MatrixCoefficient::SetTime(t); +} + void MatrixRestrictedCoefficient::Eval(DenseMatrix &K, ElementTransformation &T, const IntegrationPoint &ip) { @@ -448,6 +515,33 @@ void MatrixRestrictedCoefficient::Eval(DenseMatrix &K, ElementTransformation &T, } } +void SumCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->Coefficient::SetTime(t); +} + +void ProductCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->Coefficient::SetTime(t); +} + +void RatioCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->Coefficient::SetTime(t); +} + +void PowerCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + this->Coefficient::SetTime(t); +} + InnerProductCoefficient::InnerProductCoefficient(VectorCoefficient &A, VectorCoefficient &B) : a(&A), b(&B), va(A.GetVDim()), vb(B.GetVDim()) @@ -457,6 +551,13 @@ InnerProductCoefficient::InnerProductCoefficient(VectorCoefficient &A, "Arguments have incompatible dimensions."); } +void InnerProductCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->Coefficient::SetTime(t); +} + double InnerProductCoefficient::Eval(ElementTransformation &T, const IntegrationPoint &ip) { @@ -474,6 +575,13 @@ VectorRotProductCoefficient::VectorRotProductCoefficient(VectorCoefficient &A, "Arguments must have dimension equal to two."); } +void VectorRotProductCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->Coefficient::SetTime(t); +} + double VectorRotProductCoefficient::Eval(ElementTransformation &T, const IntegrationPoint &ip) { @@ -490,6 +598,12 @@ DeterminantCoefficient::DeterminantCoefficient(MatrixCoefficient &A) "Argument must be a square matrix."); } +void DeterminantCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + this->Coefficient::SetTime(t); +} + double DeterminantCoefficient::Eval(ElementTransformation &T, const IntegrationPoint &ip) { @@ -538,6 +652,15 @@ VectorSumCoefficient::VectorSumCoefficient(VectorCoefficient &A_, "Arguments must have the same dimension."); } +void VectorSumCoefficient::SetTime(double t) +{ + if (ACoef) { ACoef->SetTime(t); } + if (BCoef) { BCoef->SetTime(t); } + if (alphaCoef) { alphaCoef->SetTime(t); } + if (betaCoef) { betaCoef->SetTime(t); } + this->VectorCoefficient::SetTime(t); +} + void VectorSumCoefficient::Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip) { @@ -561,6 +684,13 @@ ScalarVectorProductCoefficient::ScalarVectorProductCoefficient( : VectorCoefficient(B.GetVDim()), aConst(0.0), a(&A), b(&B) {} +void ScalarVectorProductCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->VectorCoefficient::SetTime(t); +} + void ScalarVectorProductCoefficient::Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip) { @@ -574,6 +704,12 @@ NormalizedVectorCoefficient::NormalizedVectorCoefficient(VectorCoefficient &A, : VectorCoefficient(A.GetVDim()), a(&A), tol(tol_) {} +void NormalizedVectorCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + this->VectorCoefficient::SetTime(t); +} + void NormalizedVectorCoefficient::Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip) { @@ -592,6 +728,13 @@ VectorCrossProductCoefficient::VectorCrossProductCoefficient( "Arguments must have dimension equal to three."); } +void VectorCrossProductCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->VectorCoefficient::SetTime(t); +} + void VectorCrossProductCoefficient::Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip) { @@ -613,6 +756,13 @@ MatrixVectorProductCoefficient::MatrixVectorProductCoefficient( "Arguments have incompatible dimensions."); } +void MatrixVectorProductCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->VectorCoefficient::SetTime(t); +} + void MatrixVectorProductCoefficient::Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip) { @@ -642,6 +792,13 @@ MatrixSumCoefficient::MatrixSumCoefficient(MatrixCoefficient &A, "Arguments must have the same dimensions."); } +void MatrixSumCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->MatrixCoefficient::SetTime(t); +} + void MatrixSumCoefficient::Eval(DenseMatrix &M, ElementTransformation &T, const IntegrationPoint &ip) { @@ -683,6 +840,13 @@ ScalarMatrixProductCoefficient::ScalarMatrixProductCoefficient( : MatrixCoefficient(B.GetHeight(), B.GetWidth()), aConst(0.0), a(&A), b(&B) {} +void ScalarMatrixProductCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->MatrixCoefficient::SetTime(t); +} + void ScalarMatrixProductCoefficient::Eval(DenseMatrix &M, ElementTransformation &T, const IntegrationPoint &ip) @@ -696,6 +860,12 @@ TransposeMatrixCoefficient::TransposeMatrixCoefficient(MatrixCoefficient &A) : MatrixCoefficient(A.GetWidth(), A.GetHeight()), a(&A) {} +void TransposeMatrixCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + this->MatrixCoefficient::SetTime(t); +} + void TransposeMatrixCoefficient::Eval(DenseMatrix &M, ElementTransformation &T, const IntegrationPoint &ip) @@ -712,6 +882,12 @@ InverseMatrixCoefficient::InverseMatrixCoefficient(MatrixCoefficient &A) "Argument must be a square matrix."); } +void InverseMatrixCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + this->MatrixCoefficient::SetTime(t); +} + void InverseMatrixCoefficient::Eval(DenseMatrix &M, ElementTransformation &T, const IntegrationPoint &ip) @@ -726,6 +902,13 @@ OuterProductCoefficient::OuterProductCoefficient(VectorCoefficient &A, va(A.GetVDim()), vb(B.GetVDim()) {} +void OuterProductCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (b) { b->SetTime(t); } + this->MatrixCoefficient::SetTime(t); +} + void OuterProductCoefficient::Eval(DenseMatrix &M, ElementTransformation &T, const IntegrationPoint &ip) { @@ -752,6 +935,13 @@ CrossCrossCoefficient::CrossCrossCoefficient(Coefficient &A, vk(K.GetVDim()) {} +void CrossCrossCoefficient::SetTime(double t) +{ + if (a) { a->SetTime(t); } + if (k) { k->SetTime(t); } + this->MatrixCoefficient::SetTime(t); +} + void CrossCrossCoefficient::Eval(DenseMatrix &M, ElementTransformation &T, const IntegrationPoint &ip) { diff --git a/fem/coefficient.hpp b/fem/coefficient.hpp index 9ca50595c3..cdcaadc560 100644 --- a/fem/coefficient.hpp +++ b/fem/coefficient.hpp @@ -45,7 +45,7 @@ public: Coefficient() { time = 0.; } /// Set the time for time dependent coefficients - void SetTime(double t) { time = t; } + virtual void SetTime(double t) { time = t; } /// Get the time for time dependent coefficients double GetTime() { return time; } @@ -217,6 +217,9 @@ public: double (*F)(double,double)) : Q1(q1), Q2(q2), Transform2(F) { Transform1 = 0; } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Evaluate the coefficient at @a ip. virtual double Eval(ElementTransformation &T, const IntegrationPoint &ip); }; @@ -269,6 +272,9 @@ public: weight = NULL; sdim = 3; tdf = NULL; } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Set the center location of the delta function. void SetDeltaCenter(const Vector& center); @@ -333,6 +339,9 @@ public: RestrictedCoefficient(Coefficient &c_, Array &attr) { c = &c_; attr.Copy(active_attr); } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Evaluate the coefficient at @a ip. virtual double Eval(ElementTransformation &T, const IntegrationPoint &ip) { return active_attr[T.Attribute-1] ? c->Eval(T, ip, GetTime()) : 0.0; } @@ -350,7 +359,7 @@ public: VectorCoefficient(int vd) { vdim = vd; time = 0.; } /// Set the time for time dependent coefficients - void SetTime(double t) { time = t; } + virtual void SetTime(double t) { time = t; } /// Get the time for time dependent coefficients double GetTime() { return time; } @@ -456,6 +465,9 @@ public: still need to be added with Set(). */ explicit VectorArrayCoefficient(int dim); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Returns i'th coefficient. Coefficient* GetCoeff(int i) { return Coeff[i]; } @@ -632,6 +644,9 @@ public: double s) : VectorCoefficient(dir_.Size()), dir(dir_), d(x,y,z,s) { } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Replace the associated DeltaCoefficient with a new DeltaCoefficient. /** The new DeltaCoefficient cannot have a specified weight Coefficient, i.e. DeltaCoefficient::Weight() should return NULL. */ @@ -677,6 +692,9 @@ public: : VectorCoefficient(vc.GetVDim()) { c = &vc; attr.Copy(active_attr); } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Evaluate the vector coefficient at @a ip. virtual void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip); @@ -708,7 +726,7 @@ public: height(h), width(w), time(0.), symmetric(symm) { } /// Set the time for time dependent coefficients - void SetTime(double t) { time = t; } + virtual void SetTime(double t) { time = t; } /// Get the time for time dependent coefficients double GetTime() { return time; } @@ -817,6 +835,9 @@ public: : MatrixCoefficient(dim), TDFunction(std::move(TDF)), Q(q) { } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Evaluate the matrix coefficient at @a ip. virtual void Eval(DenseMatrix &K, ElementTransformation &T, const IntegrationPoint &ip); @@ -844,6 +865,9 @@ public: actual coefficients still need to be added with Set(). */ explicit MatrixArrayCoefficient (int dim); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Get the coefficient located at (i,j) in the matrix. Coefficient* GetCoeff (int i, int j) { return Coeff[i*width+j]; } @@ -881,6 +905,9 @@ public: : MatrixCoefficient(mc.GetHeight(), mc.GetWidth()) { c = &mc; attr.Copy(active_attr); } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Evaluate the matrix coefficient at @a ip. virtual void Eval(DenseMatrix &K, ElementTransformation &T, const IntegrationPoint &ip); @@ -911,6 +938,9 @@ public: double alpha_ = 1.0, double beta_ = 1.0) : aConst(0.0), a(&A), b(&B), alpha(alpha_), beta(beta_) { } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the first term in the linear combination as a constant void SetAConst(double A) { a = NULL; aConst = A; } /// Return the first term in the linear combination @@ -959,7 +989,7 @@ public: { dim = dimension; time = 0.; } /// Set the time for time dependent coefficients - void SetTime(double t) { time = t; } + virtual void SetTime(double t) { time = t; } /// Get the time for time dependent coefficients double GetTime() { return time; } @@ -1037,6 +1067,9 @@ public: : SymmetricMatrixCoefficient(dim), TDFunction(std::move(TDF)), Q(q) { } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Evaluate the matrix coefficient at @a ip. virtual void Eval(DenseSymmetricMatrix &K, ElementTransformation &T, const IntegrationPoint &ip); @@ -1063,6 +1096,9 @@ public: ProductCoefficient(Coefficient &A, Coefficient &B) : aConst(0.0), a(&A), b(&B) { } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the first term in the product as a constant void SetAConst(double A) { a = NULL; aConst = A; } /// Return the first term in the product @@ -1108,6 +1144,9 @@ public: RatioCoefficient(Coefficient &A, double B) : aConst(0.0), bConst(B), a(&A), b(NULL) { } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the numerator in the ratio as a constant void SetAConst(double A) { a = NULL; aConst = A; } /// Return the numerator of the ratio @@ -1151,6 +1190,9 @@ public: PowerCoefficient(Coefficient &A, double p_) : a(&A), p(p_) { } + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the base coefficient void SetACoef(Coefficient &A) { a = &A; } /// Return the base coefficient @@ -1181,6 +1223,9 @@ public: /// Construct with the two vector coefficients. Result is \f$ A \cdot B \f$. InnerProductCoefficient(VectorCoefficient &A, VectorCoefficient &B); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the first vector in the inner product void SetACoef(VectorCoefficient &A) { a = &A; } /// Return the first vector coefficient in the inner product @@ -1210,6 +1255,9 @@ public: /// Constructor with two vector coefficients. Result is \f$ A_x B_y - A_y * B_x; \f$. VectorRotProductCoefficient(VectorCoefficient &A, VectorCoefficient &B); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the first vector in the product void SetACoef(VectorCoefficient &A) { a = &A; } /// Return the first vector of the product @@ -1237,6 +1285,9 @@ public: /// Construct with the matrix. DeterminantCoefficient(MatrixCoefficient &A); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the matrix coefficient void SetACoef(MatrixCoefficient &A) { a = &A; } /// Return the matrix coefficient @@ -1280,6 +1331,9 @@ public: VectorSumCoefficient(VectorCoefficient &A_, VectorCoefficient &B_, Coefficient &alpha_, Coefficient &beta_); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the first vector coefficient void SetACoef(VectorCoefficient &A) { ACoef = &A; } /// Return the first vector coefficient @@ -1341,6 +1395,9 @@ public: /// Constructor with two coefficients. Result is A * B. ScalarVectorProductCoefficient(Coefficient &A, VectorCoefficient &B); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the scalar factor as a constant void SetAConst(double A) { a = NULL; aConst = A; } /// Return the scalar factor @@ -1379,6 +1436,9 @@ public: */ NormalizedVectorCoefficient(VectorCoefficient &A, double tol = 1e-6); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the vector coefficient void SetACoef(VectorCoefficient &A) { a = &A; } /// Return the vector coefficient @@ -1404,6 +1464,9 @@ public: /// Construct with the two coefficients. Result is A x B. VectorCrossProductCoefficient(VectorCoefficient &A, VectorCoefficient &B); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the first term in the product void SetACoef(VectorCoefficient &A) { a = &A; } /// Return the first term in the product @@ -1435,6 +1498,9 @@ public: /// Constructor with two coefficients. Result is A*B. MatrixVectorProductCoefficient(MatrixCoefficient &A, VectorCoefficient &B); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the matrix coefficient void SetACoef(MatrixCoefficient &A) { a = &A; } /// Return the matrix coefficient @@ -1487,6 +1553,9 @@ public: MatrixSumCoefficient(MatrixCoefficient &A, MatrixCoefficient &B, double alpha_ = 1.0, double beta_ = 1.0); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the first matrix coefficient void SetACoef(MatrixCoefficient &A) { a = &A; } /// Return the first matrix coefficient @@ -1557,6 +1626,9 @@ public: /// Constructor with two coefficients. Result is A*B. ScalarMatrixProductCoefficient(Coefficient &A, MatrixCoefficient &B); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the scalar factor as a constant void SetAConst(double A) { a = NULL; aConst = A; } /// Return the scalar factor @@ -1587,6 +1659,9 @@ public: /// Construct with the matrix coefficient. Result is \f$ A^T \f$. TransposeMatrixCoefficient(MatrixCoefficient &A); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the matrix coefficient void SetACoef(MatrixCoefficient &A) { a = &A; } /// Return the matrix coefficient @@ -1607,6 +1682,9 @@ public: /// Construct with the matrix coefficient. Result is \f$ A^{-1} \f$. InverseMatrixCoefficient(MatrixCoefficient &A); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the matrix coefficient void SetACoef(MatrixCoefficient &A) { a = &A; } /// Return the matrix coefficient @@ -1631,6 +1709,9 @@ public: /// Construct with two vector coefficients. Result is \f$ A B^T \f$. OuterProductCoefficient(VectorCoefficient &A, VectorCoefficient &B); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the first vector in the outer product void SetACoef(VectorCoefficient &A) { a = &A; } /// Return the first vector coefficient in the outer product @@ -1666,6 +1747,9 @@ public: CrossCrossCoefficient(double A, VectorCoefficient &K); CrossCrossCoefficient(Coefficient &A, VectorCoefficient &K); + /// Set the time for internally stored coefficients + void SetTime(double t); + /// Reset the scalar factor as a constant void SetAConst(double A) { a = NULL; aConst = A; } /// Return the scalar factor diff --git a/fem/fe/fe_fixed_order.cpp b/fem/fe/fe_fixed_order.cpp index b3e18b1360..fbe0414315 100644 --- a/fem/fe/fe_fixed_order.cpp +++ b/fem/fe/fe_fixed_order.cpp @@ -6035,6 +6035,15 @@ void RT0PyrFiniteElement::CalcVShape(const IntegrationPoint &ip, shape(4,1) = - 0.5; shape(4,2) = 1.0; + if (!rt0) + { + for (int i=1; i<5; i++) + for (int j=0; j<3; j++) + { + shape(i, j) *= 0.5; + } + } + return; } diff --git a/fem/fespace.cpp b/fem/fespace.cpp index b24928d0d4..6b9f5d211e 100644 --- a/fem/fespace.cpp +++ b/fem/fespace.cpp @@ -106,8 +106,11 @@ void FiniteElementSpace::CopyProlongationAndRestriction( SparseMatrix *perm_mat = NULL, *perm_mat_tr = NULL; if (perm) { + // Note: although n and fes.GetVSize() are typically equal, in + // variable-order spaces they may differ, since nonconforming edges/faces + // my have fictitious DOFs. int n = perm->Size(); - perm_mat = new SparseMatrix(n, n); + perm_mat = new SparseMatrix(n, fes.GetVSize()); for (int i=0; i *children; + + RefType(Geometry::Type g, int n, const Pair *c) + : geom(g), num_children(n), children(c) { } + + bool operator<(const RefType &other) const + { + if (geom < other.geom) { return true; } + if (geom > other.geom) { return false; } + if (num_children < other.num_children) { return true; } + if (num_children > other.num_children) { return false; } + for (int i = 0; i < num_children; i++) + { + if (children[i].one < other.children[i].one) { return true; } + if (children[i].one > other.children[i].one) { return false; } + } + return false; // everything is equal + } +}; + +void GetCoarseToFineMap(const CoarseFineTransformations &cft, + const mfem::Mesh &fine_mesh, + Table &coarse_to_fine, + Array &coarse_to_ref_type, + Table &ref_type_to_matrix, + Array &ref_type_to_geom) +{ + const int fine_ne = cft.embeddings.Size(); + int coarse_ne = -1; + for (int i = 0; i < fine_ne; i++) + { + coarse_ne = std::max(coarse_ne, cft.embeddings[i].parent); + } + coarse_ne++; + + coarse_to_ref_type.SetSize(coarse_ne); + coarse_to_fine.SetDims(coarse_ne, fine_ne); + + Array cf_i(coarse_to_fine.GetI(), coarse_ne+1); + Array > cf_j(fine_ne); + cf_i = 0; + for (int i = 0; i < fine_ne; i++) + { + cf_i[cft.embeddings[i].parent+1]++; + } + cf_i.PartialSum(); + MFEM_ASSERT(cf_i.Last() == cf_j.Size(), "internal error"); + for (int i = 0; i < fine_ne; i++) + { + const Embedding &e = cft.embeddings[i]; + cf_j[cf_i[e.parent]].one = e.matrix; // used as sort key below + cf_j[cf_i[e.parent]].two = i; + cf_i[e.parent]++; + } + std::copy_backward(cf_i.begin(), cf_i.end()-1, cf_i.end()); + cf_i[0] = 0; + for (int i = 0; i < coarse_ne; i++) + { + std::sort(&cf_j[cf_i[i]], cf_j.GetData() + cf_i[i+1]); + } + for (int i = 0; i < fine_ne; i++) + { + coarse_to_fine.GetJ()[i] = cf_j[i].two; + } + + using std::map; + using std::pair; + + map ref_type_map; + for (int i = 0; i < coarse_ne; i++) + { + const int num_children = cf_i[i+1]-cf_i[i]; + MFEM_ASSERT(num_children > 0, ""); + const int fine_el = cf_j[cf_i[i]].two; + // Assuming the coarse and the fine elements have the same geometry: + const Geometry::Type geom = fine_mesh.GetElementBaseGeometry(fine_el); + const RefType ref_type(geom, num_children, &cf_j[cf_i[i]]); + pair::iterator,bool> res = + ref_type_map.insert( + pair(ref_type, (int)ref_type_map.size())); + coarse_to_ref_type[i] = res.first->second; + } + + ref_type_to_matrix.MakeI((int)ref_type_map.size()); + ref_type_to_geom.SetSize((int)ref_type_map.size()); + for (map::iterator it = ref_type_map.begin(); + it != ref_type_map.end(); ++it) + { + ref_type_to_matrix.AddColumnsInRow(it->second, it->first.num_children); + ref_type_to_geom[it->second] = it->first.geom; + } + + ref_type_to_matrix.MakeJ(); + for (map::iterator it = ref_type_map.begin(); + it != ref_type_map.end(); ++it) + { + const RefType &rt = it->first; + for (int j = 0; j < rt.num_children; j++) + { + ref_type_to_matrix.AddConnection(it->second, rt.children[j].one); + } + } + ref_type_to_matrix.ShiftUpI(); +} + +} // namespace internal + + /// TODO: Implement DofTransformation support FiniteElementSpace::DerefinementOperator::DerefinementOperator( const FiniteElementSpace *f_fes, const FiniteElementSpace *c_fes, @@ -1764,8 +1894,9 @@ FiniteElementSpace::DerefinementOperator::DerefinementOperator( } Table ref_type_to_matrix; - rtrans.GetCoarseToFineMap(*f_mesh, coarse_to_fine, coarse_to_ref_type, - ref_type_to_matrix, ref_type_to_geom); + internal::GetCoarseToFineMap(rtrans, *f_mesh, coarse_to_fine, + coarse_to_ref_type, ref_type_to_matrix, + ref_type_to_geom); MFEM_ASSERT(coarse_to_fine.Size() == c_fes->GetNE(), ""); const int total_ref_types = ref_type_to_geom.Size(); diff --git a/fem/gridfunc.cpp b/fem/gridfunc.cpp index 5348a7d40c..ab806f6397 100644 --- a/fem/gridfunc.cpp +++ b/fem/gridfunc.cpp @@ -1362,21 +1362,20 @@ void GridFunction::ProjectVectorFieldOn(GridFunction &vec_field, int comp) } } -void GridFunction::GetDerivative(int comp, int der_comp, GridFunction &der) +void GridFunction::AccumulateAndCountDerivativeValues(int comp, int der_comp, + GridFunction &der, + Array &zones_per_dof) { FiniteElementSpace * der_fes = der.FESpace(); ElementTransformation * transf; - Array overlap(der_fes->GetVSize()); + zones_per_dof.SetSize(der_fes->GetVSize()); Array der_dofs, vdofs; DenseMatrix dshape, inv_jac; Vector pt_grad, loc_func; int i, j, k, dim, dof, der_dof, ind; double a; - for (i = 0; i < overlap.Size(); i++) - { - overlap[i] = 0; - } + zones_per_dof = 0; der = 0.0; comp--; @@ -1411,11 +1410,17 @@ void GridFunction::GetDerivative(int comp, int der_comp, GridFunction &der) a += inv_jac(j, der_comp) * pt_grad(j); } der(der_dofs[k]) += a; - overlap[der_dofs[k]]++; + zones_per_dof[der_dofs[k]]++; } } +} - for (i = 0; i < overlap.Size(); i++) +void GridFunction::GetDerivative(int comp, int der_comp, GridFunction &der) +{ + Array overlap; + AccumulateAndCountDerivativeValues(comp, der_comp, der, overlap); + + for (int i = 0; i < overlap.Size(); i++) { der(i) /= overlap[i]; } diff --git a/fem/gridfunc.hpp b/fem/gridfunc.hpp index 3ba3bf38a4..bfc18af6ac 100644 --- a/fem/gridfunc.hpp +++ b/fem/gridfunc.hpp @@ -311,6 +311,16 @@ public: void ProjectVectorFieldOn(GridFunction &vec_field, int comp = 0); + /** @brief Compute a certain derivative of a function's component. + Derivatives of the function are computed at the DOF locations of @a der, + and averaged over overlapping DOFs. Thus this function projects the + derivative to the FiniteElementSpace of @a der. + @param[in] comp Index of the function's component to be differentiated. + The index is 1-based, i.e., use 1 for scalar functions. + @param[in] der_comp Use 0/1/2 for derivatives in x/y/z directions. + @param[out] der The resulting derivative (scalar function). The + FiniteElementSpace of this function must be set + before the call. */ void GetDerivative(int comp, int der_comp, GridFunction &der); double GetDivergence(ElementTransformation &tr) const; @@ -412,6 +422,12 @@ protected: void AccumulateAndCountZones(VectorCoefficient &vcoeff, AvgType type, Array &zones_per_vdof); + /** @brief Used for the serial and parallel implementations of the + GetDerivative() method; see its documentation. */ + void AccumulateAndCountDerivativeValues(int comp, int der_comp, + GridFunction &der, + Array &zones_per_dof); + void AccumulateAndCountBdrValues(Coefficient *coeff[], VectorCoefficient *vcoeff, Array &attr, Array &values_counter); diff --git a/fem/lor.cpp b/fem/lor.cpp index 7428552764..558b565707 100644 --- a/fem/lor.cpp +++ b/fem/lor.cpp @@ -34,7 +34,8 @@ void LORBase::AddIntegratorsAndMarkers(BilinearForm &a_from, BilinearForm &a_to, GetIntegratorsFn get_integrators, GetMarkersFn get_markers, - AddIntegratorMarkersFn add_integrator, + AddIntegratorMarkersFn add_integrator_marker, + AddIntegratorFn add_integrator, const IntegrationRule *ir) { Array *integrators = (a_from.*get_integrators)(); @@ -42,7 +43,14 @@ void LORBase::AddIntegratorsAndMarkers(BilinearForm &a_from, for (int i=0; iSize(); ++i) { - (a_to.*add_integrator)((*integrators)[i], *(*markers[i])); + if (*markers[i]) + { + (a_to.*add_integrator_marker)((*integrators)[i], *(*markers[i])); + } + else + { + (a_to.*add_integrator)((*integrators)[i]); + } ir_map[(*integrators)[i]] = ((*integrators)[i])->GetIntegrationRule(); if (ir) { ((*integrators)[i])->SetIntegrationRule(*ir); } } @@ -92,13 +100,29 @@ void LORBase::ConstructLocalDofPermutation(Array &perm_) const int dim = mesh_lor.Dimension(); const CoarseFineTransformations &cf_tr = mesh_lor.GetRefinementTransforms(); + using GeomRef = std::pair; + std::map point_matrices_offsets; perm_.SetSize(fes_lor.GetVSize()); Array vdof_ho, vdof_lor; for (int ilor=0; ilor &perm_) const continue; } - int p = fes_ho.GetOrder(iho); int p1 = p+1; int ndof_per_dim = (dim == 2) ? p*p1 : type == ND ? p*p1*p1 : p*p*p1; @@ -181,7 +204,7 @@ void LORBase::ConstructLocalDofPermutation(Array &perm_) const void LORBase::ConstructDofPermutation() const { FESpaceType type = GetFESpaceType(); - if (type == H1 || type == L2 || nonconforming) + if (type == H1 || type == L2) { // H1 and L2: no permutation necessary, return identity perm.SetSize(fes->GetTrueVSize()); @@ -226,10 +249,10 @@ const Array &LORBase::GetDofPermutation() const return perm; } -bool LORBase::RequiresDofPermutation() const +bool LORBase::HasSameDofNumbering() const { FESpaceType type = GetFESpaceType(); - return (type == H1 || type == L2 || nonconforming) ? false : true; + return type == H1 || type == L2; } const OperatorHandle &LORBase::GetAssembledSystem() const @@ -238,7 +261,7 @@ const OperatorHandle &LORBase::GetAssembledSystem() const return A; } -void LORBase::AssembleSystem(BilinearForm &a_ho, const Array &ess_dofs) +void LORBase::AssembleSystem_(BilinearForm &a_ho, const Array &ess_dofs) { a->UseExternalIntegrators(); AddIntegrators(a_ho, *a, &BilinearForm::GetDBFI, @@ -247,40 +270,23 @@ void LORBase::AssembleSystem(BilinearForm &a_ho, const Array &ess_dofs) &BilinearForm::AddInteriorFaceIntegrator, ir_face); AddIntegratorsAndMarkers(a_ho, *a, &BilinearForm::GetBBFI, &BilinearForm::GetBBFI_Marker, + &BilinearForm::AddBoundaryIntegrator, &BilinearForm::AddBoundaryIntegrator, ir_face); AddIntegratorsAndMarkers(a_ho, *a, &BilinearForm::GetBFBFI, &BilinearForm::GetBFBFI_Marker, + &BilinearForm::AddBdrFaceIntegrator, &BilinearForm::AddBdrFaceIntegrator, ir_face); a->Assemble(); - if (RequiresDofPermutation()) - { - const Array &p = GetDofPermutation(); - // Form inverse permutation: given high-order dof i, pi[i] is corresp. LO - Array pi(p.Size()); - for (int i=0; i ess_dofs_perm(ess_dofs.Size()); - for (int i=0; iFormSystemMatrix(ess_dofs_perm, A); - } - else - { - a->FormSystemMatrix(ess_dofs, A); - } + a->FormSystemMatrix(ess_dofs, A); ResetIntegrationRules(&BilinearForm::GetDBFI); ResetIntegrationRules(&BilinearForm::GetFBFI); ResetIntegrationRules(&BilinearForm::GetBBFI); ResetIntegrationRules(&BilinearForm::GetBFBFI); } -void LORBase::SetupNonconforming() +void LORBase::SetupProlongationAndRestriction() { - if (RequiresDofPermutation()) + if (!HasSameDofNumbering()) { Array p; ConstructLocalDofPermutation(p); @@ -290,7 +296,6 @@ void LORBase::SetupNonconforming() { fes->CopyProlongationAndRestriction(fes_ho, NULL); } - nonconforming = true; } template @@ -373,7 +378,6 @@ LORDiscretization::LORDiscretization(BilinearForm &a_ho_, int ref_type) : LORDiscretization(*a_ho_.FESpace(), ref_type) { - a = new BilinearForm(fes); AssembleSystem(a_ho_, ess_tdof_list); } @@ -382,23 +386,32 @@ LORDiscretization::LORDiscretization(FiniteElementSpace &fes_ho, { CheckBasisType(fes_ho); - // TODO: support variable-order spaces - MFEM_VERIFY(!fes_ho.IsVariableOrder(), - "Cannot construct LOR operators on variable-order spaces"); - - int order = fes_ho.GetMaxElementOrder(); - if (GetFESpaceType() == L2) { ++order; } - Mesh &mesh_ho = *fes_ho.GetMesh(); - mesh = new Mesh(Mesh::MakeRefined(mesh_ho, order, ref_type)); + // For H1, ND and RT spaces, use refinement = element order, for DG spaces, + // use refinement = element order + 1 (since LOR is p = 0 in this case). + int increment = (GetFESpaceType() == L2) ? 1 : 0; + Array refinements(mesh_ho.GetNE()); + for (int i=0; iClone(GetLOROrder()); fes = new FiniteElementSpace(mesh, fec); - if (fes_ho.Nonconforming()) { SetupNonconforming(); } + SetupProlongationAndRestriction(); A.SetType(Operator::MFEM_SPARSEMAT); } +void LORDiscretization::AssembleSystem(BilinearForm &a_ho, + const Array &ess_dofs) +{ + delete a; + a = new BilinearForm(&GetFESpace()); + AssembleSystem_(a_ho, ess_dofs); +} + SparseMatrix &LORDiscretization::GetAssembledMatrix() const { MFEM_VERIFY(a != NULL && A.Ptr() != NULL, "No LOR system assembled"); @@ -412,7 +425,6 @@ ParLORDiscretization::ParLORDiscretization(ParBilinearForm &a_ho_, int ref_type) : ParLORDiscretization(*a_ho_.ParFESpace(), ref_type) { - a = new ParBilinearForm(static_cast(fes)); AssembleSystem(a_ho_, ess_tdof_list); } @@ -420,7 +432,7 @@ ParLORDiscretization::ParLORDiscretization(ParFiniteElementSpace &fes_ho, int ref_type) : LORBase(fes_ho) { if (fes_ho.GetMyRank() == 0) { CheckBasisType(fes_ho); } - // TODO: support variable-order spaces + // TODO: support variable-order spaces in parallel MFEM_VERIFY(!fes_ho.IsVariableOrder(), "Cannot construct LOR operators on variable-order spaces"); @@ -434,11 +446,19 @@ ParLORDiscretization::ParLORDiscretization(ParFiniteElementSpace &fes_ho, fec = fes_ho.FEColl()->Clone(GetLOROrder()); ParFiniteElementSpace *pfes = new ParFiniteElementSpace(pmesh, fec); fes = pfes; - if (fes_ho.Nonconforming()) { SetupNonconforming(); } + SetupProlongationAndRestriction(); A.SetType(Operator::Hypre_ParCSR); } +void ParLORDiscretization::AssembleSystem(ParBilinearForm &a_ho, + const Array &ess_dofs) +{ + delete a; + a = new ParBilinearForm(&GetParFESpace()); + AssembleSystem_(a_ho, ess_dofs); +} + HypreParMatrix &ParLORDiscretization::GetAssembledMatrix() const { MFEM_VERIFY(a != NULL && A.Ptr() != NULL, "No LOR system assembled"); diff --git a/fem/lor.hpp b/fem/lor.hpp index 08aff63995..2863e86256 100644 --- a/fem/lor.hpp +++ b/fem/lor.hpp @@ -35,7 +35,7 @@ private: /// Adds all the integrators from the BilinearForm @a a_from to @a a_to. If /// the mesh consists of tensor product elements, temporarily changes the /// integration rules of the integrators to use collocated quadrature for - /// better conditioning of the %LOR system. + /// better conditioning of the LOR system. void AddIntegrators(BilinearForm &a_from, BilinearForm &a_to, GetIntegratorsFn get_integrators, @@ -49,11 +49,12 @@ private: BilinearForm &a_to, GetIntegratorsFn get_integrators, GetMarkersFn get_markers, - AddIntegratorMarkersFn add_integrator, + AddIntegratorMarkersFn add_integrator_marker, + AddIntegratorFn add_integrator, const IntegrationRule *ir); /// Resets the integration rules of the integrators of @a a to their original - /// values (after temporarily changing them for %LOR assembly). + /// values (after temporarily changing them for LOR assembly). void ResetIntegrationRules(GetIntegratorsFn get_integrators); static inline int absdof(int i) { return i < 0 ? -1-i : i; } @@ -68,37 +69,42 @@ protected: BilinearForm *a; OperatorHandle A; mutable Array perm; - bool nonconforming = false; /// Constructs the local DOF (ldof) permutation. In parallel this is used as /// an intermediate step in computing the DOF permutation (see /// ConstructDofPermutation and GetDofPermutation). void ConstructLocalDofPermutation(Array &perm_) const; - /// Construct the permutation that maps %LOR DOFs to high-order DOFs. See + /// Construct the permutation that maps LOR DOFs to high-order DOFs. See /// GetDofPermutation. void ConstructDofPermutation() const; - /// Sets up the prolongation and restriction operators required for - /// nonconforming spaces. - void SetupNonconforming(); + /// Returns true if the LOR space and HO space have the same DOF numbering + /// (H1 or L2 spaces), false otherwise (ND or RT spaces). + bool HasSameDofNumbering() const; + + /// Sets up the prolongation and restriction operators required in the case + /// of different DOF numberings (ND or RT spaces) or nonconforming spaces. + void SetupProlongationAndRestriction(); /// Returns the type of finite element space: H1, ND, RT or L2. FESpaceType GetFESpaceType() const; - /// Returns the order of the %LOR space. 1 for H1 or ND, 0 for L2 or RT. + /// Returns the order of the LOR space. 1 for H1 or ND, 0 for L2 or RT. int GetLOROrder() const; + /// Assembles the LOR system (used internally by + /// LORDiscretization::AssembleSystem and + /// ParLORDiscretization::AssembleSystem). + void AssembleSystem_(BilinearForm &a_ho, const Array &ess_dofs); + LORBase(FiniteElementSpace &fes_ho_); public: - /// Returns the assembled %LOR system. + /// Returns the assembled LOR system. const OperatorHandle &GetAssembledSystem() const; - /// Assembles the %LOR system. - void AssembleSystem(BilinearForm &a_ho, const Array &ess_dofs); - - /// @brief Returns the permutation that maps %LOR DOFs to high-order DOFs. + /// @brief Returns the permutation that maps LOR DOFs to high-order DOFs. /// /// This permutation is constructed the first time it is requested, and then /// is cached. For H1 and L2 finite element spaces (or for nonconforming @@ -108,16 +114,9 @@ public: /// /// For vector finite element spaces (ND and RT), the DOF permutation is /// nontrivial. Returns an array @a perm such that, given an index @a i of a - /// %LOR dof, @a perm[i] is the index of the corresponding HO dof. + /// LOR dof, @a perm[i] is the index of the corresponding HO dof. const Array &GetDofPermutation() const; - /// Returns true if the %LOR spaces requires a DOF permutation (if the - /// corresponding %LOR and HO DOFs are numbered differently), false - /// otherwise. Note: permutations are not required in the case of - /// nonconforming spaces, since the DOF numbering is incorporated into the - /// prolongation operators. - bool RequiresDofPermutation() const; - /// Returns the low-order refined finite element space. FiniteElementSpace &GetFESpace() const { return *fes; } @@ -144,7 +143,10 @@ public: LORDiscretization(FiniteElementSpace &fes_ho, int ref_type=BasisType::GaussLobatto); - /// Return the assembled %LOR operator as a SparseMatrix. + /// Assembles the LOR system corresponding to @a a_ho. + void AssembleSystem(BilinearForm &a_ho, const Array &ess_dofs); + + /// Return the assembled LOR operator as a SparseMatrix. SparseMatrix &GetAssembledMatrix() const; }; @@ -170,10 +172,13 @@ public: ParLORDiscretization(ParFiniteElementSpace &fes_ho, int ref_type=BasisType::GaussLobatto); - /// Return the assembled %LOR operator as a HypreParMatrix. + /// Assembles the LOR system corresponding to @a a_ho. + void AssembleSystem(ParBilinearForm &a_ho, const Array &ess_dofs); + + /// Return the assembled LOR operator as a HypreParMatrix. HypreParMatrix &GetAssembledMatrix() const; - /// Return the %LOR ParFiniteElementSpace. + /// Return the LOR ParFiniteElementSpace. ParFiniteElementSpace &GetParFESpace() const; }; @@ -192,12 +197,11 @@ class LORSolver : public Solver protected: LORBase *lor; bool own_lor = true; - bool use_permutation = true; SolverType solver; mutable Vector px, py; public: /// @brief Create a solver of type @a SolverType, formed using the assembled - /// SparseMatrix of the %LOR version of @a a_ho. @see LORDiscretization + /// SparseMatrix of the LOR version of @a a_ho. @see LORDiscretization LORSolver(BilinearForm &a_ho, const Array &ess_tdof_list, int ref_type=BasisType::GaussLobatto) { @@ -207,7 +211,7 @@ public: #ifdef MFEM_USE_MPI /// @brief Create a solver of type @a SolverType, formed using the assembled - /// HypreParMatrix of the %LOR version of @a a_ho. @see ParLORDiscretization + /// HypreParMatrix of the LOR version of @a a_ho. @see ParLORDiscretization LORSolver(ParBilinearForm &a_ho, const Array &ess_tdof_list, int ref_type=BasisType::GaussLobatto) { @@ -218,8 +222,6 @@ public: /// @brief Create a solver of type @a SolverType using Operator @a op and /// arguments @a args. - /// - /// The object @a lor_ will be used for DOF permutations. template LORSolver(const Operator &op, LORBase &lor_, Args&&... args) : solver(args...) { @@ -228,7 +230,7 @@ public: SetOperator(op); } - /// @brief Create a solver of type @a SolverType using the assembled %LOR + /// @brief Create a solver of type @a SolverType using the assembled LOR /// operator represented by @a lor_. /// /// The given @a args will be used as arguments to the solver constructor. @@ -243,42 +245,7 @@ public: height = solver.Height(); } - void Mult(const Vector &x, Vector &y) const - { - if (use_permutation && lor->RequiresDofPermutation()) - { - const Array &p = lor->GetDofPermutation(); - px.SetSize(x.Size()); - py.SetSize(y.Size()); - for (int i=0; iGetNE(); i++) { fe = fes->GetFE(i); - fes->GetElementVDofs(i, vdofs); + doftrans = fes->GetElementVDofs(i, vdofs); T = fes->GetElementTransformation(i); x.GetSubVector(vdofs, el_x); + if (doftrans) {doftrans->InvTransformPrimal(el_x); } for (int k = 0; k < dnfi.Size(); k++) { energy += dnfi[k]->GetElementEnergy(*fe, *T, el_x); @@ -166,6 +168,7 @@ void NonlinearForm::Mult(const Vector &x, Vector &y) const Vector el_x, el_y; const FiniteElement *fe; ElementTransformation *T; + DofTransformation *doftrans; Mesh *mesh = fes->GetMesh(); py = 0.0; @@ -175,12 +178,14 @@ void NonlinearForm::Mult(const Vector &x, Vector &y) const for (int i = 0; i < fes->GetNE(); i++) { fe = fes->GetFE(i); - fes->GetElementVDofs(i, vdofs); + doftrans = fes->GetElementVDofs(i, vdofs); T = fes->GetElementTransformation(i); px.GetSubVector(vdofs, el_x); + if (doftrans) {doftrans->InvTransformPrimal(el_x); } for (int k = 0; k < dnfi.Size(); k++) { dnfi[k]->AssembleElementVector(*fe, *T, el_x, el_y); + if (doftrans) {doftrans->TransformDual(el_y); } py.AddElementVector(vdofs, el_y); } } @@ -302,6 +307,7 @@ Operator &NonlinearForm::GetGradient(const Vector &x) const DenseMatrix elmat; const FiniteElement *fe; ElementTransformation *T; + DofTransformation *doftrans; Mesh *mesh = fes->GetMesh(); const Vector &px = Prolongate(x); @@ -319,12 +325,14 @@ Operator &NonlinearForm::GetGradient(const Vector &x) const for (int i = 0; i < fes->GetNE(); i++) { fe = fes->GetFE(i); - fes->GetElementVDofs(i, vdofs); + doftrans = fes->GetElementVDofs(i, vdofs); T = fes->GetElementTransformation(i); px.GetSubVector(vdofs, el_x); + if (doftrans) {doftrans->InvTransformPrimal(el_x); } for (int k = 0; k < dnfi.Size(); k++) { dnfi[k]->AssembleElementGrad(*fe, *T, el_x, elmat); + if (doftrans) { doftrans->TransformDual(elmat); } Grad->AddSubMatrix(vdofs, vdofs, elmat, skip_zeros); // Grad->AddSubMatrix(vdofs, vdofs, elmat, 1); } @@ -583,6 +591,7 @@ double BlockNonlinearForm::GetEnergyBlocked(const BlockVector &bx) const Array el_x_const(fes.Size()); Array fe(fes.Size()); ElementTransformation *T; + DofTransformation *doftrans; double energy = 0.0; for (int i=0; iGetFE(i); - fes[s]->GetElementVDofs(i, *vdofs[s]); + doftrans = fes[s]->GetElementVDofs(i, *vdofs[s]); bx.GetBlock(s).GetSubVector(*vdofs[s], *el_x[s]); + if (doftrans) {doftrans->InvTransformPrimal(*el_x[s]); } } for (int k = 0; k < dnfi.Size(); ++k) @@ -645,6 +655,7 @@ void BlockNonlinearForm::MultBlocked(const BlockVector &bx, Array fe(fes.Size()); Array fe2(fes.Size()); ElementTransformation *T; + Array doftrans(fes.Size()); doftrans = nullptr; by.UseDevice(true); by = 0.0; @@ -664,9 +675,10 @@ void BlockNonlinearForm::MultBlocked(const BlockVector &bx, T = fes[0]->GetElementTransformation(i); for (int s = 0; s < fes.Size(); ++s) { - fes[s]->GetElementVDofs(i, *(vdofs[s])); + doftrans[s] = fes[s]->GetElementVDofs(i, *(vdofs[s])); fe[s] = fes[s]->GetFE(i); bx.GetBlock(s).GetSubVector(*(vdofs[s]), *el_x[s]); + if (doftrans[s]) {doftrans[s]->InvTransformPrimal(*el_x[s]); } } for (int k = 0; k < dnfi.Size(); ++k) @@ -677,6 +689,7 @@ void BlockNonlinearForm::MultBlocked(const BlockVector &bx, for (int s=0; sSize() == 0) { continue; } + if (doftrans[s]) {doftrans[s]->TransformDual(*el_y[s]); } by.GetBlock(s).AddElementVector(*(vdofs[s]), *el_y[s]); } } @@ -844,6 +857,7 @@ void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const Arrayfe(fes.Size()); Arrayfe2(fes.Size()); ElementTransformation * T; + Array doftrans(fes.Size()); doftrans = nullptr; for (int i=0; iGetFE(i); - fes[s]->GetElementVDofs(i, *vdofs[s]); + doftrans[s] = fes[s]->GetElementVDofs(i, *vdofs[s]); bx.GetBlock(s).GetSubVector(*vdofs[s], *el_x[s]); + if (doftrans[s]) {doftrans[s]->InvTransformPrimal(*el_x[s]); } } for (int k = 0; k < dnfi.Size(); ++k) @@ -893,6 +908,10 @@ void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const for (int l=0; lHeight() == 0) { continue; } + if (doftrans[j] || doftrans[l]) + { + TransformDual(doftrans[j], doftrans[l], *elmats(j,l)); + } Grads(j,l)->AddSubMatrix(*vdofs[j], *vdofs[l], *elmats(j,l), skip_zeros); } diff --git a/fem/pfespace.cpp b/fem/pfespace.cpp index 7e6f253186..20707693d0 100644 --- a/fem/pfespace.cpp +++ b/fem/pfespace.cpp @@ -3195,8 +3195,11 @@ void ParFiniteElementSpace::CopyProlongationAndRestriction( SparseMatrix *perm_mat = NULL, *perm_mat_tr = NULL; if (perm) { + // Note: although n and fes.GetVSize() are typically equal, in + // variable-order spaces they may differ, since nonconforming edges/faces + // my have fictitious DOFs. int n = perm->Size(); - perm_mat = new SparseMatrix(n, n); + perm_mat = new SparseMatrix(n, fes.GetVSize()); for (int i=0; iP); } nonconf_P = true; } + else if (perm != NULL) + { + HYPRE_BigInt glob_nrows = GlobalVSize(); + HYPRE_BigInt glob_ncols = GlobalTrueVSize(); + HYPRE_BigInt *col_starts = GetTrueDofOffsets(); + HYPRE_BigInt *row_starts = GetDofOffsets(); + P = new HypreParMatrix(MyComm, glob_nrows, glob_ncols, row_starts, + col_starts, perm_mat); + nonconf_P = true; + } if (pfes->R != NULL) { if (perm) { R = Mult(*pfes->R, *perm_mat_tr); } else { R = new SparseMatrix(*pfes->R); } } + else if (perm != NULL) + { + R = perm_mat_tr; + perm_mat_tr = NULL; + } delete perm_mat; delete perm_mat_tr; diff --git a/fem/pgridfunc.cpp b/fem/pgridfunc.cpp index 0925a77115..7551b3ab69 100644 --- a/fem/pgridfunc.cpp +++ b/fem/pgridfunc.cpp @@ -481,6 +481,27 @@ void ParGridFunction::GetVectorValue(ElementTransformation &T, } } +void ParGridFunction::GetDerivative(int comp, int der_comp, + ParGridFunction &der) +{ + Array overlap; + AccumulateAndCountDerivativeValues(comp, der_comp, der, overlap); + + // Count the zones globally. + GroupCommunicator &gcomm = der.ParFESpace()->GroupComm(); + gcomm.Reduce(overlap, GroupCommunicator::Sum); + gcomm.Bcast(overlap); + + // Accumulate for all dofs. + gcomm.Reduce(der.HostReadWrite(), GroupCommunicator::Sum); + gcomm.Bcast(der.HostReadWrite()); + + for (int i = 0; i < overlap.Size(); i++) + { + der(i) /= overlap[i]; + } +} + void ParGridFunction::GetElementDofValues(int el, Vector &dof_vals) const { int ne = fes->GetNE(); diff --git a/fem/pgridfunc.hpp b/fem/pgridfunc.hpp index 8a15eae840..3841dfba53 100644 --- a/fem/pgridfunc.hpp +++ b/fem/pgridfunc.hpp @@ -226,6 +226,9 @@ public: const IntegrationPoint &ip, Vector &val, Vector *tr = NULL) const; + /// Parallel version of GridFunction::GetDerivative(); see its documentation. + void GetDerivative(int comp, int der_comp, ParGridFunction &der); + /** Sets the output vector @a dof_vals to the values of the degrees of freedom of element @a el. If @a el is greater than or equal to the number of local elements, it will be interpreted as a shifted index of a face diff --git a/fem/tmop.cpp b/fem/tmop.cpp index e1127bd1f6..7e4b445190 100644 --- a/fem/tmop.cpp +++ b/fem/tmop.cpp @@ -2326,6 +2326,8 @@ TMOP_Integrator::~TMOP_Integrator() { delete lim_func; delete zeta; + delete sigma; + delete sigma_bar; for (int i = 0; i < ElemDer.Size(); i++) { delete ElemDer[i]; @@ -2393,6 +2395,87 @@ void TMOP_Integrator::EnableAdaptiveLimiting(const ParGridFunction &z0, } #endif +void TMOP_Integrator::EnableSurfaceFitting(const GridFunction &s0, + const Array &smarker, + Coefficient &coeff, + AdaptivityEvaluator &ae) +{ + delete sigma; + sigma = new GridFunction(s0); + sigma_marker = &smarker; + coeff_sigma = &coeff; + sigma_eval = &ae; + + // Compute the restricted sigma. + delete sigma_bar; + sigma_bar = new GridFunction(*sigma); + for (int i = 0; i < sigma_marker->Size(); i++) + { + if ((*sigma_marker)[i] == false) { (*sigma_bar)(i) = 0.0; } + } + + sigma_eval->SetSerialMetaInfo(*s0.FESpace()->GetMesh(), + *s0.FESpace()->FEColl(), 1); + sigma_eval->SetInitialField + (*sigma->FESpace()->GetMesh()->GetNodes(), *sigma); +} + +#ifdef MFEM_USE_MPI +void TMOP_Integrator::EnableSurfaceFitting(const ParGridFunction &s0, + const Array &smarker, + Coefficient &coeff, + AdaptivityEvaluator &ae) +{ + delete sigma; + sigma = new GridFunction(s0); + sigma_marker = &smarker; + coeff_sigma = &coeff; + sigma_eval = &ae; + + // Compute the restricted sigma. + delete sigma_bar; + sigma_bar = new GridFunction(*sigma); + for (int i = 0; i < sigma_marker->Size(); i++) + { + if ((*sigma_marker)[i] == false) { (*sigma_bar)(i) = 0.0; } + } + + sigma_eval->SetParMetaInfo(*s0.ParFESpace()->GetParMesh(), + *s0.ParFESpace()->FEColl(), 1); + sigma_eval->SetInitialField + (*sigma->FESpace()->GetMesh()->GetNodes(), *sigma); +} +#endif + +void TMOP_Integrator::GetSurfaceFittingErrors(double &err_avg, double &err_max) +{ + MFEM_VERIFY(sigma, "Surface fitting has not been enabled."); + + int loc_cnt = 0; + double loc_max = 0.0, loc_sum = 0.0; + for (int i = 0; i < sigma_marker->Size(); i++) + { + if ((*sigma_marker)[i] == true) + { + loc_cnt++; + loc_max = std::max(loc_max, std::abs((*sigma_bar)(i))); + loc_sum += std::abs((*sigma_bar)(i)); + } + } + err_avg = loc_sum / loc_cnt; + err_max = loc_max; + +#ifdef MFEM_USE_MPI + if (targetC->Parallel() == false) { return; } + int glob_cnt; + MPI_Comm comm = targetC->GetComm(); + MPI_Allreduce(&loc_max, &err_max, 1, MPI_DOUBLE, MPI_MAX, comm); + MPI_Allreduce(&loc_cnt, &glob_cnt, 1, MPI_INT, MPI_SUM, comm); + MPI_Allreduce(&loc_sum, &err_avg, 1, MPI_DOUBLE, MPI_SUM, comm); + err_avg = err_avg / glob_cnt; +#endif +} + void TMOP_Integrator::UpdateAfterMeshTopologyChange() { if (zeta) @@ -2419,16 +2502,19 @@ void TMOP_Integrator::ParUpdateAfterMeshTopologyChange() } #endif - double TMOP_Integrator::GetElementEnergy(const FiniteElement &el, ElementTransformation &T, const Vector &elfun) { const int dof = el.GetDof(), dim = el.GetDim(); + const int el_id = T.ElementNo; double energy; - // No adaptive limiting terms if this is a FD computation. + // No adaptive limiting / surface fitting terms if the function is called + // as part of a FD derivative computation (because we include the exact + // derivatives of these terms in FD computations). const bool adaptive_limiting = (zeta && fd_call_flag == false); + const bool surface_fit = (sigma && fd_call_flag == false); DSh.SetSize(dof, dim); Jrt.SetSize(dim); @@ -2440,7 +2526,7 @@ double TMOP_Integrator::GetElementEnergy(const FiniteElement &el, energy = 0.0; DenseTensor Jtr(dim, dim, ir.GetNPoints()); - targetC->ComputeElementTargets(T.ElementNo, el, ir, elfun, Jtr); + targetC->ComputeElementTargets(el_id, el, ir, elfun, Jtr); // Limited case. Vector shape, p, p0, d_vals; @@ -2453,11 +2539,11 @@ double TMOP_Integrator::GetElementEnergy(const FiniteElement &el, pos0.SetSize(dof, dim); Vector pos0V(pos0.Data(), dof * dim); Array pos_dofs; - nodes0->FESpace()->GetElementVDofs(T.ElementNo, pos_dofs); + nodes0->FESpace()->GetElementVDofs(el_id, pos_dofs); nodes0->GetSubVector(pos_dofs, pos0V); if (lim_dist) { - lim_dist->GetValues(T.ElementNo, ir, d_vals); + lim_dist->GetValues(el_id, ir, d_vals); } else { @@ -2467,11 +2553,11 @@ double TMOP_Integrator::GetElementEnergy(const FiniteElement &el, // Define ref->physical transformation, when a Coefficient is specified. IsoparametricTransformation *Tpr = NULL; - if (coeff1 || coeff0 || adaptive_limiting) + if (coeff1 || coeff0 || adaptive_limiting || surface_fit) { Tpr = new IsoparametricTransformation; Tpr->SetFE(&el); - Tpr->ElementNo = T.ElementNo; + Tpr->ElementNo = el_id; Tpr->ElementType = ElementTransformation::ELEMENT; Tpr->Attribute = T.Attribute; Tpr->GetPointMat().Transpose(PMatI); // PointMat = PMatI^T @@ -2487,13 +2573,17 @@ double TMOP_Integrator::GetElementEnergy(const FiniteElement &el, Vector zeta_q, zeta0_q; if (adaptive_limiting) { - zeta->GetValues(T.ElementNo, ir, zeta_q); - zeta_0->GetValues(T.ElementNo, ir, zeta0_q); + zeta->GetValues(el_id, ir, zeta_q); + zeta_0->GetValues(el_id, ir, zeta0_q); } + Vector sigma_bar_q; + if (surface_fit) { sigma_bar->GetValues(el_id, ir, sigma_bar_q); } + for (int i = 0; i < ir.GetNPoints(); i++) { const IntegrationPoint &ip = ir.IntPoint(i); + const DenseMatrix &Jtr_i = Jtr(i); metric->SetTargetJacobian(Jtr_i); CalcInverse(Jtr_i, Jrt); @@ -2516,16 +2606,24 @@ double TMOP_Integrator::GetElementEnergy(const FiniteElement &el, coeff0->Eval(*Tpr, ip); } + // Contribution from the adaptive limiting term. if (adaptive_limiting) { const double diff = zeta_q(i) - zeta0_q(i); val += coeff_zeta->Eval(*Tpr, ip) * lim_normal * diff * diff; } + // Contribution from the surface fitting term. + if (surface_fit) + { + val += coeff_sigma->Eval(*Tpr, ip) * sigma_normal * + sigma_bar_q(i) * sigma_bar_q(i); + } + energy += weight * val; } - delete Tpr; + delete Tpr; return energy; } @@ -2747,7 +2845,7 @@ void TMOP_Integrator::AssembleElementVectorExact(const FiniteElement &el, // Define ref->physical transformation, when a Coefficient is specified. IsoparametricTransformation *Tpr = NULL; - if (coeff1 || coeff0 || zeta || exact_action) + if (coeff1 || coeff0 || zeta || sigma || exact_action) { Tpr = new IsoparametricTransformation; Tpr->SetFE(&el); @@ -2829,7 +2927,8 @@ void TMOP_Integrator::AssembleElementVectorExact(const FiniteElement &el, } } - if (zeta) { AssembleElemVecAdaptLim(el, weights, *Tpr, ir, PMatO); } + if (zeta) { AssembleElemVecAdaptLim(el, *Tpr, ir, weights, PMatO); } + if (sigma) { AssembleElemVecSurfFit(el, *Tpr, ir, weights, PMatO); } delete Tpr; } @@ -2881,7 +2980,7 @@ void TMOP_Integrator::AssembleElementGradExact(const FiniteElement &el, // Define ref->physical transformation, when a Coefficient is specified. IsoparametricTransformation *Tpr = NULL; - if (coeff1 || coeff0 || zeta) + if (coeff1 || coeff0 || zeta || sigma) { Tpr = new IsoparametricTransformation; Tpr->SetFE(&el); @@ -2935,21 +3034,20 @@ void TMOP_Integrator::AssembleElementGradExact(const FiniteElement &el, } } - if (zeta) { AssembleElemGradAdaptLim(el, weights, *Tpr, ir, elmat); } + if (zeta) { AssembleElemGradAdaptLim(el, *Tpr, ir, weights, elmat); } + if (sigma) { AssembleElemGradSurfFit(el, *Tpr, ir, weights, elmat); } delete Tpr; } void TMOP_Integrator::AssembleElemVecAdaptLim(const FiniteElement &el, - const Vector &weights, IsoparametricTransformation &Tpr, const IntegrationRule &ir, + const Vector &weights, DenseMatrix &mat) { - if (zeta == NULL) { return; } - - const int dof = el.GetDof(), dim = el.GetDim(); - Vector shape(dof), zeta_e, zeta_q, zeta0_q; + const int dof = el.GetDof(), dim = el.GetDim(), nqp = weights.Size(); + Vector shape(dof), zeta_e, zeta_q, zeta0_q(nqp); Array dofs; zeta->FESpace()->GetElementDofs(Tpr.ElementNo, dofs); @@ -2967,7 +3065,6 @@ void TMOP_Integrator::AssembleElemVecAdaptLim(const FiniteElement &el, Vector zeta_grad_q(dim); - const int nqp = weights.Size(); for (int q = 0; q < nqp; q++) { const IntegrationPoint &ip = ir.IntPoint(q); @@ -2980,15 +3077,13 @@ void TMOP_Integrator::AssembleElemVecAdaptLim(const FiniteElement &el, } void TMOP_Integrator::AssembleElemGradAdaptLim(const FiniteElement &el, - const Vector &weights, IsoparametricTransformation &Tpr, const IntegrationRule &ir, + const Vector &weights, DenseMatrix &mat) { - if (zeta == NULL) { return; } - - const int dof = el.GetDof(), dim = el.GetDim(); - Vector shape(dof), zeta_e, zeta_q, zeta0_q; + const int dof = el.GetDof(), dim = el.GetDim(), nqp = weights.Size(); + Vector shape(dof), zeta_e, zeta_q, zeta0_q(nqp); Array dofs; zeta->FESpace()->GetElementDofs(Tpr.ElementNo, dofs); @@ -3014,7 +3109,6 @@ void TMOP_Integrator::AssembleElemGradAdaptLim(const FiniteElement &el, Vector zeta_grad_q(dim); DenseMatrix zeta_grad_grad_q(dim, dim); - const int nqp = weights.Size(); for (int q = 0; q < nqp; q++) { const IntegrationPoint &ip = ir.IntPoint(q); @@ -3043,6 +3137,169 @@ void TMOP_Integrator::AssembleElemGradAdaptLim(const FiniteElement &el, } } +void TMOP_Integrator::AssembleElemVecSurfFit(const FiniteElement &el_x, + IsoparametricTransformation &Tpr, + const IntegrationRule &ir_quad, + const Vector &weights, + DenseMatrix &mat) +{ + const int el_id = Tpr.ElementNo; + const FiniteElement &el_s = *sigma->FESpace()->GetFE(el_id); + + const int dof_x = el_x.GetDof(), dim = el_x.GetDim(), + dof_s = el_s.GetDof(), nqp = ir_quad.GetNPoints(); + + Vector sigma_e, sigma_bar_e; + Vector sigma_bar_q; + Array dofs; + sigma->FESpace()->GetElementDofs(el_id, dofs); + sigma->GetSubVector(dofs, sigma_e); + sigma_bar->GetSubVector(dofs, sigma_bar_e); + sigma_bar->GetValues(el_id, ir_quad, sigma_bar_q); + + // Project the gradient of sigma in the same space. + // The FE coefficients of the gradient go in sigma_grad_e. + DenseMatrix sigma_grad_e(dof_s, dim); + DenseMatrix grad_phys; // This will be (dof x dim, dof). + el_s.ProjectGrad(el_s, Tpr, grad_phys); + Vector grad_ptr(sigma_grad_e.GetData(), dof_s * dim); + grad_phys.Mult(sigma_e, grad_ptr); + + // Gradient of sigma_bar. + DenseMatrix sigma_bar_grad_e(dof_s, dim); + Vector ptr(sigma_bar_grad_e.GetData(), dof_s * dim); + grad_phys.Mult(sigma_bar_e, ptr); + + Vector shape_x(dof_x), shape_s(dof_s), grad_q(dim); + + for (int q = 0; q < nqp; q++) + { + const IntegrationPoint &ip = ir_quad.IntPoint(q); + Tpr.SetIntPoint(&ip); + el_s.CalcShape(ip, shape_s); + + // Grad of sigma_bar at the current quad point. + sigma_bar_grad_e.MultTranspose(shape_s, grad_q); + + for (int s = 0; s < dof_s; s++) + { + if ((*sigma_marker)[dofs[s]] == false) { continue; } + + for (int d = 0; d < dim; d++) + { + // Grad of sigma must be taken at the active DOFs. + grad_q(d) += sigma_grad_e(s, d) * shape_s(s); + } + } + + grad_q *= 2.0 * sigma_normal * coeff_sigma->Eval(Tpr, ip) * + weights(q) * sigma_bar_q(q); + + el_x.CalcShape(ip, shape_x); + AddMultVWt(shape_x, grad_q, mat); + } +} + +void TMOP_Integrator::AssembleElemGradSurfFit(const FiniteElement &el_x, + IsoparametricTransformation &Tpr, + const IntegrationRule &ir_quad, + const Vector &weights, + DenseMatrix &mat) +{ + const int el_id = Tpr.ElementNo, nqp = ir_quad.GetNPoints(); + const FiniteElement &el_s = *sigma->FESpace()->GetFE(el_id); + + const int dof_x = el_x.GetDof(), dim = el_x.GetDim(), + dof_s = el_s.GetDof(); + + Vector sigma_e, sigma_bar_e; + Vector sigma_bar_q; + + Array dofs; + sigma->FESpace()->GetElementDofs(el_id, dofs); + sigma->GetSubVector(dofs, sigma_e); + sigma_bar->GetSubVector(dofs, sigma_bar_e); + sigma_bar->GetValues(el_id, ir_quad, sigma_bar_q); + + // Project the gradient of sigma in the same space. + // The FE coefficients of the gradient go in sigma_grad_e. + DenseMatrix sigma_grad_e(dof_s, dim); + DenseMatrix grad_phys; // This will be (dof x dim, dof). + el_s.ProjectGrad(el_s, Tpr, grad_phys); + Vector grad_ptr(sigma_grad_e.GetData(), dof_s * dim); + grad_phys.Mult(sigma_e, grad_ptr); + + // Gradient of sigma_bar. + DenseMatrix sigma_bar_grad_e(dof_s, dim); + Vector ptr(sigma_bar_grad_e.GetData(), dof_s * dim); + grad_phys.Mult(sigma_bar_e, ptr); + + // Project the gradient of each gradient of sigma in the same space. + // The FE coefficients of the second derivatives go in sigma_grad_grad_e. + DenseMatrix sigma_grad_grad_e(dof_s * dim, dim); + Mult(grad_phys, sigma_grad_e, sigma_grad_grad_e); + + // Project the gradient of each gradient of sigma in the same space. + // The FE coefficients of the second derivatives go in sigma_grad_grad_e. + DenseMatrix sigma_bar_grad_grad_e(dof_s * dim, dim); + Mult(grad_phys, sigma_bar_grad_e, sigma_bar_grad_grad_e); + // Reshape to be more convenient later (no change in the data). + sigma_bar_grad_grad_e.SetSize(dof_s, dim * dim); + + DenseMatrix sigma_bar_grad_grad_q(dim, dim); + + Vector shape_x(dof_x), shape_s(dof_s), sigma_bar_grad_q(dim); + DenseMatrix dshape_s(dof_s, dim); + + for (int q = 0; q < nqp; q++) + { + const IntegrationPoint &ip = ir_quad.IntPoint(q); + Tpr.SetIntPoint(&ip); + el_s.CalcShape(ip, shape_s); + el_x.CalcShape(ip, shape_x); + // We could reuse grad_phys, but this is more accurate. + el_s.CalcPhysDShape(Tpr, dshape_s); + + // Grad of sigma_bar at the current quad point. + sigma_bar_grad_e.MultTranspose(shape_s, sigma_bar_grad_q); + + // Grad-grad of sigma_bar at the current quad point. + Vector gg_ptr(sigma_bar_grad_grad_q.GetData(), dim * dim); + sigma_bar_grad_grad_e.MultTranspose(shape_s, gg_ptr); + + // Loops over the local matrix. + const double w = 2.0 * sigma_normal * + coeff_sigma->Eval(Tpr, ip) * weights(q); + for (int i = 0; i < dof_x * dim; i++) + { + const int idof = i % dof_x, idim = i / dof_x; + for (int j = 0; j <= i; j++) + { + const int jdof = j % dof_x, jdim = j / dof_x; + + double Di = sigma_bar_grad_q(idim), + Dj = sigma_bar_grad_q(jdim), + DD = sigma_bar_grad_grad_q(idim, jdim); + for (int s = 0; s < dof_s; s++) + { + if ((*sigma_marker)[dofs[s]] == false) { continue; } + + Di += sigma_grad_e(s, idim) * shape_s(s); + Dj += sigma_grad_e(s, jdim) * shape_s(s); + DD += sigma_grad_e(s, idim) * dshape_s(s, jdim) + + sigma_grad_grad_e(dof_s * idim + s, jdim) * shape_s(s) + + sigma_grad_e(s, jdim) * dshape_s(s, idim); + } + const double entry = w * (Di * Dj + sigma_bar_q(q) * DD) * + shape_x(idof) * shape_x(jdof); + + mat(i, j) += entry; + if (i != j) { mat(j, i) += entry; } + } + } + } +} + double TMOP_Integrator::GetFDDerivative(const FiniteElement &el, ElementTransformation &T, Vector &elfun, const int dofidx, @@ -3103,8 +3360,8 @@ void TMOP_Integrator::AssembleElementVectorFD(const FiniteElement &el, } fd_call_flag = false; - // Contributions from adaptive limiting (exact derivatives). - if (zeta) + // Contributions from adaptive limiting, surface fitting (exact derivatives). + if (zeta || sigma) { const IntegrationRule &ir = ActionIntegrationRule(el); const int nqp = ir.GetNPoints(); @@ -3125,7 +3382,8 @@ void TMOP_Integrator::AssembleElementVectorFD(const FiniteElement &el, } PMatO.UseExternalData(elvect.GetData(), dof, dim); - AssembleElemVecAdaptLim(el, weights, Tpr, ir, PMatO); + if (zeta) { AssembleElemVecAdaptLim(el, Tpr, ir, weights, PMatO); } + if (sigma) { AssembleElemVecSurfFit(el, Tpr, ir, weights, PMatO); } } } @@ -3200,7 +3458,7 @@ void TMOP_Integrator::AssembleElementGradFD(const FiniteElement &el, fd_call_flag = false; // Contributions from adaptive limiting. - if (zeta) + if (zeta || sigma) { const IntegrationRule &ir = GradientIntegrationRule(el); const int nqp = ir.GetNPoints(); @@ -3220,35 +3478,41 @@ void TMOP_Integrator::AssembleElementGradFD(const FiniteElement &el, weights(q) = ir.IntPoint(q).weight * Jtr(q).Det(); } - AssembleElemGradAdaptLim(el, weights, Tpr, ir, elmat); + if (zeta) { AssembleElemGradAdaptLim(el, Tpr, ir, weights, elmat); } + if (sigma) { AssembleElemGradSurfFit(el, Tpr, ir, weights, elmat); } } } void TMOP_Integrator::EnableNormalization(const GridFunction &x) { - ComputeNormalizationEnergies(x, metric_normal, lim_normal); + ComputeNormalizationEnergies(x, metric_normal, lim_normal, sigma_normal); metric_normal = 1.0 / metric_normal; lim_normal = 1.0 / lim_normal; + //if (sigma) { sigma_normal = 1.0 / sigma_normal; } + if (sigma) { sigma_normal = lim_normal; } } #ifdef MFEM_USE_MPI void TMOP_Integrator::ParEnableNormalization(const ParGridFunction &x) { - double loc[2]; - ComputeNormalizationEnergies(x, loc[0], loc[1]); - double rdc[2]; - MPI_Allreduce(loc, rdc, 2, MPI_DOUBLE, MPI_SUM, x.ParFESpace()->GetComm()); + double loc[3]; + ComputeNormalizationEnergies(x, loc[0], loc[1], loc[2]); + double rdc[3]; + MPI_Allreduce(loc, rdc, 3, MPI_DOUBLE, MPI_SUM, x.ParFESpace()->GetComm()); metric_normal = 1.0 / rdc[0]; lim_normal = 1.0 / rdc[1]; + // if (sigma) { sigma_normal = 1.0 / rdc[2]; } + if (sigma) { sigma_normal = lim_normal; } } #endif void TMOP_Integrator::ComputeNormalizationEnergies(const GridFunction &x, double &metric_energy, - double &lim_energy) + double &lim_energy, + double &sigma_energy) { Array vdofs; - Vector x_vals; + Vector x_vals, sigma_bar_q; const FiniteElementSpace* const fes = x.FESpace(); const int dim = fes->GetMesh()->Dimension(); @@ -3258,6 +3522,7 @@ void TMOP_Integrator::ComputeNormalizationEnergies(const GridFunction &x, metric_energy = 0.0; lim_energy = 0.0; + sigma_energy = 0.0; for (int i = 0; i < fes->GetNE(); i++) { const FiniteElement *fe = fes->GetFE(i); @@ -3273,6 +3538,8 @@ void TMOP_Integrator::ComputeNormalizationEnergies(const GridFunction &x, targetC->ComputeElementTargets(i, *fe, ir, x_vals, Jtr); + if (sigma) { sigma_bar->GetValues(i, ir, sigma_bar_q); } + for (int q = 0; q < nqp; q++) { const IntegrationPoint &ip = ir.IntPoint(q); @@ -3286,8 +3553,15 @@ void TMOP_Integrator::ComputeNormalizationEnergies(const GridFunction &x, metric_energy += weight * metric->EvalW(Jpt); lim_energy += weight; + + // Normalization of the surface fitting term. + if (sigma) + { + sigma_energy += weight * sigma_bar_q(q) * sigma_bar_q(q); + } } } + if (targetC->ContainsVolumeInfo() == false) { // Special case when the targets don't contain volumetric information. @@ -3336,6 +3610,17 @@ void TMOP_Integrator::UpdateAfterMeshPositionChange(const Vector &new_x) } // Update zeta if adaptive limiting is enabled. if (zeta) { adapt_eval->ComputeAtNewPosition(new_x, *zeta); } + + // Update sigma if surface fitting is enabled. + if (sigma) + { + sigma_eval->ComputeAtNewPosition(new_x, *sigma); + // Update the restricted sigma. + for (int i = 0; i < sigma_marker->Size(); i++) + { + (*sigma_bar)(i) = ((*sigma_marker)[i] == true) ? (*sigma)(i) : 0.0; + } + } } void TMOP_Integrator::ComputeFDh(const Vector &x, const FiniteElementSpace &fes) diff --git a/fem/tmop.hpp b/fem/tmop.hpp index 7dde7ab586..feab3f7fb1 100644 --- a/fem/tmop.hpp +++ b/fem/tmop.hpp @@ -592,6 +592,27 @@ public: virtual int Id() const { return 321; } }; +/// 3D barrier Shape+Size (VS) metric (polyconvex). +class TMOP_Metric_328 : public TMOP_Combo_QualityMetric +{ +protected: + mutable InvariantsEvaluator2D ie; + double gamma; + TMOP_QualityMetric *sh_metric, *sz_metric; + +public: + TMOP_Metric_328(double gamma_) : gamma(gamma_), + sh_metric(new TMOP_Metric_301), + sz_metric(new TMOP_Metric_316) + { + // (1-gamma) mu_301 + gamma mu_316 + AddQualityMetric(sh_metric, 1.-gamma_); + AddQualityMetric(sz_metric, gamma_); + } + + virtual ~TMOP_Metric_328() { delete sh_metric; delete sz_metric; } +}; + /// 3D barrier Shape+Size (VS) metric (polyconvex). class TMOP_Metric_332 : public TMOP_Combo_QualityMetric { @@ -619,6 +640,7 @@ public: class TMOP_Metric_333 : public TMOP_Combo_QualityMetric { protected: + mutable InvariantsEvaluator2D ie; double gamma; TMOP_QualityMetric *sh_metric, *sz_metric; @@ -632,12 +654,30 @@ public: AddQualityMetric(sz_metric, gamma_); } - virtual int Id() const { return 333; } - double GetGamma() const { return gamma; } - virtual ~TMOP_Metric_333() { delete sh_metric; delete sz_metric; } }; +/// 3D barrier Shape+Size (VS) metric (polyconvex). +class TMOP_Metric_334 : public TMOP_Combo_QualityMetric +{ +protected: + mutable InvariantsEvaluator2D ie; + double gamma; + TMOP_QualityMetric *sh_metric, *sz_metric; + +public: + TMOP_Metric_334(double gamma_) : gamma(gamma_), + sh_metric(new TMOP_Metric_303), + sz_metric(new TMOP_Metric_316) + { + // (1-gamma) mu_303 + gamma mu_316 + AddQualityMetric(sh_metric, 1.-gamma_); + AddQualityMetric(sz_metric, gamma_); + } + + virtual ~TMOP_Metric_334() { delete sh_metric; delete sz_metric; } +}; + /// Shifted barrier form of 3D metric 16 (volume, ideal barrier metric), 3D class TMOP_Metric_352 : public TMOP_QualityMetric { @@ -897,9 +937,6 @@ protected: #ifdef MFEM_USE_MPI MPI_Comm comm; - bool Parallel() const { return (comm != MPI_COMM_NULL); } -#else - bool Parallel() const { return false; } #endif // should be called only if avg_volume == 0.0, i.e. avg_volume is not @@ -936,6 +973,13 @@ public: #endif virtual ~TargetConstructor() { } +#ifdef MFEM_USE_MPI + bool Parallel() const { return (comm != MPI_COMM_NULL); } + MPI_Comm GetComm() const { return comm; } +#else + bool Parallel() const { return false; } +#endif + /** @brief Set the nodes to be used in the target-matrix construction. This method should be called every time the target nodes are updated @@ -1296,6 +1340,13 @@ protected: Coefficient *coeff_zeta; // Not owned. AdaptivityEvaluator *adapt_eval; // Not owned. + // Surface fitting. + GridFunction *sigma, *sigma_bar; // Owned. Updated by sigma_eval. + const Array *sigma_marker; // Not owned. + Coefficient *coeff_sigma; // Not owned. + AdaptivityEvaluator *sigma_eval; // Not owned. + double sigma_normal; + DiscreteAdaptTC *discr_tc; // Parameters for FD-based Gradient & Hessian calculation. @@ -1364,7 +1415,8 @@ protected: } PA; void ComputeNormalizationEnergies(const GridFunction &x, - double &metric_energy, double &lim_energy); + double &metric_energy, double &lim_energy, + double &sigma_energy); void AssembleElementVectorExact(const FiniteElement &el, ElementTransformation &T, @@ -1383,12 +1435,25 @@ protected: ElementTransformation &T, const Vector &elfun, DenseMatrix &elmat); - void AssembleElemVecAdaptLim(const FiniteElement &el, const Vector &weights, + void AssembleElemVecAdaptLim(const FiniteElement &el, IsoparametricTransformation &Tpr, - const IntegrationRule &ir, DenseMatrix &m); - void AssembleElemGradAdaptLim(const FiniteElement &el, const Vector &weights, + const IntegrationRule &ir, + const Vector &weights, DenseMatrix &mat); + void AssembleElemGradAdaptLim(const FiniteElement &el, IsoparametricTransformation &Tpr, - const IntegrationRule &ir, DenseMatrix &m); + const IntegrationRule &ir, + const Vector &weights, DenseMatrix &m); + + // First derivative of the surface fitting term. + void AssembleElemVecSurfFit(const FiniteElement &el_x, + IsoparametricTransformation &Tpr, + const IntegrationRule &ir_quad, + const Vector &weights, DenseMatrix &mat); + // Second derivative of the surface fitting term. + void AssembleElemGradSurfFit(const FiniteElement &el_x, + IsoparametricTransformation &Tpr, + const IntegrationRule &ir_quad, + const Vector &weights, DenseMatrix &mat); double GetFDDerivative(const FiniteElement &el, ElementTransformation &T, @@ -1470,6 +1535,8 @@ public: nodes0(NULL), coeff0(NULL), lim_dist(NULL), lim_func(NULL), lim_normal(1.0), zeta_0(NULL), zeta(NULL), coeff_zeta(NULL), adapt_eval(NULL), + sigma(NULL), sigma_bar(NULL), sigma_marker(NULL), coeff_sigma(NULL), + sigma_eval(NULL), sigma_normal(1.0), discr_tc(dynamic_cast(tc)), fdflag(false), dxscale(1.0e3), fd_call_flag(false), exact_action(false) { PA.enabled = false; } @@ -1522,7 +1589,7 @@ public: Adds the term @f$ \int c (z(x) - z_0(x_0))^2 @f$, where z0(x0) is a given function on the starting mesh, and z(x) is its image on the new mesh. - Minimizing this, means that a node at x0 is allowed to move to a + Minimizing this term means that a node at x0 is allowed to move to a position x(x0) only if z(x) ~ z0(x0). Such term can be used for tangential mesh relaxation. @@ -1537,6 +1604,32 @@ public: AdaptivityEvaluator &ae); #endif + /** @brief Fitting of certain DOFs to the zero level set of a function. + + Having a level set function s0(x0) on the starting mesh, and a set of + marked nodes (or DOFs), we move these nodes to the zero level set of s0. + If s(x) is the image of s0(x0) on the current mesh, this function adds to + the TMOP functional the term @f$ \int c \bar{s}(x))^2 @f$, where + @f$\bar{s}(x)@f$ is the restriction of s(x) on the aligned DOFs. + Minimizing this term means that a marked node at x0 is allowed to move to + a position x(x0) only if s(x) ~ 0. + Such term can be used for surface fitting and tangential relaxation. + + @param[in] s0 The level set function on the initial mesh. + @param[in] smarker Indicates which DOFs will be aligned. + @param[in] coeff Coefficient c for the above integral. + @param[in] ae AdaptivityEvaluator to compute s(x) from s0(x0). */ + void EnableSurfaceFitting(const GridFunction &s0, + const Array &smarker, Coefficient &coeff, + AdaptivityEvaluator &ae); +#ifdef MFEM_USE_MPI + /// Parallel support for surface fitting. + void EnableSurfaceFitting(const ParGridFunction &s0, + const Array &smarker, Coefficient &coeff, + AdaptivityEvaluator &ae); +#endif + void GetSurfaceFittingErrors(double &err_avg, double &err_max); + /// Update the original/reference nodes used for limiting. void SetLimitingNodes(const GridFunction &n0) { nodes0 = &n0; } diff --git a/fem/tmop_amr.cpp b/fem/tmop_amr.cpp index 46b1d684ec..647104867e 100755 --- a/fem/tmop_amr.cpp +++ b/fem/tmop_amr.cpp @@ -394,12 +394,13 @@ bool TMOPDeRefinerEstimator::GetDerefineEnergyForIntegrator( const CoarseFineTransformations &dtrans = meshcopy.ncmesh->GetDerefinementTransforms(); - Table coarse_to_fine; - dtrans.GetCoarseToFineMap(meshcopy, coarse_to_fine); + Table coarse_to_fine; + dtrans.MakeCoarseToFineTable(coarse_to_fine); + + Array tabrow; for (int pe = 0; pe < coarse_to_fine.Size(); pe++) { - Array tabrow; coarse_to_fine.GetRow(pe, tabrow); int nchild = tabrow.Size(); double parent_energy = coarse_energy(pe); @@ -446,12 +447,13 @@ bool TMOPDeRefinerEstimator::GetDerefineEnergyForIntegrator( const CoarseFineTransformations &dtrans = meshcopy.pncmesh->GetDerefinementTransforms(); - Table coarse_to_fine; - dtrans.GetCoarseToFineMap(meshcopy, coarse_to_fine); + Table coarse_to_fine; + dtrans.MakeCoarseToFineTable(coarse_to_fine); + + Array tabrow; for (int pe = 0; pe < meshcopy.GetNE(); pe++) { - Array tabrow; coarse_to_fine.GetRow(pe, tabrow); int nchild = tabrow.Size(); double parent_energy = coarse_energy(pe); diff --git a/general/array.hpp b/general/array.hpp index 5767323e78..328503f753 100644 --- a/general/array.hpp +++ b/general/array.hpp @@ -70,6 +70,10 @@ public: explicit inline Array(int asize) : size(asize) { asize > 0 ? data.New(asize) : data.Reset(); } + /// Creates array of @a asize elements with a given MemoryType + inline Array(int asize, MemoryType mt) + : size(asize) { asize > 0 ? data.New(asize, mt) : data.Reset(mt); } + /** @brief Creates array using an existing c-array of asize elements; allocsize is set to -asize to indicate that the data will not be deleted. */ diff --git a/general/forall.hpp b/general/forall.hpp index 364971aebd..4c3905d491 100644 --- a/general/forall.hpp +++ b/general/forall.hpp @@ -139,7 +139,7 @@ void RajaCuWrap2D(const int N, DBODY &&d_body, using RAJA::RangeSegment; launch - (DEVICE, Resources(Teams(G), Threads(X, Y, BZ)), + (DEVICE, Grid(Teams(G), Threads(X, Y, BZ)), [=] RAJA_DEVICE (LaunchContext ctx) { @@ -172,7 +172,7 @@ void RajaCuWrap3D(const int N, DBODY &&d_body, using RAJA::RangeSegment; launch - (DEVICE, Resources(Teams(GRID), Threads(X, Y, Z)), + (DEVICE, Grid(Teams(GRID), Threads(X, Y, Z)), [=] RAJA_DEVICE (LaunchContext ctx) { @@ -183,6 +183,42 @@ void RajaCuWrap3D(const int N, DBODY &&d_body, MFEM_GPU_CHECK(cudaGetLastError()); } +template +struct RajaCuWrap; + +template <> +struct RajaCuWrap<1> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + RajaCuWrap1D(N, d_body); + } +}; + +template <> +struct RajaCuWrap<2> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + RajaCuWrap2D(N, d_body, X, Y, Z); + } +}; + +template <> +struct RajaCuWrap<3> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + RajaCuWrap3D(N, d_body, X, Y, Z, G); + } +}; + #endif #if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_HIP) @@ -205,7 +241,7 @@ void RajaHipWrap2D(const int N, DBODY &&d_body, using RAJA::RangeSegment; launch - (DEVICE, Resources(Teams(G), Threads(X, Y, BZ)), + (DEVICE, Grid(Teams(G), Threads(X, Y, BZ)), [=] RAJA_DEVICE (LaunchContext ctx) { @@ -238,7 +274,7 @@ void RajaHipWrap3D(const int N, DBODY &&d_body, using RAJA::RangeSegment; launch - (DEVICE, Resources(Teams(GRID), Threads(X, Y, Z)), + (DEVICE, Grid(Teams(GRID), Threads(X, Y, Z)), [=] RAJA_DEVICE (LaunchContext ctx) { @@ -248,6 +284,43 @@ void RajaHipWrap3D(const int N, DBODY &&d_body, MFEM_GPU_CHECK(hipGetLastError()); } + +template +struct RajaHipWrap; + +template <> +struct RajaHipWrap<1> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + RajaHipWrap1D(N, d_body); + } +}; + +template <> +struct RajaHipWrap<2> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + RajaHipWrap2D(N, d_body, X, Y, Z); + } +}; + +template <> +struct RajaHipWrap<3> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + RajaHipWrap3D(N, d_body, X, Y, Z, G); + } +}; + #endif /// RAJA OpenMP backend @@ -333,6 +406,42 @@ void CuWrap3D(const int N, DBODY &&d_body, MFEM_GPU_CHECK(cudaGetLastError()); } +template +struct CuWrap; + +template <> +struct CuWrap<1> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + CuWrap1D(N, d_body); + } +}; + +template <> +struct CuWrap<2> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + CuWrap2D(N, d_body, X, Y, Z); + } +}; + +template <> +struct CuWrap<3> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + CuWrap3D(N, d_body, X, Y, Z, G); + } +}; + #endif // MFEM_USE_CUDA @@ -392,6 +501,42 @@ void HipWrap3D(const int N, DBODY &&d_body, MFEM_GPU_CHECK(hipGetLastError()); } +template +struct HipWrap; + +template <> +struct HipWrap<1> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + HipWrap1D(N, d_body); + } +}; + +template <> +struct HipWrap<2> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + HipWrap2D(N, d_body, X, Y, Z); + } +}; + +template <> +struct HipWrap<3> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + HipWrap3D(N, d_body, X, Y, Z, G); + } +}; + #endif // MFEM_USE_HIP @@ -413,9 +558,7 @@ inline void ForallWrap(const bool use_dev, const int N, // If Backend::RAJA_CUDA is allowed, use it if (Device::Allows(Backend::RAJA_CUDA)) { - if (DIM == 1) { return RajaCuWrap1D(N, d_body); } - if (DIM == 2) { return RajaCuWrap2D(N, d_body, X, Y, Z); } - if (DIM == 3) { return RajaCuWrap3D(N, d_body, X, Y, Z, G); } + return RajaCuWrap::run(N, d_body, X, Y, Z, G); } #endif @@ -423,9 +566,7 @@ inline void ForallWrap(const bool use_dev, const int N, // If Backend::RAJA_HIP is allowed, use it if (Device::Allows(Backend::RAJA_HIP)) { - if (DIM == 1) { return RajaHipWrap1D(N, d_body); } - if (DIM == 2) { return RajaHipWrap2D(N, d_body, X, Y, Z); } - if (DIM == 3) { return RajaHipWrap3D(N, d_body, X, Y, Z, G); } + return RajaHipWrap::run(N, d_body, X, Y, Z, G); } #endif @@ -433,9 +574,7 @@ inline void ForallWrap(const bool use_dev, const int N, // If Backend::CUDA is allowed, use it if (Device::Allows(Backend::CUDA)) { - if (DIM == 1) { return CuWrap1D(N, d_body); } - if (DIM == 2) { return CuWrap2D(N, d_body, X, Y, Z); } - if (DIM == 3) { return CuWrap3D(N, d_body, X, Y, Z, G); } + return CuWrap::run(N, d_body, X, Y, Z, G); } #endif @@ -443,9 +582,7 @@ inline void ForallWrap(const bool use_dev, const int N, // If Backend::HIP is allowed, use it if (Device::Allows(Backend::HIP)) { - if (DIM == 1) { return HipWrap1D(N, d_body); } - if (DIM == 2) { return HipWrap2D(N, d_body, X, Y, Z); } - if (DIM == 3) { return HipWrap3D(N, d_body, X, Y, Z, G); } + return HipWrap::run(N, d_body, X, Y, Z, G); } #endif diff --git a/general/mem_manager.cpp b/general/mem_manager.cpp index ce3d26df55..ca1b959e64 100644 --- a/general/mem_manager.cpp +++ b/general/mem_manager.cpp @@ -33,7 +33,8 @@ #endif #ifdef MFEM_USE_UMPIRE -#include "umpire/Umpire.hpp" +#include +#include // Make sure Umpire is build with CUDA support if MFEM is built with it. #if defined(MFEM_USE_CUDA) && !defined(UMPIRE_ENABLE_CUDA) @@ -535,7 +536,7 @@ public: { if (!rm.isAllocator(name)) { - allocator = rm.makeAllocator( + allocator = rm.makeAllocator( name, rm.getAllocator(space)); owns_allocator = true; } @@ -910,7 +911,12 @@ MemoryType MemoryManager::Delete_(void *h_ptr, MemoryType h_mt, unsigned flags) MFEM_ASSERT(IsHostMemory(h_mt), "invalid h_mt = " << (int)h_mt); // MFEM_ASSERT(registered || IsHostMemory(h_mt),""); MFEM_ASSERT(!owns_device || owns_internal, "invalid Memory state"); - MFEM_ASSERT(registered || !(owns_host || owns_device || owns_internal), + // If at least one of the 'own_*' flags is true then 'registered' must be + // true too. An acceptable exception is the special case when 'h_ptr' is + // NULL, and both 'own_device' and 'own_internal' are false -- this case is + // an exception only when 'own_host' is true and 'registered' is false. + MFEM_ASSERT(registered || !(owns_host || owns_device || owns_internal) || + (!(owns_device || owns_internal) && h_ptr == nullptr), "invalid Memory state"); if (!mm.exists || !registered) { return h_mt; } if (alias) diff --git a/general/mem_manager.hpp b/general/mem_manager.hpp index ade9540687..021682d532 100644 --- a/general/mem_manager.hpp +++ b/general/mem_manager.hpp @@ -368,8 +368,7 @@ public: be updated as described above. */ inline void SetDeviceMemoryType(MemoryType d_mt); - /** @brief Delete the owned pointers. The Memory is not reset by this method, - i.e. it will, generally, not be Empty() after this call. */ + /** @brief Delete the owned pointers and reset the Memory object. */ inline void Delete(); /** @brief Delete the device pointer, if owned. If @a copy_to_host is true @@ -986,6 +985,7 @@ inline void Memory::Delete() { if (flags & OWNS_HOST) { delete [] h_ptr; } } + Reset(h_mt); } template diff --git a/linalg/amgxsolver.cpp b/linalg/amgxsolver.cpp index 418d498b2a..aff32f4909 100644 --- a/linalg/amgxsolver.cpp +++ b/linalg/amgxsolver.cpp @@ -604,11 +604,16 @@ void AmgXSolver::SetMatrix(const HypreParMatrix &A, const bool update_mat) mfem_error("Hypre version 2.16+ is required when using AmgX \n"); #endif + // Ensure HypreParMatrix is on the host + A.HostRead(); + hypre_ParCSRMatrix * A_ptr = (hypre_ParCSRMatrix *)const_cast(A); hypre_CSRMatrix *A_csr = hypre_MergeDiagAndOffd(A_ptr); + A.HypreRead(); + Array loc_A(A_csr->data, (int)A_csr->num_nonzeros); const Array loc_I(A_csr->i, (int)A_csr->num_rows+1); diff --git a/linalg/hypre.cpp b/linalg/hypre.cpp index 9e664b863e..7521a8bb30 100644 --- a/linalg/hypre.cpp +++ b/linalg/hypre.cpp @@ -303,6 +303,18 @@ void HypreParVector::Print(const char *fname) const hypre_ParVectorPrint(x,fname); } +void HypreParVector::Read(MPI_Comm comm, const char *fname) +{ + if (own_ParVector) + { + hypre_ParVectorDestroy(x); + } + data.Delete(); + x = hypre_ParVectorRead(comm, fname); + own_ParVector = true; + _SetDataAndSize_(); +} + HypreParVector::~HypreParVector() { if (own_ParVector) diff --git a/linalg/hypre.hpp b/linalg/hypre.hpp index 36ebe13a95..4ec95ca92c 100644 --- a/linalg/hypre.hpp +++ b/linalg/hypre.hpp @@ -252,6 +252,9 @@ public: /// Prints the locally owned rows in parallel void Print(const char *fname) const; + /// Reads a HypreParVector from files saved with HypreParVector::Print + void Read(MPI_Comm comm, const char *fname); + /// Calls hypre's destroy function ~HypreParVector(); diff --git a/linalg/kernels.hpp b/linalg/kernels.hpp index 68d4a73b50..051d3e5a94 100644 --- a/linalg/kernels.hpp +++ b/linalg/kernels.hpp @@ -160,7 +160,7 @@ double Norml2(const int size, const T *data) data of the input and output vectors. */ template MFEM_HOST_DEVICE inline -void Mult(const int height, const int width, TA *data, const TX *x, TY *y) +void Mult(const int height, const int width, const TA *data, const TX *x, TY *y) { if (width == 0) { @@ -170,7 +170,7 @@ void Mult(const int height, const int width, TA *data, const TX *x, TY *y) } return; } - TA *d_col = data; + const TA *d_col = data; TX x_col = x[0]; for (int row = 0; row < height; row++) { @@ -188,6 +188,35 @@ void Mult(const int height, const int width, TA *data, const TX *x, TY *y) } } +/** @brief Matrix transpose vector multiplication: y = At x, where the matrix A + is of size @a height x @a width with given @a data, while @a x and @a y + specify the data of the input and output vectors. */ +template +MFEM_HOST_DEVICE inline +void MultTranspose(const int height, const int width, const TA *data, + const TX *x, TY *y) +{ + if (height == 0) + { + for (int row = 0; row < width; row++) + { + y[row] = 0.0; + } + return; + } + TY *y_off = y; + for (int i = 0; i < width; ++i) + { + TY val = 0.0; + for (int j = 0; j < height; ++j) + { + val += x[j] * data[i * height + j]; + } + *y_off = val; + y_off++; + } +} + /// Symmetrize a square matrix with given @a size and @a data: A -> (A+A^T)/2. template MFEM_HOST_DEVICE inline @@ -353,6 +382,30 @@ void MultABt(const int Aheight, const int Awidth, const int Bheight, } } +/** @brief Multiply the transpose of a matrix of size @a Aheight x @a Awidth + and data @a Adata with a matrix of size @a Aheight x @a Bwidth and data @a + Bdata: At * B. Return the result in a matrix with data @a AtBdata. */ +template +MFEM_HOST_DEVICE inline +void MultAtB(const int Aheight, const int Awidth, const int Bwidth, + const TA *Adata, const TB *Bdata, TC *AtBdata) +{ + TC *c = AtBdata; + for (int i = 0; i < Bwidth; ++i) + { + for (int j = 0; j < Awidth; ++j) + { + TC val = 0.0; + for (int k = 0; k < Aheight; ++k) + { + val += Adata[j * Aheight + k] * Bdata[i * Aheight + k]; + } + *c = val; + c++; + } + } +} + /// Compute the spectrum of the matrix of size dim with given @a data, returning /// the eigenvalues in the array @a lambda and the eigenvectors in the array @a /// vec (listed consecutively). diff --git a/linalg/operator.hpp b/linalg/operator.hpp index 1e454a79f2..d9ca0e5839 100644 --- a/linalg/operator.hpp +++ b/linalg/operator.hpp @@ -242,7 +242,7 @@ public: void FormDiscreteOperator(Operator* &A); /// Prints operator with input size n and output size m in Matlab format. - void PrintMatlab(std::ostream & out, int n = 0, int m = 0) const; + void PrintMatlab(std::ostream & out, int n, int m = 0) const; /// Prints operator in Matlab format. virtual void PrintMatlab(std::ostream & out) const; diff --git a/mesh/mesh.cpp b/mesh/mesh.cpp index f40c037005..1021857edf 100644 --- a/mesh/mesh.cpp +++ b/mesh/mesh.cpp @@ -75,7 +75,7 @@ void Mesh::GetElementCenter(int i, Vector ¢er) double Mesh::GetElementSize(ElementTransformation *T, int type) { - DenseMatrix J(spaceDim,Dim); + DenseMatrix J(spaceDim, Dim); Geometry::Type geom = T->GetGeometryType(); T->SetIntPoint(&Geometries.GetCenter(geom)); @@ -102,7 +102,7 @@ double Mesh::GetElementSize(int i, int type) double Mesh::GetElementSize(int i, const Vector &dir) { - DenseMatrix J(spaceDim,Dim); + DenseMatrix J(spaceDim, Dim); Vector d_hat(Dim); GetElementJacobian(i, J); J.MultTranspose(dir, d_hat); @@ -8527,8 +8527,8 @@ void Mesh::LocalRefinement(const Array &marked_el, int type) elements[new_e] = new Segment(new_v, vert[1], attr); vert[1] = new_v; - CoarseFineTr.embeddings[i] = Embedding(i, 1); - CoarseFineTr.embeddings[new_e] = Embedding(i, 2); + CoarseFineTr.embeddings[i] = Embedding(i, Geometry::SEGMENT, 1); + CoarseFineTr.embeddings[new_e] = Embedding(i, Geometry::SEGMENT, 2); } static double seg_children[3*2] = { 0.0,1.0, 0.0,0.5, 0.5,1.0 }; @@ -9276,7 +9276,7 @@ void Mesh::Bisection(int i, const DSTable &v_to_v, int coarse = FindCoarseElement(i); CoarseFineTr.embeddings[i].parent = coarse; - CoarseFineTr.embeddings.Append(Embedding(coarse)); + CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TRIANGLE)); // 3. edge1 and edge2 may have to be changed for the second triangle. if (v[1][0] < v_to_v.NumberOfRows() && v[1][1] < v_to_v.NumberOfRows()) @@ -9396,7 +9396,7 @@ void Mesh::Bisection(int i, HashTable &v_to_v) int coarse = FindCoarseElement(i); CoarseFineTr.embeddings[i].parent = coarse; - CoarseFineTr.embeddings.Append(Embedding(coarse)); + CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TETRAHEDRON)); // 3. Set the bisection flag switch (type) @@ -9534,10 +9534,10 @@ void Mesh::UniformRefinement(int i, const DSTable &v_to_v, // set parent indices int coarse = FindCoarseElement(i); - CoarseFineTr.embeddings[i] = Embedding(coarse); - CoarseFineTr.embeddings.Append(Embedding(coarse)); - CoarseFineTr.embeddings.Append(Embedding(coarse)); - CoarseFineTr.embeddings.Append(Embedding(coarse)); + CoarseFineTr.embeddings[i] = Embedding(coarse, Geometry::TRIANGLE); + CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TRIANGLE)); + CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TRIANGLE)); + CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TRIANGLE)); NumOfElements += 3; } @@ -9555,7 +9555,7 @@ void Mesh::InitRefinementTransforms() for (int i = 0; i < NumOfElements; i++) { elements[i]->ResetTransform(0); - CoarseFineTr.embeddings[i] = Embedding(i); + CoarseFineTr.embeddings[i] = Embedding(i, GetElementGeometry(i)); } } diff --git a/mesh/mesh_operators.cpp b/mesh/mesh_operators.cpp index 403b5ceec8..043749ad78 100644 --- a/mesh/mesh_operators.cpp +++ b/mesh/mesh_operators.cpp @@ -157,6 +157,142 @@ int ThresholdDerefiner::ApplyImpl(Mesh &mesh) } +int CoefficientRefiner::ApplyImpl(Mesh &mesh) +{ + int max_it = 1; + return PreprocessMesh(mesh, max_it); +} + +int CoefficientRefiner::PreprocessMesh(Mesh &mesh, int max_it) +{ + int rank = 0; + MFEM_VERIFY(max_it > 0, "max_it must be strictly positive") + + int dim = mesh.Dimension(); + L2_FECollection l2fec(order, dim); + FiniteElementSpace* l2fes = NULL; + + bool par = false; + GridFunction *gf = NULL; + +#ifdef MFEM_USE_MPI + ParMesh* pmesh = dynamic_cast(&mesh); + if (pmesh && pmesh->Nonconforming()) + { + par = true; + l2fes = new ParFiniteElementSpace(pmesh, &l2fec); + gf = new ParGridFunction(static_cast(l2fes)); + } +#endif + if (!par) + { + l2fes = new FiniteElementSpace(&mesh, &l2fec); + gf = new GridFunction(l2fes); + } + + // If custom integration rule has not been set, + // then use the default integration rule + if (!irs) + { + int order_quad = 2*order + 3; + for (int i=0; i < Geometry::NumGeom; ++i) + { + ir_default[i] = &(IntRules.Get(i, order_quad)); + } + irs = ir_default; + } + + for (int i = 0; i < max_it; i++) + { + // Compute number of elements and L2-norm of f. + int NE = mesh.GetNE(); + int globalNE = 0; + double norm_of_coeff = 0.0; + if (par) + { +#ifdef MFEM_USE_MPI + globalNE = pmesh->GetGlobalNE(); + norm_of_coeff = ComputeGlobalLpNorm(2.0,*coeff,*pmesh,irs); +#endif + } + else + { + globalNE = NE; + norm_of_coeff = ComputeLpNorm(2.0,*coeff,mesh,irs); + } + + // Compute average L2-norm of f + double av_norm_of_coeff = norm_of_coeff / sqrt(globalNE); + + // Compute element-wise L2-norms of (I - Π) f + Vector element_norms_of_fine_scale(NE); + gf->SetSpace(l2fes); + gf->ProjectCoefficient(*coeff); + gf->ComputeElementL2Errors(*coeff,element_norms_of_fine_scale,irs); + + // Define osc_K(f) := || h ⋅ (I - Π) f ||_K and select elements + // for refinement based on threshold. Also record relative osc(f). + global_osc = 0.0; + mesh_refinements.SetSize(0); + element_oscs.Destroy(); + element_oscs.SetSize(NE); + element_oscs = 0.0; + for (int j = 0; j < NE; j++) + { + double h = mesh.GetElementSize(j); + double element_osc = h * element_norms_of_fine_scale(j); + if ( element_osc > threshold * av_norm_of_coeff ) + { + mesh_refinements.Append(j); + } + element_oscs(j) = element_osc/(norm_of_coeff + 1e-10); + global_osc += element_osc*element_osc; + } +#ifdef MFEM_USE_MPI + if (par) + { + MPI_Comm comm = pmesh->GetComm(); + MPI_Allreduce(MPI_IN_PLACE, &global_osc, 1, MPI_DOUBLE, MPI_SUM, comm); + MPI_Comm_rank(comm, &rank); + } +#endif + global_osc = sqrt(global_osc)/(norm_of_coeff + 1e-10); + + // Exit if the global threshold or maximum number of elements is reached. + if (global_osc < threshold || globalNE > max_elements) + { + if (global_osc > threshold && globalNE > max_elements && rank == 0 && + print_level) + { + MFEM_WARNING("Reached maximum number of elements " + "before resolving data to tolerance."); + } + delete l2fes; + delete gf; + return STOP; + } + + // Refine elements. + mesh.GeneralRefinement(mesh_refinements, nonconforming, nc_limit); + l2fes->Update(false); + gf->Update(); + + } + delete l2fes; + delete gf; + return CONTINUE + REFINED; + +} + +void CoefficientRefiner::Reset() +{ + element_oscs.Destroy(); + global_osc = 0.0; + coeff = NULL; + irs = NULL; +} + + int Rebalancer::ApplyImpl(Mesh &mesh) { #ifdef MFEM_USE_MPI diff --git a/mesh/mesh_operators.hpp b/mesh/mesh_operators.hpp index 6bc2d449c8..63fce0f1b7 100644 --- a/mesh/mesh_operators.hpp +++ b/mesh/mesh_operators.hpp @@ -308,6 +308,118 @@ public: }; +/** @brief Refinement operator to control data oscillation. + + This class computes osc_K(f) := || h ⋅ (I - Π) f ||_K at each element K. + Here, Π is the L2-projection and ||⋅||_K is the L2-norm, restricted to the + element K. All elements satisfying the inequality + \code + osc_K(f) > threshold ⋅ ||f|| / sqrt(n_el), + \endcode + are refined. Here, threshold is a positive parameter, ||⋅|| is the L2-norm + over the entire domain Ω, and n_el is the number of elements in the mesh. + + Note that if osc(f) = threshold ⋅ ||f|| / sqrt(n_el) for each K, then + \code + osc(f) = sqrt(sum_K osc_K^2(f)) = threshold ⋅ ||f||. + \endcode + This is the reason for the 1/sqrt(n_el) factor. +*/ +class CoefficientRefiner : public MeshOperator +{ +protected: + bool print_level = false; + int nc_limit = 1; + int nonconforming = -1; + int order; + long max_elements = std::numeric_limits::max(); + double threshold = 1.0e-2; + double global_osc = NAN; + Array mesh_refinements; + Vector element_oscs; + Coefficient *coeff = NULL; + const IntegrationRule *ir_default[Geometry::NumGeom]; + const IntegrationRule **irs = NULL; + + /** @brief Apply the operator to the mesh once. + @return STOP if a stopping criterion is satisfied or no elements were + marked for refinement; REFINED + CONTINUE otherwise. */ + virtual int ApplyImpl(Mesh &mesh); + +public: + /// Constructor + CoefficientRefiner(Coefficient &coeff_, int order_) + { + // function f + coeff = &coeff_; + + // order of the projection Π + order = order_; + } + + /** @brief Apply the operator to the mesh max_it times or until tolerance + * achieved. + @return STOP if a stopping criterion is satisfied or no elements were + marked for refinement; REFINED + CONTINUE otherwise. */ + virtual int PreprocessMesh(Mesh &mesh, int max_it); + + int PreprocessMesh(Mesh &mesh) + { + int max_it = 10; + return PreprocessMesh(mesh, max_it); + } + + /// Set the refinement threshold. The default value is 1.0e-2. + void SetThreshold(double threshold_) { threshold = threshold_; } + + /** @brief Set the maximum number of elements stopping criterion: stop when + the input mesh has num_elements >= max_elem. The default value is + LONG_MAX. */ + void SetMaxElements(long max_elements_) { max_elements = max_elements_; } + + /// Reset the function f + void ResetCoefficient(Coefficient &coeff_) + { + element_oscs.Destroy(); + global_osc = NAN; + coeff = &coeff_; + } + + /// Reset the oscillation order + void SetOrder(double order_) { order = order_; } + + /** @brief Set the maximum ratio of refinement levels of adjacent elements + (0 = unlimited). The default value is 1, which helps ensure appropriate + refinements in pathological situations where the default quadrature + order is too low. */ + void SetNCLimit(int nc_limit_) + { + MFEM_ASSERT(nc_limit_ >= 0, "Invalid NC limit"); + nc_limit = nc_limit_; + } + + // Set a custom integration rule + void SetIntRule(const IntegrationRule *irs_[]) { irs = irs_; } + + // Set print level + void PrintWarnings() { print_level = true; } + + // Return the value of the global relative data oscillation + double GetOsc() const { return global_osc; } + + // Return the local relative data oscillation errors + const Vector & GetLocalOscs() const + { + MFEM_ASSERT(element_oscs.Size() > 0, + "Local oscillations have not been computed yet") + return element_oscs; + } + + /// Reset + virtual void Reset(); +}; + + /** @brief ParMesh rebalancing operator. If the mesh is a parallel mesh, perform rebalancing; otherwise, do nothing. diff --git a/mesh/ncmesh.cpp b/mesh/ncmesh.cpp index 399809b1fd..a2778dedda 100644 --- a/mesh/ncmesh.cpp +++ b/mesh/ncmesh.cpp @@ -1865,8 +1865,12 @@ void NCMesh::InitDerefTransforms() transforms.embeddings.SetSize(nfine); for (int i = 0; i < nfine; i++) { - transforms.embeddings[i].parent = -1; - transforms.embeddings[i].matrix = 0; + Embedding &emb = transforms.embeddings[i]; + emb.parent = -1; + emb.matrix = 0; + Element &el = elements[leaf_elements[i]]; + emb.geom = el.Geom(); + emb.ghost = IsGhost(el); } } @@ -1879,7 +1883,7 @@ void NCMesh::SetDerefMatrixCodes(int parent, Array &fine_coarse) Element &ch = elements[prn.child[i]]; if (ch.index >= 0) { - int code = (prn.ref_type << 8) | (i << 4) | prn.geom; + int code = (prn.ref_type << 4) | i; transforms.embeddings[ch.index].matrix = code; fine_coarse[ch.index] = parent; } @@ -4291,6 +4295,8 @@ void NCMesh::TraverseRefinements(int elem, int coarse_index, Embedding &emb = transforms.embeddings[el.index]; emb.parent = coarse_index; emb.matrix = matrix - 1; + emb.geom = el.Geom(); + emb.ghost = IsGhost(el); } else { @@ -4378,15 +4384,14 @@ const CoarseFineTransformations& NCMesh::GetDerefinementTransforms() // assign numbers to the different matrices used for (int i = 0; i < transforms.embeddings.Size(); i++) { - int code = transforms.embeddings[i].matrix; + Embedding &emb = transforms.embeddings[i]; + int code = emb.matrix; // see SetDerefMatrixCodes() if (code) { - int geom = code & 0xf; // see SetDerefMatrixCodes() - int ref_type_child = code >> 4; + int &matrix = mat_no[emb.geom][code]; + if (!matrix) { matrix = mat_no[emb.geom].size(); } - int &matrix = mat_no[geom][ref_type_child]; - if (!matrix) { matrix = mat_no[geom].size(); } - transforms.embeddings[i].matrix = matrix - 1; + emb.matrix = matrix - 1; } } @@ -4421,136 +4426,26 @@ const CoarseFineTransformations& NCMesh::GetDerefinementTransforms() return transforms; } -namespace internal +void CoarseFineTransformations::MakeCoarseToFineTable(Table &coarse_to_fine, + bool want_ghosts) const { + Array conn; + conn.Reserve(embeddings.Size()); -// Used in CoarseFineTransformations::GetCoarseToFineMap() below. -struct RefType -{ - Geometry::Type geom; - int num_children; - const Pair *children; - - RefType(Geometry::Type g, int n, const Pair *c) - : geom(g), num_children(n), children(c) { } - - bool operator<(const RefType &other) const + int max_parent = -1; + for (int i = 0; i < embeddings.Size(); i++) { - if (geom < other.geom) { return true; } - if (geom > other.geom) { return false; } - if (num_children < other.num_children) { return true; } - if (num_children > other.num_children) { return false; } - for (int i = 0; i < num_children; i++) + const Embedding &emb = embeddings[i]; + if ((emb.parent >= 0) && + (!emb.ghost || want_ghosts)) { - if (children[i].one < other.children[i].one) { return true; } - if (children[i].one > other.children[i].one) { return false; } - } - return false; // everything is equal - } -}; - -} // namespace internal - -void CoarseFineTransformations::GetCoarseToFineMap( - const mfem::Mesh &fine_mesh, Table &coarse_to_fine, - Array &coarse_to_ref_type, Table &ref_type_to_matrix, - Array &ref_type_to_geom, - bool get_coarse_to_fine_only) const -{ - const int fine_ne = embeddings.Size(); - int coarse_ne = -1; - for (int i = 0; i < fine_ne; i++) - { - coarse_ne = std::max(coarse_ne, embeddings[i].parent); - } - coarse_ne++; - - coarse_to_ref_type.SetSize(coarse_ne); - coarse_to_fine.SetDims(coarse_ne, fine_ne); - - Array cf_i(coarse_to_fine.GetI(), coarse_ne+1); - Array > cf_j(fine_ne); - cf_i = 0; - for (int i = 0; i < fine_ne; i++) - { - cf_i[embeddings[i].parent+1]++; - } - cf_i.PartialSum(); - MFEM_ASSERT(cf_i.Last() == cf_j.Size(), "internal error"); - for (int i = 0; i < fine_ne; i++) - { - const Embedding &e = embeddings[i]; - cf_j[cf_i[e.parent]].one = e.matrix; // used as sort key below - cf_j[cf_i[e.parent]].two = i; - cf_i[e.parent]++; - } - std::copy_backward(cf_i.begin(), cf_i.end()-1, cf_i.end()); - cf_i[0] = 0; - for (int i = 0; i < coarse_ne; i++) - { - std::sort(&cf_j[cf_i[i]], cf_j.GetData() + cf_i[i+1]); - } - for (int i = 0; i < fine_ne; i++) - { - coarse_to_fine.GetJ()[i] = cf_j[i].two; - } - - if (get_coarse_to_fine_only) { return; } - MFEM_VERIFY(fine_mesh.GetLastOperation() != Mesh::Operation::DEREFINE, - "GetCoarseToFineMap is not fully supported for derefined meshes." - " Set 'get_coarse_to_fine_only=true'.") - - using internal::RefType; - using std::map; - using std::pair; - - map ref_type_map; - for (int i = 0; i < coarse_ne; i++) - { - const int num_children = cf_i[i+1]-cf_i[i]; - MFEM_ASSERT(num_children > 0, ""); - const int fine_el = cf_j[cf_i[i]].two; - // Assuming the coarse and the fine elements have the same geometry: - const Geometry::Type geom = fine_mesh.GetElementBaseGeometry(fine_el); - const RefType ref_type(geom, num_children, &cf_j[cf_i[i]]); - pair::iterator,bool> res = - ref_type_map.insert( - pair(ref_type, (int)ref_type_map.size())); - coarse_to_ref_type[i] = res.first->second; - } - - ref_type_to_matrix.MakeI((int)ref_type_map.size()); - ref_type_to_geom.SetSize((int)ref_type_map.size()); - for (map::iterator it = ref_type_map.begin(); - it != ref_type_map.end(); ++it) - { - ref_type_to_matrix.AddColumnsInRow(it->second, it->first.num_children); - ref_type_to_geom[it->second] = it->first.geom; - } - - ref_type_to_matrix.MakeJ(); - for (map::iterator it = ref_type_map.begin(); - it != ref_type_map.end(); ++it) - { - const RefType &rt = it->first; - for (int j = 0; j < rt.num_children; j++) - { - ref_type_to_matrix.AddConnection(it->second, rt.children[j].one); + conn.Append(Connection(emb.parent, i)); + max_parent = std::max(emb.parent, max_parent); } } - ref_type_to_matrix.ShiftUpI(); -} -void CoarseFineTransformations::GetCoarseToFineMap(const Mesh &fine_mesh, - Table &coarse_to_fine) const -{ - Array coarse_to_ref_type; - Table ref_type_to_matrix; - Array ref_type_to_geom; - bool get_coarse_to_fine_only = true; - GetCoarseToFineMap(fine_mesh, coarse_to_fine, coarse_to_ref_type, - ref_type_to_matrix, ref_type_to_geom, - get_coarse_to_fine_only); + conn.Sort(); // NOTE: unique is not necessary + coarse_to_fine.MakeFromList(max_parent+1, conn); } void NCMesh::ClearTransforms() @@ -4580,7 +4475,7 @@ bool CoarseFineTransformations::IsInitialized() const void Swap(CoarseFineTransformations &a, CoarseFineTransformations &b) { - for (int g=0; g embeddings; - void GetCoarseToFineMap(const Mesh &fine_mesh, - Table &coarse_to_fine, - Array &coarse_to_ref_type, - Table &ref_type_to_matrix, - Array &ref_type_to_geom, - bool get_coarse_to_fine_only = false) const; + /** A "dictionary" of matrices for IsoparametricTransformation. Use + Embedding::{geom,matrix} to access a fine element point matrix. */ + DenseTensor point_matrices[Geometry::NumGeom]; - void GetCoarseToFineMap(const Mesh &fine_mesh, - Table &coarse_to_fine) const; + /** Invert the 'embeddings' array: create a Table with coarse elements as + rows and fine elements as columns. If 'want_ghosts' is false, parallel + ghost fine elements are not included in the table. */ + void MakeCoarseToFineTable(Table &coarse_to_fine, + bool want_ghosts = false) const; void Clear(); bool IsInitialized() const; long MemoryUsage() const; + + MFEM_DEPRECATED + void GetCoarseToFineMap(const Mesh &fine_mesh, Table &coarse_to_fine) const + { MakeCoarseToFineTable(coarse_to_fine, true); (void) fine_mesh; } }; void Swap(CoarseFineTransformations &a, CoarseFineTransformations &b); diff --git a/mesh/pmesh.cpp b/mesh/pmesh.cpp index f19d6e92a2..8c37546571 100644 --- a/mesh/pmesh.cpp +++ b/mesh/pmesh.cpp @@ -3706,8 +3706,8 @@ void ParMesh::LocalRefinement(const Array &marked_el, int type) elements[new_e] = new Segment(new_v, vert[1], attr); vert[1] = new_v; - CoarseFineTr.embeddings[i] = Embedding(i, 1); - CoarseFineTr.embeddings[new_e] = Embedding(i, 2); + CoarseFineTr.embeddings[i] = Embedding(i, Geometry::SEGMENT, 1); + CoarseFineTr.embeddings[new_e] = Embedding(i, Geometry::SEGMENT, 2); } static double seg_children[3*2] = { 0.0,1.0, 0.0,0.5, 0.5,1.0 }; diff --git a/miniapps/meshing/mesh-optimizer.cpp b/miniapps/meshing/mesh-optimizer.cpp index 94780d185e..913bcc10ab 100644 --- a/miniapps/meshing/mesh-optimizer.cpp +++ b/miniapps/meshing/mesh-optimizer.cpp @@ -69,6 +69,10 @@ // Adaptive limiting through FD (requires GSLIB): // * mesh-optimizer -m stretched2D.mesh -o 2 -mid 2 -tid 1 -ni 50 -qo 5 -nor -vl 1 -alc 0.5 -fd -ae 1 // +// Adaptive surface fitting: +// mesh-optimizer -m square01.mesh -o 3 -rs 1 -mid 58 -tid 1 -ni 200 -vl 1 -sfc 5e4 -rtol 1e-5 -nor +// mesh-optimizer -m square01-tri.mesh -o 3 -rs 0 -mid 58 -tid 1 -ni 200 -vl 1 -sfc 1e4 -rtol 1e-5 -nor +// // Blade shape: // mesh-optimizer -m blade.mesh -o 4 -mid 2 -tid 1 -ni 30 -ls 3 -art 1 -bnd -qt 1 -qo 8 // Blade shape with FD-based solver: @@ -96,7 +100,6 @@ // mesh-optimizer -m jagged.mesh -o 2 -mid 22 -tid 1 -ni 50 -li 50 -qo 4 -fd -vl 1 // 3D untangling (the mesh is in the mfem/data GitHub repository): // * mesh-optimizer -m ../../../mfem_data/cube-holes-inv.mesh -o 3 -mid 313 -tid 1 -rtol 1e-5 -li 50 -qo 4 -fd -vl 1 -// #include "mfem.hpp" #include "../common/mfem-common.hpp" @@ -117,7 +120,8 @@ int main(int argc, char *argv[]) int metric_id = 1; int target_id = 1; double lim_const = 0.0; - double adapt_lim_const = 0.0; + double adapt_lim_const = 0.0; + double surface_fit_const = 0.0; int quad_type = 1; int quad_order = 8; int solver_type = 0; @@ -195,6 +199,8 @@ int main(int argc, char *argv[]) args.AddOption(&lim_const, "-lc", "--limit-const", "Limiting constant."); args.AddOption(&adapt_lim_const, "-alc", "--adapt-limit-const", "Adaptive limiting coefficient constant."); + args.AddOption(&surface_fit_const, "-sfc", "--surface-fit-const", + "Surface preservation constant."); args.AddOption(&quad_type, "-qt", "--quad-type", "Quadrature rule type:\n\t" "1: Gauss-Lobatto\n\t" @@ -283,10 +289,6 @@ int main(int argc, char *argv[]) Mesh *mesh = new Mesh(mesh_file, 1, 1, false); for (int lev = 0; lev < rs_levels; lev++) { mesh->UniformRefinement(); } const int dim = mesh->Dimension(); - cout << "Mesh curvature: "; - if (mesh->GetNodes()) { cout << mesh->GetNodes()->OwnFEC()->Name(); } - else { cout << "(NONE)"; } - cout << endl; if (hradaptivity) { mesh->EnsureNCMesh(); } @@ -722,8 +724,6 @@ int main(int argc, char *argv[]) << irules->Get(Geometry::PRISM, quad_order).GetNPoints() << endl; } - if (normalization) { he_nlf_integ->EnableNormalization(x0); } - // Limit the node movement. // The limiting distances can be given by a general function of space. FiniteElementSpace dist_fespace(mesh, fec); // scalar space @@ -765,6 +765,77 @@ int main(int argc, char *argv[]) } } + // Surface fitting. + L2_FECollection mat_coll(0, dim); + H1_FECollection sigma_fec(mesh_poly_deg, dim); + FiniteElementSpace sigma_fes(mesh, &sigma_fec); + FiniteElementSpace mat_fes(mesh, &mat_coll); + GridFunction mat(&mat_fes); + GridFunction marker_gf(&sigma_fes); + GridFunction ls_0(&sigma_fes); + Array marker(ls_0.Size()); + ConstantCoefficient coef_ls(surface_fit_const); + AdaptivityEvaluator *adapt_surface = NULL; + if (surface_fit_const > 0.0) + { + MFEM_VERIFY(hradaptivity == false, + "Surface fitting with HR is not implemented yet."); + MFEM_VERIFY(pa == false, + "Surface fitting with PA is not implemented yet."); + + FunctionCoefficient ls_coeff(surface_level_set); + ls_0.ProjectCoefficient(ls_coeff); + + for (int i = 0; i < mesh->GetNE(); i++) + { + mat(i) = material_id(i, ls_0); + mesh->SetAttribute(i, mat(i) + 1); + } + + GridFunctionCoefficient coeff_mat(&mat); + marker_gf.ProjectDiscCoefficient(coeff_mat, GridFunction::ARITHMETIC); + for (int j = 0; j < marker.Size(); j++) + { + if (marker_gf(j) > 0.1 && marker_gf(j) < 0.9) + { + marker[j] = true; + marker_gf(j) = 1.0; + } + else + { + marker[j] = false; + marker_gf(j) = 0.0; + } + } + + if (adapt_eval == 0) { adapt_surface = new AdvectorCG; } + else if (adapt_eval == 1) + { +#ifdef MFEM_USE_GSLIB + adapt_surface = new InterpolatorFP; +#else + MFEM_ABORT("MFEM is not built with GSLIB support!"); +#endif + } + else { MFEM_ABORT("Bad interpolation option."); } + + he_nlf_integ->EnableSurfaceFitting(ls_0, marker, coef_ls, *adapt_surface); + if (visualization) + { + socketstream vis1, vis2, vis3; + common::VisualizeField(vis1, "localhost", 19916, ls_0, "Level Set 0", + 300, 600, 300, 300); + common::VisualizeField(vis2, "localhost", 19916, mat, "Materials", + 600, 600, 300, 300); + common::VisualizeField(vis3, "localhost", 19916, marker_gf, "Dofs to Move", + 900, 600, 300, 300); + } + } + + // Has to be after the enabling of the limiting / alignment, as it computes + // normalization factors for these terms as well. + if (normalization) { he_nlf_integ->EnableNormalization(x0); } + // 12. Setup the final NonlinearForm (which defines the integral of interest, // its first and second derivatives). Here we can use a combination of // metrics, i.e., optimize the sum of two integrals, where both are @@ -855,6 +926,18 @@ int main(int argc, char *argv[]) // For HR tests, the energy is normalized by the number of elements. const double init_energy = a.GetGridFunctionEnergy(x) / (hradaptivity ? mesh->GetNE() : 1); + double init_metric_energy = init_energy; + if (lim_const > 0.0 || adapt_lim_const > 0.0 || surface_fit_const > 0.0) + { + lim_coeff.constant = 0.0; + coef_zeta.constant = 0.0; + coef_ls.constant = 0.0; + init_metric_energy = a.GetGridFunctionEnergy(x) / + (hradaptivity ? mesh->GetNE() : 1); + lim_coeff.constant = lim_const; + coef_zeta.constant = adapt_lim_const; + coef_ls.constant = surface_fit_const; + } // Visualize the starting mesh and metric values. // Note that for combinations of metrics, this only shows the first metric. @@ -1012,23 +1095,26 @@ int main(int argc, char *argv[]) const double fin_energy = a.GetGridFunctionEnergy(x) / (hradaptivity ? mesh->GetNE() : 1); - double metric_part = fin_energy; + double fin_metric_energy = fin_energy; if (lim_const > 0.0 || adapt_lim_const > 0.0) { lim_coeff.constant = 0.0; coef_zeta.constant = 0.0; - metric_part = a.GetGridFunctionEnergy(x) / - (hradaptivity ? mesh->GetNE() : 1); + coef_ls.constant = 0.0; + fin_metric_energy = a.GetGridFunctionEnergy(x) / + (hradaptivity ? mesh->GetNE() : 1); lim_coeff.constant = lim_const; coef_zeta.constant = adapt_lim_const; + coef_ls.constant = surface_fit_const; } + std::cout << std::scientific << std::setprecision(4); cout << "Initial strain energy: " << init_energy - << " = metrics: " << init_energy - << " + limiting term: " << 0.0 << endl; + << " = metrics: " << init_metric_energy + << " + extra terms: " << init_energy - init_metric_energy << endl; cout << " Final strain energy: " << fin_energy - << " = metrics: " << metric_part - << " + limiting term: " << fin_energy - metric_part << endl; - cout << "The strain energy decreased by: " << setprecision(12) + << " = metrics: " << fin_metric_energy + << " + extra terms: " << fin_energy - fin_metric_energy << endl; + cout << "The strain energy decreased by: " << (init_energy - fin_energy) * 100.0 / init_energy << " %." << endl; // 16. Visualize the final mesh and metric values. @@ -1045,6 +1131,22 @@ int main(int argc, char *argv[]) 600, 600, 300, 300); } + if (surface_fit_const > 0.0) + { + if (visualization) + { + socketstream vis2, vis3; + common::VisualizeField(vis2, "localhost", 19916, mat, "Materials", + 600, 900, 300, 300); + common::VisualizeField(vis3, "localhost", 19916, marker_gf, "Surface dof", + 900, 900, 300, 300); + } + double err_avg, err_max; + he_nlf_integ->GetSurfaceFittingErrors(err_avg, err_max); + std::cout << "Avg fitting error: " << err_avg << std::endl + << "Max fitting error: " << err_max << std::endl; + } + // 17. Visualize the mesh displacement. if (visualization) { @@ -1066,6 +1168,7 @@ int main(int argc, char *argv[]) delete metric2; delete coeff1; delete adapt_evaluator; + delete adapt_surface; delete target_c; delete hr_adapt_coeff; delete adapt_coeff; diff --git a/miniapps/meshing/mesh-optimizer.hpp b/miniapps/meshing/mesh-optimizer.hpp index e2d4f662a5..e930dacbbc 100644 --- a/miniapps/meshing/mesh-optimizer.hpp +++ b/miniapps/meshing/mesh-optimizer.hpp @@ -376,6 +376,54 @@ double adapt_lim_fun(const Vector &x) return val; } +// Used for exact surface alignment +double surface_level_set(const Vector &x) +{ + const int type = 1; + + const int dim = x.Size(); + if (type == 0) + { + const double sine = 0.25 * std::sin(4 * M_PI * x(0)); + return (x(1) >= sine + 0.5) ? 1.0 : -1.0; + } + else + { + if (dim == 2) + { + const double xc = x(0) - 0.5, yc = x(1) - 0.5; + const double r = sqrt(xc*xc + yc*yc); + return std::tanh(2.0*(r-0.3)); + } + else + { + const double xc = x(0) - 0.5, yc = x(1) - 0.5, zc = x(2) - 0.5; + const double r = sqrt(xc*xc + yc*yc + zc*zc); + return std::tanh(2.0*(r-0.3)); + } + } +} + +int material_id(int el_id, const GridFunction &g) +{ + const FiniteElementSpace *fes = g.FESpace(); + const FiniteElement *fe = fes->GetFE(el_id); + Vector g_vals; + const IntegrationRule &ir = + IntRules.Get(fe->GetGeomType(), fes->GetOrder(el_id) + 2); + + double integral = 0.0; + g.GetValues(el_id, ir, g_vals); + ElementTransformation *Tr = fes->GetMesh()->GetElementTransformation(el_id); + for (int q = 0; q < ir.GetNPoints(); q++) + { + const IntegrationPoint &ip = ir.IntPoint(q); + Tr->SetIntPoint(&ip); + integral += ip.weight * g_vals(q) * Tr->Weight(); + } + return (integral > 0.0) ? 1.0 : 0.0; +} + void DiffuseField(GridFunction &field, int smooth_steps) { // Setup the Laplacian operator diff --git a/miniapps/meshing/pmesh-optimizer.cpp b/miniapps/meshing/pmesh-optimizer.cpp index ca7f26c943..4633defb70 100644 --- a/miniapps/meshing/pmesh-optimizer.cpp +++ b/miniapps/meshing/pmesh-optimizer.cpp @@ -69,6 +69,10 @@ // Adaptive limiting through FD (requires GSLIB): // * mpirun -np 4 pmesh-optimizer -m stretched2D.mesh -o 2 -mid 2 -tid 1 -ni 50 -qo 5 -nor -vl 1 -alc 0.5 -fd -ae 1 // +// Adaptive surface fitting: +// mpirun -np 4 pmesh-optimizer -m square01.mesh -o 3 -rs 1 -mid 58 -tid 1 -ni 200 -vl 1 -sfc 5e4 -rtol 1e-5 -nor +// mpirun -np 4 pmesh-optimizer -m square01-tri.mesh -o 3 -rs 0 -mid 58 -tid 1 -ni 200 -vl 1 -sfc 1e4 -rtol 1e-5 -nor +// // Blade shape: // mpirun -np 4 pmesh-optimizer -m blade.mesh -o 4 -mid 2 -tid 1 -ni 30 -ls 3 -art 1 -bnd -qt 1 -qo 8 // Blade shape with FD-based solver: @@ -96,7 +100,6 @@ // mpirun -np 4 pmesh-optimizer -m jagged.mesh -o 2 -mid 22 -tid 1 -ni 50 -li 50 -qo 4 -fd -vl 1 // 3D untangling (the mesh is in the mfem/data GitHub repository): // * mpirun -np 4 pmesh-optimizer -m ../../../mfem_data/cube-holes-inv.mesh -o 3 -mid 313 -tid 1 -rtol 1e-5 -li 50 -qo 4 -fd -vl 1 -// #include "mfem.hpp" #include "../common/mfem-common.hpp" @@ -124,7 +127,8 @@ int main (int argc, char *argv[]) int metric_id = 1; int target_id = 1; double lim_const = 0.0; - double adapt_lim_const = 0.0; + double adapt_lim_const = 0.0; + double surface_fit_const = 0.0; int quad_type = 1; int quad_order = 8; int solver_type = 0; @@ -204,6 +208,8 @@ int main (int argc, char *argv[]) args.AddOption(&lim_const, "-lc", "--limit-const", "Limiting constant."); args.AddOption(&adapt_lim_const, "-alc", "--adapt-limit-const", "Adaptive limiting coefficient constant."); + args.AddOption(&surface_fit_const, "-sfc", "--surface-fit-const", + "Surface preservation constant."); args.AddOption(&quad_type, "-qt", "--quad-type", "Quadrature rule type:\n\t" "1: Gauss-Lobatto\n\t" @@ -294,13 +300,6 @@ int main (int argc, char *argv[]) mesh->UniformRefinement(); } const int dim = mesh->Dimension(); - if (myid == 0) - { - cout << "Mesh curvature: "; - if (mesh->GetNodes()) { cout << mesh->GetNodes()->OwnFEC()->Name(); } - else { cout << "(NONE)"; } - cout << endl; - } if (hradaptivity) { mesh->EnsureNCMesh(); } ParMesh *pmesh = new ParMesh(MPI_COMM_WORLD, *mesh); @@ -760,8 +759,6 @@ int main (int argc, char *argv[]) << irules->Get(Geometry::PRISM, quad_order).GetNPoints() << endl; } - if (normalization) { he_nlf_integ->ParEnableNormalization(x0); } - // Limit the node movement. // The limiting distances can be given by a general function of space. ParFiniteElementSpace dist_pfespace(pmesh, fec); // scalar space @@ -803,6 +800,77 @@ int main (int argc, char *argv[]) } } + // Surface fitting. + L2_FECollection mat_coll(0, dim); + H1_FECollection sigma_fec(mesh_poly_deg, dim); + ParFiniteElementSpace sigma_fes(pmesh, &sigma_fec); + ParFiniteElementSpace mat_fes(pmesh, &mat_coll); + ParGridFunction mat(&mat_fes); + ParGridFunction marker_gf(&sigma_fes); + ParGridFunction ls_0(&sigma_fes); + Array marker(ls_0.Size()); + ConstantCoefficient coef_ls(surface_fit_const); + AdaptivityEvaluator *adapt_surface = NULL; + if (surface_fit_const > 0.0) + { + MFEM_VERIFY(hradaptivity == false, + "Surface fitting with HR is not implemented yet."); + MFEM_VERIFY(pa == false, + "Surface fitting with PA is not implemented yet."); + + FunctionCoefficient ls_coeff(surface_level_set); + ls_0.ProjectCoefficient(ls_coeff); + + for (int i = 0; i < pmesh->GetNE(); i++) + { + mat(i) = material_id(i, ls_0); + pmesh->SetAttribute(i, mat(i) + 1); + } + + GridFunctionCoefficient coeff_mat(&mat); + marker_gf.ProjectDiscCoefficient(coeff_mat, GridFunction::ARITHMETIC); + for (int j = 0; j < marker.Size(); j++) + { + if (marker_gf(j) > 0.1 && marker_gf(j) < 0.9) + { + marker[j] = true; + marker_gf(j) = 1.0; + } + else + { + marker[j] = false; + marker_gf(j) = 0.0; + } + } + + if (adapt_eval == 0) { adapt_surface = new AdvectorCG; } + else if (adapt_eval == 1) + { +#ifdef MFEM_USE_GSLIB + adapt_surface = new InterpolatorFP; +#else + MFEM_ABORT("MFEM is not built with GSLIB support!"); +#endif + } + else { MFEM_ABORT("Bad interpolation option."); } + + he_nlf_integ->EnableSurfaceFitting(ls_0, marker, coef_ls, *adapt_surface); + if (visualization) + { + socketstream vis1, vis2, vis3; + common::VisualizeField(vis1, "localhost", 19916, ls_0, "Level Set 0", + 300, 600, 300, 300); + common::VisualizeField(vis2, "localhost", 19916, mat, "Materials", + 600, 600, 300, 300); + common::VisualizeField(vis3, "localhost", 19916, marker_gf, "Dofs to Move", + 900, 600, 300, 300); + } + } + + // Has to be after the enabling of the limiting / alignment, as it computes + // normalization factors for these terms as well. + if (normalization) { he_nlf_integ->ParEnableNormalization(x0); } + // 13. Setup the final NonlinearForm (which defines the integral of interest, // its first and second derivatives). Here we can use a combination of // metrics, i.e., optimize the sum of two integrals, where both are @@ -899,6 +967,18 @@ int main (int argc, char *argv[]) // For HR tests, the energy is normalized by the number of elements. const double init_energy = a.GetParGridFunctionEnergy(x) / (hradaptivity ? pmesh->GetGlobalNE() : 1); + double init_metric_energy = init_energy; + if (lim_const > 0.0 || adapt_lim_const > 0.0 || surface_fit_const > 0.0) + { + lim_coeff.constant = 0.0; + coef_zeta.constant = 0.0; + coef_ls.constant = 0.0; + init_metric_energy = a.GetParGridFunctionEnergy(x) / + (hradaptivity ? pmesh->GetGlobalNE() : 1); + lim_coeff.constant = lim_const; + coef_zeta.constant = adapt_lim_const; + coef_ls.constant = surface_fit_const; + } // Visualize the starting mesh and metric values. // Note that for combinations of metrics, this only shows the first metric. @@ -1059,28 +1139,31 @@ int main (int argc, char *argv[]) pmesh->PrintAsOne(mesh_ofs); } - // 17. Compute the amount of energy decrease. + // Compute the final energy of the functional. const double fin_energy = a.GetParGridFunctionEnergy(x) / (hradaptivity ? pmesh->GetGlobalNE() : 1); - double metric_part = fin_energy; - if (lim_const > 0.0 || adapt_lim_const > 0.0) + double fin_metric_energy = fin_energy; + if (lim_const > 0.0 || adapt_lim_const > 0.0 || surface_fit_const > 0.0) { lim_coeff.constant = 0.0; coef_zeta.constant = 0.0; - metric_part = a.GetParGridFunctionEnergy(x) / - (hradaptivity ? pmesh->GetGlobalNE() : 1); + coef_ls.constant = 0.0; + fin_metric_energy = a.GetParGridFunctionEnergy(x) / + (hradaptivity ? pmesh->GetGlobalNE() : 1); lim_coeff.constant = lim_const; coef_zeta.constant = adapt_lim_const; + coef_ls.constant = surface_fit_const; } if (myid == 0) { + std::cout << std::scientific << std::setprecision(4); cout << "Initial strain energy: " << init_energy - << " = metrics: " << init_energy - << " + limiting term: " << 0.0 << endl; + << " = metrics: " << init_metric_energy + << " + extra terms: " << init_energy - init_metric_energy << endl; cout << " Final strain energy: " << fin_energy - << " = metrics: " << metric_part - << " + limiting term: " << fin_energy - metric_part << endl; - cout << "The strain energy decreased by: " << setprecision(12) + << " = metrics: " << fin_metric_energy + << " + extra terms: " << fin_energy - fin_metric_energy << endl; + cout << "The strain energy decreased by: " << (init_energy - fin_energy) * 100.0 / init_energy << " %." << endl; } @@ -1098,6 +1181,25 @@ int main (int argc, char *argv[]) 600, 600, 300, 300); } + if (surface_fit_const > 0.0) + { + if (visualization) + { + socketstream vis2, vis3; + common::VisualizeField(vis2, "localhost", 19916, mat, + "Materials", 600, 900, 300, 300); + common::VisualizeField(vis3, "localhost", 19916, marker_gf, + "Surface dof", 900, 900, 300, 300); + } + double err_avg, err_max; + he_nlf_integ->GetSurfaceFittingErrors(err_avg, err_max); + if (myid == 0) + { + std::cout << "Avg fitting error: " << err_avg << std::endl + << "Max fitting error: " << err_max << std::endl; + } + } + // 19. Visualize the mesh displacement. if (visualization) { @@ -1126,6 +1228,7 @@ int main (int argc, char *argv[]) delete metric2; delete coeff1; delete adapt_evaluator; + delete adapt_surface; delete target_c; delete hr_adapt_coeff; delete adapt_coeff; diff --git a/miniapps/meshing/square01-tri.mesh b/miniapps/meshing/square01-tri.mesh new file mode 100644 index 0000000000..a807803d45 --- /dev/null +++ b/miniapps/meshing/square01-tri.mesh @@ -0,0 +1,147 @@ +MFEM mesh v1.0 + +# +# MFEM Geometry Types (see mesh/geom.hpp): +# +# POINT = 0 +# SEGMENT = 1 +# TRIANGLE = 2 +# SQUARE = 3 +# TETRAHEDRON = 4 +# CUBE = 5 +# PRISM = 6 +# + +dimension +2 + +elements +64 +1 2 0 1 25 +1 2 1 6 25 +1 2 6 5 25 +1 2 5 0 25 +1 2 1 2 26 +1 2 2 7 26 +1 2 7 6 26 +1 2 6 1 26 +1 2 2 3 27 +1 2 3 8 27 +1 2 8 7 27 +1 2 7 2 27 +1 2 3 4 28 +1 2 4 9 28 +1 2 9 8 28 +1 2 8 3 28 +1 2 5 6 29 +1 2 6 11 29 +1 2 11 10 29 +1 2 10 5 29 +1 2 6 7 30 +1 2 7 12 30 +1 2 12 11 30 +1 2 11 6 30 +1 2 7 8 31 +1 2 8 13 31 +1 2 13 12 31 +1 2 12 7 31 +1 2 8 9 32 +1 2 9 14 32 +1 2 14 13 32 +1 2 13 8 32 +1 2 10 11 33 +1 2 11 16 33 +1 2 16 15 33 +1 2 15 10 33 +1 2 11 12 34 +1 2 12 17 34 +1 2 17 16 34 +1 2 16 11 34 +1 2 12 13 35 +1 2 13 18 35 +1 2 18 17 35 +1 2 17 12 35 +1 2 13 14 36 +1 2 14 19 36 +1 2 19 18 36 +1 2 18 13 36 +1 2 15 16 37 +1 2 16 21 37 +1 2 21 20 37 +1 2 20 15 37 +1 2 16 17 38 +1 2 17 22 38 +1 2 22 21 38 +1 2 21 16 38 +1 2 17 18 39 +1 2 18 23 39 +1 2 23 22 39 +1 2 22 17 39 +1 2 18 19 40 +1 2 19 24 40 +1 2 24 23 40 +1 2 23 18 40 + +boundary +16 +2 1 0 1 +1 1 5 0 +2 1 1 2 +2 1 2 3 +2 1 3 4 +1 1 4 9 +1 1 10 5 +1 1 9 14 +1 1 15 10 +1 1 14 19 +2 1 21 20 +1 1 20 15 +2 1 22 21 +2 1 23 22 +1 1 19 24 +2 1 24 23 + +vertices +41 +2 +0.000000 0.000000 +0.250000 0.000000 +0.500000 0.000000 +0.750000 0.000000 +1.000000 0.000000 +0.000000 0.250000 +0.250000 0.250000 +0.500000 0.250000 +0.750000 0.250000 +1.000000 0.250000 +0.000000 0.500000 +0.250000 0.500000 +0.500000 0.500000 +0.750000 0.500000 +1.000000 0.500000 +0.000000 0.750000 +0.250000 0.750000 +0.500000 0.750000 +0.750000 0.750000 +1.000000 0.750000 +0.000000 1.000000 +0.250000 1.000000 +0.500000 1.000000 +0.750000 1.000000 +1.000000 1.000000 +0.125000 0.125000 +0.375000 0.125000 +0.625000 0.125000 +0.875000 0.125000 +0.125000 0.375000 +0.375000 0.375000 +0.625000 0.375000 +0.875000 0.375000 +0.125000 0.625000 +0.375000 0.625000 +0.625000 0.625000 +0.875000 0.625000 +0.125000 0.875000 +0.375000 0.875000 +0.625000 0.875000 +0.875000 0.875000 diff --git a/miniapps/shifted/diffusion.cpp b/miniapps/shifted/diffusion.cpp index 23839ae5e5..7311bec91c 100644 --- a/miniapps/shifted/diffusion.cpp +++ b/miniapps/shifted/diffusion.cpp @@ -33,14 +33,20 @@ // // Problem 1: Circular hole of radius 0.2 at the center of the domain. // Solves -nabla^2 u = 1 with homogeneous boundary conditions. -// mpirun -np 4 diffusion -m ../../data/inline-quad.mesh -rs 3 -o 1 -vis -lst 1 +// Dirichlet boundary condition +// mpirun -np 4 diffusion -rs 3 -o 1 -vis -lst 1 // mpirun -np 4 diffusion -m ../../data/inline-hex.mesh -rs 2 -o 2 -vis -lst 1 -ho 1 -alpha 10 +// Neumann boundary condition +// mpirun -np 4 diffusion -rs 3 -o 1 -vis -nlst 1 -ho 1 // // Problem 2: Circular hole of radius 0.2 at the center of the domain. -// Solves -nabla^2 u = f with inhomogeneous boundary conditions, and -// f is setup such that u = x^p + y^p, where p = 2 by default. +// Solves -nabla^2 u = f with inhomogeneous boundary conditions, +// and f is setup such that u = x^p + y^p, where p = 2 by default. // This is a 2D convergence test. +// Dirichlet BC // mpirun -np 4 diffusion -rs 2 -o 2 -vis -lst 2 +// Neumann BC (inhomogeneous condition derived using exact solution) +// mpirun -np 4 diffusion -rs 2 -o 2 -vis -nlst 2 -ho 1 // // Problem 3: Domain is y = [0, 1] but mesh is shifted to [-1.e-4, 1]. // Solves -nabla^2 u = f with inhomogeneous boundary conditions, @@ -53,9 +59,17 @@ // the boundary conditions. // mpirun -np 4 diffusion -rs 2 -o 1 -vis -lst 3 // -// Problem 4: Complex 2D shape: +// Problem 4: Complex 2D / 3D shapes: // Solves -nabla^2 u = 1 with homogeneous boundary conditions. -// mpirun -np 4 diffusion -rs 5 -lst 4 -alpha 2 +// mpirun -np 4 diffusion -m ../../data/inline-quad.mesh -rs 4 -lst 4 -alpha 10 +// mpirun -np 4 diffusion -m ../../data/inline-tri.mesh -rs 4 -lst 4 -alpha 10 +// mpirun -np 4 diffusion -m ../../data/inline-hex.mesh -rs 3 -lst 8 -alpha 10 +// mpirun -np 4 diffusion -m ../../data/inline-tet.mesh -rs 3 -lst 8 -alpha 10 +// +// Problem 5: Circular hole with homogeneous Neumann, triangular hole with +// inhomogeneous Dirichlet, and a square hole with homogeneous +// Dirichlet boundary condition. +// mpirun -np 4 diffusion -rs 3 -o 1 -vis -lst 5 -ho 1 -nlst 7 -alpha 10.0 -dc #include "mfem.hpp" #include "../common/mfem-common.hpp" @@ -86,7 +100,9 @@ int main(int argc, char *argv[]) int order = 2; bool visualization = true; int ser_ref_levels = 0; - int level_set_type = 1; + int dirichlet_level_set_type = -1; + int neumann_level_set_type = -1; + bool dirichlet_combo = false; int ho_terms = 0; double alpha = 1; bool include_cut_cell = false; @@ -102,8 +118,13 @@ int main(int argc, char *argv[]) "Enable or disable GLVis visualization."); args.AddOption(&ser_ref_levels, "-rs", "--refine-serial", "Number of times to refine the mesh uniformly in serial."); - args.AddOption(&level_set_type, "-lst", "--level-set-type", - "level-set-type:"); + args.AddOption(&dirichlet_level_set_type, "-lst", "--level-set-type", + "level-set-type."); + args.AddOption(&neumann_level_set_type, "-nlst", "--neumann-level-set-type", + "neumann-level-set-type."); + args.AddOption(&dirichlet_combo, "-dc", "--dcombo", + "no-dc", "--no-dcombo", + "Combination of two Dirichlet level sets."); args.AddOption(&ho_terms, "-ho", "--high-order", "Additional high-order terms to include"); args.AddOption(&alpha, "-alpha", "--alpha", @@ -111,7 +132,6 @@ int main(int argc, char *argv[]) args.AddOption(&include_cut_cell, "-cut", "--cut", "-no-cut-cell", "--no-cut-cell", "Include or not include elements cut by true boundary."); - args.Parse(); if (!args.Good()) { @@ -124,6 +144,14 @@ int main(int argc, char *argv[]) } if (myid == 0) { args.PrintOptions(cout); } + // Use Dirichlet level set if no level sets are specified. + if (dirichlet_level_set_type <= 0 && neumann_level_set_type <= 0) + { + dirichlet_level_set_type = 1; + } + MFEM_VERIFY((neumann_level_set_type > 0 && ho_terms < 1) == false, + "Shifted Neumann BC requires extra terms, i.e., -ho >= 1."); + // Enable hardware devices such as GPUs, and programming models such as CUDA, // OCCA, RAJA and OpenMP based on command line options. Device device("cpu"); @@ -133,6 +161,11 @@ int main(int argc, char *argv[]) Mesh mesh(mesh_file, 1, 1); int dim = mesh.Dimension(); for (int lev = 0; lev < ser_ref_levels; lev++) { mesh.UniformRefinement(); } + if (myid == 0) + { + std::cout << "Number of elements: " << mesh.GetNE() << std::endl; + } + MFEM_VERIFY(mesh.Conforming(), "AMR capability is not implemented yet!"); // MPI distribution. ParMesh pmesh(MPI_COMM_WORLD, mesh); @@ -147,15 +180,15 @@ int main(int argc, char *argv[]) Vector vxyz; // Set the nodal grid function for the mesh, and modify the nodal positions - // for level_set_type = 3 such that some of the mesh elements are intersected - // by the true boundary (y = 0). + // for dirichlet_level_set_type = 3 such that some of the mesh elements are + // intersected by the true boundary (y = 0). ParFiniteElementSpace pfespace_mesh(&pmesh, &fec, dim); pmesh.SetNodalFESpace(&pfespace_mesh); ParGridFunction x_mesh(&pfespace_mesh); pmesh.SetNodalGridFunction(&x_mesh); vxyz = *pmesh.GetNodes(); int nodes_cnt = vxyz.Size()/dim; - if (level_set_type == 3) + if (dirichlet_level_set_type == 3) { for (int i = 0; i < nodes_cnt; i++) { @@ -174,21 +207,58 @@ int main(int argc, char *argv[]) // Define the solution vector x as a finite element grid function // corresponding to pfespace. ParGridFunction x(&pfespace); - // ParGridFunction for level_set_value. - ParGridFunction level_set_val(&pfespace); // Determine if each element in the ParMesh is inside the actual domain, // partially cut by its boundary, or completely outside the domain. - Dist_Level_Set_Coefficient dist_fun_level_coef(level_set_type); - level_set_val.ProjectCoefficient(dist_fun_level_coef); - // Exchange information for ghost elements i.e. elements that share a face - // with element on the current processor, but belong to another processor. - level_set_val.ExchangeFaceNbrData(); - // Setup the class to mark all elements based on whether they are located - // inside or outside the true domain, or intersected by the true boundary. - ShiftedFaceMarker marker(pmesh, level_set_val, pfespace, include_cut_cell); + // Setup the level-set coefficients, and mark the elements. + Dist_Level_Set_Coefficient *dirichlet_dist_coef = NULL; + Dist_Level_Set_Coefficient *dirichlet_dist_coef_2 = NULL; + Dist_Level_Set_Coefficient *neumann_dist_coef = NULL; + Combo_Level_Set_Coefficient combo_dist_coef; + + ParGridFunction level_set_gf(&pfespace); + ShiftedFaceMarker marker(pmesh, pfespace, include_cut_cell); Array elem_marker; - marker.MarkElements(elem_marker); + + // Dirichlet level-set. + if (dirichlet_level_set_type > 0) + { + dirichlet_dist_coef = new Dist_Level_Set_Coefficient(dirichlet_level_set_type); + const double dx = AvgElementSize(pmesh); + PDEFilter filter(pmesh, dx); + filter.Filter(*dirichlet_dist_coef, level_set_gf); + //level_set_gf.ProjectCoefficient(*dirichlet_dist_coef); + // Exchange information for ghost elements i.e. elements that share a face + // with element on the current processor, but belong to another processor. + level_set_gf.ExchangeFaceNbrData(); + // Setup the class to mark all elements based on whether they are located + // inside or outside the true domain, or intersected by the true boundary. + marker.MarkElements(level_set_gf, elem_marker); + combo_dist_coef.Add_Level_Set_Coefficient(*dirichlet_dist_coef); + } + + // Second Dirichlet level-set. + if (dirichlet_combo) + { + MFEM_VERIFY(dirichlet_level_set_type == 5, + "The combo level set example has been only set for" + " dirichlet_level_set_type == 5."); + dirichlet_dist_coef_2 = new Dist_Level_Set_Coefficient(6); + level_set_gf.ProjectCoefficient(*dirichlet_dist_coef_2); + level_set_gf.ExchangeFaceNbrData(); + marker.MarkElements(level_set_gf, elem_marker); + combo_dist_coef.Add_Level_Set_Coefficient(*dirichlet_dist_coef_2); + } + + // Neumann level-set. + if (neumann_level_set_type > 0) + { + neumann_dist_coef = new Dist_Level_Set_Coefficient(neumann_level_set_type); + level_set_gf.ProjectCoefficient(*neumann_dist_coef); + level_set_gf.ExchangeFaceNbrData(); + marker.MarkElements(level_set_gf, elem_marker); + combo_dist_coef.Add_Level_Set_Coefficient(*neumann_dist_coef); + } // Visualize the element markers. if (visualization) @@ -224,7 +294,7 @@ int main(int argc, char *argv[]) int visport = 19916, s = 350; socketstream sol_sock; common::VisualizeField(sol_sock, vishost, visport, face_dofs, - "Shifted Face Dofs", 0, s, s, s, "Rjmp"); + "Shifted Face Dofs", 0, s, s, s, "Rjmplo"); } // Make a list of inactive tdofs that will be eliminated from the system. @@ -241,16 +311,21 @@ int main(int argc, char *argv[]) ParFiniteElementSpace distance_vec_space(&pmesh, &fec, dim); ParGridFunction distance(&distance_vec_space); VectorCoefficient *dist_vec = NULL; - // Compute the distance field using the HeatDistanceSolver for - // level_set_type == 4 or analytically for all other level set types. - if (level_set_type == 4) + // Compute the distance field analytically or using the HeatDistanceSolver. + if (dirichlet_level_set_type == 1 || dirichlet_level_set_type == 2 || + dirichlet_level_set_type == 3) + { + // Analytic distance vector. + dist_vec = new Dist_Vector_Coefficient(dim, dirichlet_level_set_type); + distance.ProjectDiscCoefficient(*dist_vec); + } + else { // Discrete distance vector. double dx = AvgElementSize(pmesh); ParGridFunction filt_gf(&pfespace); - PDEFilter *filter = new PDEFilter(pmesh, 2.0 * dx); - filter->Filter(dist_fun_level_coef, filt_gf); - delete filter; + PDEFilter filter(pmesh, 2.0 * dx); + filter.Filter(combo_dist_coef, filt_gf); GridFunctionCoefficient ls_filt_coeff(&filt_gf); if (visualization) @@ -262,18 +337,12 @@ int main(int argc, char *argv[]) "Input Level Set", 0, 2*s, s, s, "Rjmm"); } - HeatDistanceSolver dist_func(2.0 * dx* dx); + HeatDistanceSolver dist_func(2.0 * dx * dx); dist_func.print_level = 1; dist_func.smooth_steps = 1; dist_func.ComputeVectorDistance(ls_filt_coeff, distance); dist_vec = new VectorGridFunctionCoefficient(&distance); } - else - { - // Analytic distance vector. - dist_vec = new Dist_Vector_Coefficient(dim, level_set_type); - distance.ProjectDiscCoefficient(*dist_vec); - } // Visualize the distance vector. if (visualization) @@ -295,7 +364,7 @@ int main(int argc, char *argv[]) { if (!include_cut_cell && (elem_marker[i] == ShiftedFaceMarker::SBElementType::OUTSIDE || - elem_marker[i] == ShiftedFaceMarker::SBElementType::CUT)) + elem_marker[i] >= ShiftedFaceMarker::SBElementType::CUT)) { pmesh.SetAttribute(i, max_elem_attr+1); inactive_elements = true; @@ -317,50 +386,129 @@ int main(int argc, char *argv[]) // the FEM linear system. ParLinearForm b(&pfespace); FunctionCoefficient *rhs_f = NULL; - if (level_set_type == 1 || level_set_type == 4) + if (dirichlet_level_set_type == 1 || dirichlet_level_set_type == 4 || + dirichlet_level_set_type == 5 || dirichlet_level_set_type == 6 || + dirichlet_level_set_type == 8 || + neumann_level_set_type == 1 || neumann_level_set_type == 7) { rhs_f = new FunctionCoefficient(rhs_fun_circle); } - else if (level_set_type == 2) + else if (dirichlet_level_set_type == 2 || neumann_level_set_type == 2) { rhs_f = new FunctionCoefficient(rhs_fun_xy_exponent); } - else if (level_set_type == 3) + else if (dirichlet_level_set_type == 3) { rhs_f = new FunctionCoefficient(rhs_fun_xy_sinusoidal); } else { MFEM_ABORT("RHS function not set for level set type.\n"); } b.AddDomainIntegrator(new DomainLFIntegrator(*rhs_f), ess_elem); + // Exact solution to project for Dirichlet boundaries + FunctionCoefficient *exactCoef = NULL; // Dirichlet BC that must be imposed on the true boundary. ShiftedFunctionCoefficient *dbcCoef = NULL; - if (level_set_type == 1 || level_set_type == 4) + if (dirichlet_level_set_type == 1 || dirichlet_level_set_type >= 4) { - dbcCoef = new ShiftedFunctionCoefficient(dirichlet_velocity_circle); + dbcCoef = new ShiftedFunctionCoefficient(homogeneous); + exactCoef = new FunctionCoefficient(homogeneous); } - else if (level_set_type == 2) + else if (dirichlet_level_set_type == 2) { dbcCoef = new ShiftedFunctionCoefficient(dirichlet_velocity_xy_exponent); + exactCoef = new FunctionCoefficient(dirichlet_velocity_xy_exponent); } - else if (level_set_type == 3) + else if (dirichlet_level_set_type == 3) { dbcCoef = new ShiftedFunctionCoefficient(dirichlet_velocity_xy_sinusoidal); + exactCoef = new FunctionCoefficient(dirichlet_velocity_xy_sinusoidal); } - else + + ShiftedFunctionCoefficient *dbcCoefCombo = NULL; + if (dirichlet_combo) { - MFEM_ABORT("Dirichlet velocity function not set for level set type.\n"); + dbcCoefCombo = new ShiftedFunctionCoefficient(0.015); } - // Add integrators corresponding to the shifted boundary method (SBM). - b.AddInteriorFaceIntegrator(new SBM2DirichletLFIntegrator(&pmesh, *dbcCoef, - alpha, *dist_vec, - elem_marker, - include_cut_cell, - ho_terms)); - b.AddBdrFaceIntegrator(new SBM2DirichletLFIntegrator(&pmesh, *dbcCoef, - alpha, *dist_vec, - elem_marker, - include_cut_cell, - ho_terms), ess_shift_bdr); + + // Homogeneous Neumann boundary condition coefficient + ShiftedFunctionCoefficient *nbcCoef = NULL; + ShiftedVectorFunctionCoefficient *normalbcCoef = NULL; + if (neumann_level_set_type == 1) + { + nbcCoef = new ShiftedFunctionCoefficient(homogeneous); + normalbcCoef = new ShiftedVectorFunctionCoefficient(dim, normal_vector_1); + } + else if (neumann_level_set_type == 2) + { + nbcCoef = new ShiftedFunctionCoefficient(traction_xy_exponent); + normalbcCoef = new ShiftedVectorFunctionCoefficient(dim, normal_vector_1); + exactCoef = new FunctionCoefficient(dirichlet_velocity_xy_exponent); + } + else if (neumann_level_set_type == 7) + { + nbcCoef = new ShiftedFunctionCoefficient(homogeneous); + normalbcCoef = new ShiftedVectorFunctionCoefficient(dim, normal_vector_2); + } + else if (neumann_level_set_type > 0) + { + MFEM_ABORT(" Normal vector coefficient not implemented for level set."); + } + + // Add integrators corresponding to the shifted boundary method (SBM) + // for Dirichlet boundaries. + // For each LinearFormIntegrator, we indicate the marker that we have used + // for the cut-cell corresponding to the level-set. + int ls_cut_marker = ShiftedFaceMarker::SBElementType::CUT; + // For each BilinearFormIntegrators, we make a list of the markers + // corresponding to the cut-cell whose faces they will be applied to. + Array bf_dirichlet_marker(0), bf_neumann_marker(0); + + if (dirichlet_level_set_type > 0) + { + b.AddInteriorFaceIntegrator(new SBM2DirichletLFIntegrator(&pmesh, *dbcCoef, + alpha, *dist_vec, + elem_marker, + include_cut_cell, + ho_terms, + ls_cut_marker)); + b.AddBdrFaceIntegrator(new SBM2DirichletLFIntegrator(&pmesh, *dbcCoef, + alpha, *dist_vec, + elem_marker, + include_cut_cell, + ho_terms, + ls_cut_marker), + ess_shift_bdr); + bf_dirichlet_marker.Append(ls_cut_marker); + ls_cut_marker += 1; + } + + if (dirichlet_combo) + { + b.AddInteriorFaceIntegrator(new SBM2DirichletLFIntegrator(&pmesh, *dbcCoefCombo, + alpha, *dist_vec, + elem_marker, + include_cut_cell, + ho_terms, + ls_cut_marker)); + bf_dirichlet_marker.Append(ls_cut_marker); + ls_cut_marker += 1; + } + + // Add integrators corresponding to the shifted boundary method (SBM) + // for Neumann boundaries. + if (neumann_level_set_type > 0) + { + MFEM_VERIFY(!include_cut_cell, "include_cut_cell option must be set to" + " false for Neumann boundary conditions."); + b.AddInteriorFaceIntegrator(new SBM2NeumannLFIntegrator( + &pmesh, *nbcCoef, *dist_vec, + *normalbcCoef, elem_marker, + ho_terms, include_cut_cell, + ls_cut_marker)); + bf_neumann_marker.Append(ls_cut_marker); + ls_cut_marker += 1; + } + b.Assemble(); // Set up the bilinear form a(.,.) on the finite element space corresponding @@ -369,22 +517,43 @@ int main(int argc, char *argv[]) ParBilinearForm a(&pfespace); ConstantCoefficient one(1.); a.AddDomainIntegrator(new DiffusionIntegrator(one), ess_elem); - a.AddInteriorFaceIntegrator(new SBM2DirichletIntegrator(&pmesh, alpha, - *dist_vec, - elem_marker, - include_cut_cell, - ho_terms)); - a.AddBdrFaceIntegrator(new SBM2DirichletIntegrator(&pmesh, alpha, *dist_vec, - elem_marker, - include_cut_cell, - ho_terms), ess_shift_bdr); + if (dirichlet_level_set_type > 0) + { + a.AddInteriorFaceIntegrator(new SBM2DirichletIntegrator(&pmesh, alpha, + *dist_vec, + elem_marker, + bf_dirichlet_marker, + include_cut_cell, + ho_terms)); + a.AddBdrFaceIntegrator(new SBM2DirichletIntegrator(&pmesh, alpha, *dist_vec, + elem_marker, + bf_dirichlet_marker, + include_cut_cell, + ho_terms), ess_shift_bdr); + } + + // Add neumann bilinearform integrator. + if (neumann_level_set_type > 0) + { + a.AddInteriorFaceIntegrator(new SBM2NeumannIntegrator(&pmesh, + *dist_vec, + *normalbcCoef, + elem_marker, + bf_neumann_marker, + include_cut_cell, + ho_terms)); + } // Assemble the bilinear form and the corresponding linear system, // applying any necessary transformations. a.Assemble(); // Project the exact solution as an initial condition for Dirichlet boundary. - x.ProjectCoefficient(*dbcCoef); + if (!exactCoef) + { + exactCoef = new FunctionCoefficient(homogeneous); + } + x.ProjectCoefficient(*exactCoef); // Form the linear system and solve it. OperatorPtr A; @@ -392,13 +561,13 @@ int main(int argc, char *argv[]) a.FormLinearSystem(ess_tdof_list, x, b, A, X, B); Solver *prec = new HypreBoomerAMG; - BiCGSTABSolver *bicg = new BiCGSTABSolver(MPI_COMM_WORLD); - bicg->SetRelTol(1e-12); - bicg->SetMaxIter(2000); - bicg->SetPrintLevel(1); - bicg->SetPreconditioner(*prec); - bicg->SetOperator(*A); - bicg->Mult(B, X); + BiCGSTABSolver bicg(MPI_COMM_WORLD); + bicg.SetRelTol(1e-12); + bicg.SetMaxIter(500); + bicg.SetPrintLevel(1); + bicg.SetPreconditioner(*prec); + bicg.SetOperator(*A); + bicg.Mult(B, X); // Recover the solution as a finite element grid function. a.RecoverFEMSolution(X, b, x); @@ -411,22 +580,19 @@ int main(int argc, char *argv[]) sol_ofs.precision(8); x.SaveAsOne(sol_ofs); - // Save the solution in ParaView format if (visualization) { + // Save the solution in ParaView format. ParaViewDataCollection dacol("ParaViewDiffusion", &pmesh); dacol.SetLevelsOfDetail(order); dacol.RegisterField("distance", &distance); + dacol.RegisterField("level_set", &level_set_gf); dacol.RegisterField("solution", &x); dacol.SetTime(1.0); dacol.SetCycle(1); dacol.Save(); - } - - // Send the solution by socket to a GLVis server. - if (visualization) - { + // Send the solution by socket to a GLVis server. char vishost[] = "localhost"; int visport = 19916, s = 350; socketstream sol_sock; @@ -435,7 +601,8 @@ int main(int argc, char *argv[]) } // Construct an error grid function if the exact solution is known. - if (level_set_type == 2 || level_set_type == 3) + if (dirichlet_level_set_type == 2 || dirichlet_level_set_type == 3 || + (dirichlet_level_set_type == -1 && neumann_level_set_type == 2)) { ParGridFunction err(x); Vector pxyz(dim); @@ -445,11 +612,11 @@ int main(int argc, char *argv[]) pxyz(0) = vxyz(i); pxyz(1) = vxyz(i+nodes_cnt); double exact_val = 0.; - if (level_set_type == 2) + if (dirichlet_level_set_type == 2 || neumann_level_set_type == 2) { exact_val = dirichlet_velocity_xy_exponent(pxyz); } - else if (level_set_type == 3) + else if (dirichlet_level_set_type == 3) { exact_val = dirichlet_velocity_xy_sinusoidal(pxyz); } @@ -465,19 +632,28 @@ int main(int argc, char *argv[]) "Error", 2*s, 0, s, s, "Rj"); } - const double global_error = x.ComputeL2Error(*dbcCoef); + const double global_error = x.ComputeL2Error(*exactCoef); if (myid == 0) { std::cout << "Global L2 error: " << global_error << endl; } } + const double norm = x.ComputeL1Error(one); + if (myid == 0) { std::cout << setprecision(10) << norm << std::endl; } + // Free the used memory. delete prec; - delete bicg; + delete normalbcCoef; + delete nbcCoef; + delete dbcCoefCombo; delete dbcCoef; + delete exactCoef; delete rhs_f; delete dist_vec; + delete neumann_dist_coef; + delete dirichlet_dist_coef; + delete dirichlet_dist_coef_2; MPI_Finalize(); diff --git a/miniapps/shifted/dist_solver.hpp b/miniapps/shifted/dist_solver.hpp index 603260e53c..1f4589cce1 100644 --- a/miniapps/shifted/dist_solver.hpp +++ b/miniapps/shifted/dist_solver.hpp @@ -229,7 +229,8 @@ class PDEFilter { public: PDEFilter(ParMesh &mesh, double rh, int order = 2, - int maxiter=100, double rtol=1e-7, double atol=1e-15, int print_lv=0) + int maxiter = 100, double rtol = 1e-12, + double atol = 1e-15, int print_lv = 0) : rr(rh), fecp(order, mesh.Dimension()), fesp(&mesh, &fecp, 1), diff --git a/miniapps/shifted/distance.cpp b/miniapps/shifted/distance.cpp index 69f1507a33..da5fbe12d4 100644 --- a/miniapps/shifted/distance.cpp +++ b/miniapps/shifted/distance.cpp @@ -67,20 +67,25 @@ // mpirun -np 4 distance -m ./corners.mesh -p 0 -rs 3 -t 200.0 // // Problem 1: zero level set: circle / sphere at the center of the mesh -// mpirun -np 4 distance -m ../../data/inline-quad.mesh -rs 3 -o 2 -t 1.0 -p 1 +// mpirun -np 4 distance -m ../../data/inline-quad.mesh -rs 3 -o 2 -t 1.0 -p 1 // mpirun -np 4 distance -m ../../data/periodic-cube.mesh -rs 2 -o 2 -p 1 -s 1 // // Problem 2: zero level set: perturbed sine // mpirun -np 4 distance -m ../../data/inline-quad.mesh -rs 3 -o 2 -t 1.0 -p 2 +// mpirun -np 4 distance -m ../../data/amr-quad.mesh -rs 3 -o 2 -t 1.0 -p 2 // // Problem 3: level set: Gyroid // mpirun -np 4 distance -m ../../data/periodic-square.mesh -rs 5 -o 2 -t 1.0 -p 3 -// mpirun -np 4 distance -m ../../data/periodic-cube.mesh -rs 3 -o 2 -t 1.0 -p 3 +// mpirun -np 4 distance -m ../../data/periodic-cube.mesh -rs 3 -o 2 -t 1.0 -p 3 +// +// Problem 4: level set: Union of doughnut and swiss cheese shapes +// mpirun -np 4 distance -m ../../data/inline-hex.mesh -rs 3 -o 2 -t 1.0 -p 4 #include #include -#include "dist_solver.hpp" #include "../common/mfem-common.hpp" +#include "dist_solver.hpp" +#include "sbm_aux.hpp" using namespace std; using namespace mfem; @@ -195,7 +200,8 @@ int main(int argc, char *argv[]) "0: Point source\n\t" "1: Circle / sphere level set in 2D / 3D\n\t" "2: 2D sine-looking level set\n\t" - "3: Gyroid level set in 2D or 3D"); + "3: Gyroid level set in 2D or 3D\n\t" + "4: Combo of a doughnut and swiss cheese shapes in 3D."); args.AddOption(&rs_levels, "-rs", "--refine-serial", "Number of times to refine the mesh uniformly in serial."); args.AddOption(&order, "-o", "--order", @@ -234,7 +240,7 @@ int main(int argc, char *argv[]) ParMesh pmesh(MPI_COMM_WORLD, mesh); mesh.Clear(); - Coefficient *ls_coeff; + Coefficient *ls_coeff = nullptr; int smooth_steps; if (problem == 0) { @@ -251,11 +257,17 @@ int main(int argc, char *argv[]) ls_coeff = new FunctionCoefficient(sine_ls); smooth_steps = 0; } - else + else if (problem == 3) { ls_coeff = new FunctionCoefficient(Gyroid); smooth_steps = 0; } + else if (problem == 4) + { + ls_coeff = new FunctionCoefficient(doughnut_cheese); + smooth_steps = 0; + } + else { MFEM_ABORT("Unrecognized -problem option."); } const double dx = AvgElementSize(pmesh); DistanceSolver *dist_solver = NULL; @@ -287,13 +299,12 @@ int main(int argc, char *argv[]) // Smooth-out Gibbs oscillations from the input level set. The smoothing // parameter here is specified to be mesh dependent with length scale dx. ParGridFunction filt_gf(&pfes_s); - PDEFilter *filter = new PDEFilter(pmesh, 1.0 * dx); if (problem != 0) { - filter->Filter(*ls_coeff, filt_gf); + PDEFilter filter(pmesh, 1.0 * dx); + filter.Filter(*ls_coeff, filt_gf); } else { filt_gf.ProjectCoefficient(*ls_coeff); } - delete filter; delete ls_coeff; GridFunctionCoefficient ls_filt_coeff(&filt_gf); @@ -336,10 +347,12 @@ int main(int argc, char *argv[]) dacol.Save(); ConstantCoefficient zero(0.0); - const double d_norm = distance_s.ComputeL2Error(zero); + const double s_norm = distance_s.ComputeL2Error(zero), + v_norm = distance_v.ComputeL2Error(zero); if (myid == 0) { - cout << fixed << setprecision(10) << "Norm: " << d_norm << std::endl; + cout << fixed << setprecision(10) << "Norms: " + << s_norm << " " << v_norm << std::endl; } delete dist_solver; diff --git a/miniapps/shifted/makefile b/miniapps/shifted/makefile index 0173538711..f23a44bec0 100644 --- a/miniapps/shifted/makefile +++ b/miniapps/shifted/makefile @@ -25,8 +25,10 @@ include $(DEFAULTS_MK) MFEM_LIB_FILE = mfem_is_not_built -include $(CONFIG_MK) -SHIFTED_COMMON_SRC = dist_solver.cpp sbm_solver.cpp marking.cpp -SHIFTED_COMMON_OBJ = $(SHIFTED_COMMON_SRC:.cpp=.o) +DIFFUSION_SRC = diffusion.cpp dist_solver.cpp sbm_solver.cpp marking.cpp +DIFFUSION_OBJ = $(DIFFUSION_SRC:.cpp=.o) +DISTANCE_SRC = distance.cpp dist_solver.cpp +DISTANCE_OBJ = $(DISTANCE_SRC:.cpp=.o) PAR_MINIAPPS = distance diffusion @@ -51,14 +53,17 @@ COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\ %: %.cpp %.o: %.cpp -%.o: $(SRC)%.cpp $(MFEM_LIB_FILE) $(CONFIG_MK) | lib-common +%.o: $(SRC)%.cpp $(wildcard $(SRC)%.hpp) $(MFEM_LIB_FILE) $(CONFIG_MK) | lib-common $(MFEM_CXX) $(MFEM_FLAGS) -c $< -o $@ -%: %.o $(SHIFTED_COMMON_OBJ) - $(MFEM_CXX) $(MFEM_LINK_FLAGS) $^ -o $@ $(COMMON_LIB) $(MFEM_LIBS) - all: $(MINIAPPS) +distance: sbm_aux.hpp $(DISTANCE_OBJ) + $(MFEM_CXX) $(MFEM_LINK_FLAGS) -o $@ $(DISTANCE_OBJ) $(COMMON_LIB) $(MFEM_LIBS) + +diffusion: sbm_aux.hpp $(DIFFUSION_OBJ) + $(MFEM_CXX) $(MFEM_LINK_FLAGS) -o $@ $(DIFFUSION_OBJ) $(COMMON_LIB) $(MFEM_LIBS) + # Rule for building lib-common lib-common: $(MAKE) -C $(MFEM_BUILD_DIR)/miniapps/common diff --git a/miniapps/shifted/marking.cpp b/miniapps/shifted/marking.cpp index da8a8fd742..b59bede325 100644 --- a/miniapps/shifted/marking.cpp +++ b/miniapps/shifted/marking.cpp @@ -14,26 +14,42 @@ namespace mfem { -void ShiftedFaceMarker::MarkElements(Array &elem_marker) const +void ShiftedFaceMarker::MarkElements(const ParGridFunction &ls_func, + Array &elem_marker) { elem_marker.SetSize(pmesh.GetNE() + pmesh.GetNSharedFaces()); - elem_marker = SBElementType::INSIDE; + if (!initial_marking_done) { elem_marker = SBElementType::INSIDE; } + else { level_set_index += 1; } IntegrationRules IntRulesLo(0, Quadrature1D::GaussLobatto); + // This tolerance is relevant for points that are exactly on the zero LS. + const double eps = 1e-10; + auto outside_of_domain = [&](double value) + { + if (include_cut_cell) + { + // Points on the zero LS are considered outside the domain. + return (value - eps < 0.0); + } + else + { + // Points on the zero LS are considered inside the domain. + return (value + eps < 0.0); + } + }; + Vector vals; // Check elements on the current MPI rank for (int i = 0; i < pmesh.GetNE(); i++) { - ElementTransformation *Tr = pmesh.GetElementTransformation(i); - const IntegrationRule &ir = - IntRulesLo.Get(pmesh.GetElementBaseGeometry(i), 4*Tr->OrderJ()); + const IntegrationRule &ir = pfes_sltn->GetFE(i)->GetNodes(); ls_func.GetValues(i, ir, vals); int count = 0; for (int j = 0; j < ir.GetNPoints(); j++) { - if (vals(j) <= 0.) { count++; } + if (outside_of_domain(vals(j))) { count++; } } if (count == ir.GetNPoints()) // completely outside @@ -42,7 +58,9 @@ void ShiftedFaceMarker::MarkElements(Array &elem_marker) const } else if (count > 0) // partially outside { - elem_marker[i] = SBElementType::CUT; + MFEM_VERIFY(elem_marker[i] <= SBElementType::OUTSIDE, + " One element cut by multiple level-sets."); + elem_marker[i] = SBElementType::CUT + level_set_index; } } @@ -57,8 +75,7 @@ void ShiftedFaceMarker::MarkElements(Array &elem_marker) const ElementTransformation *eltr = pmesh.GetFaceNbrElementTransformation(Elem2NbrNo); const IntegrationRule &ir = - IntRulesLo.Get(pmesh.GetElementBaseGeometry(0), - 4*eltr->OrderJ()); + IntRulesLo.Get(pmesh.GetElementBaseGeometry(0), 4*eltr->OrderJ()); const int nip = ir.GetNPoints(); vals.SetSize(nip); @@ -66,19 +83,24 @@ void ShiftedFaceMarker::MarkElements(Array &elem_marker) const for (int j = 0; j < nip; j++) { const IntegrationPoint &ip = ir.IntPoint(j); - vals[j] = ls_func.GetValue(tr->Elem2No, ip); - if (vals[j] <= 0.) { count++; } + vals(j) = ls_func.GetValue(tr->Elem2No, ip); + if (outside_of_domain(vals(j))) { count++; } } if (count == ir.GetNPoints()) // completely outside { + MFEM_VERIFY(elem_marker[i] != SBElementType::OUTSIDE, + "An element cannot be excluded by more than 1 level-set."); elem_marker[i] = SBElementType::OUTSIDE; } else if (count > 0) // partially outside { - elem_marker[i] = SBElementType::CUT; + MFEM_VERIFY(elem_marker[i] <= SBElementType::OUTSIDE, + "An element cannot be cut by multiple level-sets."); + elem_marker[i] = SBElementType::CUT + level_set_index; } } + initial_marking_done = true; } void ShiftedFaceMarker::ListShiftedFaceDofs(const Array &elem_marker, @@ -102,27 +124,27 @@ void ShiftedFaceMarker::ListShiftedFaceDofs(const Array &elem_marker, { int te1 = elem_marker[tr->Elem1No], te2 = elem_marker[tr->Elem2No]; if (!include_cut_cell && - te1 == ShiftedFaceMarker::CUT && te2 == ShiftedFaceMarker::INSIDE) + te1 >= ShiftedFaceMarker::CUT && te2 == ShiftedFaceMarker::INSIDE) { - pfes_sltn.GetFaceDofs(f, dofs); + pfes_sltn->GetFaceDofs(f, dofs); sface_dof_list.Append(dofs); } if (!include_cut_cell && - te1 == ShiftedFaceMarker::INSIDE && te2 == ShiftedFaceMarker::CUT) + te1 == ShiftedFaceMarker::INSIDE && te2 >= ShiftedFaceMarker::CUT) { - pfes_sltn.GetFaceDofs(f, dofs); + pfes_sltn->GetFaceDofs(f, dofs); sface_dof_list.Append(dofs); } if (include_cut_cell && - te1 == SBElementType::CUT && te2 == SBElementType::OUTSIDE) + te1 >= SBElementType::CUT && te2 == SBElementType::OUTSIDE) { - pfes_sltn.GetFaceDofs(f, dofs); + pfes_sltn->GetFaceDofs(f, dofs); sface_dof_list.Append(dofs); } if (include_cut_cell && - te1 == SBElementType::OUTSIDE && te2 == SBElementType::CUT) + te1 == SBElementType::OUTSIDE && te2 >= SBElementType::CUT) { - pfes_sltn.GetFaceDofs(f, dofs); + pfes_sltn->GetFaceDofs(f, dofs); sface_dof_list.Append(dofs); } } @@ -136,9 +158,9 @@ void ShiftedFaceMarker::ListShiftedFaceDofs(const Array &elem_marker, FaceElementTransformations *tr = pmesh.GetBdrFaceTransformations(i); if (tr != NULL) { - if (elem_marker[tr->Elem1No] == SBElementType::CUT) + if (elem_marker[tr->Elem1No] >= SBElementType::CUT) { - pfes_sltn.GetFaceDofs(pmesh.GetBdrFace(i), dofs); + pfes_sltn->GetFaceDofs(pmesh.GetBdrFace(i), dofs); sface_dof_list.Append(dofs); } } @@ -158,15 +180,15 @@ void ShiftedFaceMarker::ListShiftedFaceDofs(const Array &elem_marker, // Add if the element on this MPI rank is completely inside the domain // and the element on other MPI rank is not. if (!include_cut_cell && - te2 == ShiftedFaceMarker::CUT && te1 == ShiftedFaceMarker::INSIDE) + te2 >= ShiftedFaceMarker::CUT && te1 == ShiftedFaceMarker::INSIDE) { - pfes_sltn.GetFaceDofs(faceno, dofs); + pfes_sltn->GetFaceDofs(faceno, dofs); sface_dof_list.Append(dofs); } if (include_cut_cell && - te2 == SBElementType::OUTSIDE && te1 == SBElementType::CUT) + te2 == SBElementType::OUTSIDE && te1 >= SBElementType::CUT) { - pfes_sltn.GetFaceDofs(faceno, dofs); + pfes_sltn->GetFaceDofs(faceno, dofs); sface_dof_list.Append(dofs); } } @@ -198,7 +220,7 @@ void ShiftedFaceMarker::ListEssentialTDofs(const Array &elem_marker, FaceElementTransformations *tr = pmesh.GetBdrFaceTransformations(i); if (tr != NULL) { - if (elem_marker[tr->Elem1No] == SBElementType::CUT) + if (elem_marker[tr->Elem1No] >= SBElementType::CUT) { pmesh.SetBdrAttribute(i, pmesh_bdr_attr_max+1); sbm_at_true_boundary = true; @@ -225,7 +247,7 @@ void ShiftedFaceMarker::ListEssentialTDofs(const Array &elem_marker, } } Array ess_vdofs_bdr; - pfes_sltn.GetEssentialVDofs(ess_bdr, ess_vdofs_bdr); + pfes_sltn->GetEssentialVDofs(ess_bdr, ess_vdofs_bdr); // Get all dofs associated with elements outside the domain or intersected by // the boundary. @@ -235,9 +257,9 @@ void ShiftedFaceMarker::ListEssentialTDofs(const Array &elem_marker, { if (!include_cut_cell && (elem_marker[e] == SBElementType::OUTSIDE || - elem_marker[e] == SBElementType::CUT)) + elem_marker[e] >= SBElementType::CUT)) { - pfes_sltn.GetElementVDofs(e, dofs); + pfes_sltn->GetElementVDofs(e, dofs); for (int i = 0; i < dofs.Size(); i++) { ess_vdofs[dofs[i]] = -1; @@ -246,7 +268,7 @@ void ShiftedFaceMarker::ListEssentialTDofs(const Array &elem_marker, if (include_cut_cell && elem_marker[e] == SBElementType::OUTSIDE) { - pfes_sltn.GetElementVDofs(e, dofs); + pfes_sltn->GetElementVDofs(e, dofs); for (int i = 0; i < dofs.Size(); i++) { ess_vdofs[dofs[i]] = -1; @@ -271,13 +293,13 @@ void ShiftedFaceMarker::ListEssentialTDofs(const Array &elem_marker, // Synchronize for (int i = 0; i < ess_vdofs.Size() ; i++) { ess_vdofs[i] += 1; } - pfes_sltn.Synchronize(ess_vdofs); + pfes_sltn->Synchronize(ess_vdofs); for (int i = 0; i < ess_vdofs.Size() ; i++) { ess_vdofs[i] -= 1; } // Convert to tdofs Array ess_tdofs; - pfes_sltn.GetRestrictionMatrix()->BooleanMult(ess_vdofs, ess_tdofs); - pfes_sltn.MarkerToList(ess_tdofs, ess_tdof_list); + pfes_sltn->GetRestrictionMatrix()->BooleanMult(ess_vdofs, ess_tdofs); + pfes_sltn->MarkerToList(ess_tdofs, ess_tdof_list); } void ShiftedFaceMarker::ListShiftedFaceDofs2(const Array &elem_marker, @@ -288,14 +310,14 @@ void ShiftedFaceMarker::ListShiftedFaceDofs2(const Array &elem_marker, L2_FECollection mat_coll(0, pmesh.Dimension()); ParFiniteElementSpace mat_fes(&pmesh, &mat_coll); ParGridFunction mat(&mat_fes); - ParGridFunction marker_gf(&pfes_sltn); + ParGridFunction marker_gf(pfes_sltn); for (int i = 0; i < pmesh.GetNE(); i++) { // 0 is inside, 1 is outside. mat(i) = 0.0; if (elem_marker[i] == SBElementType::OUTSIDE) { mat(i) = 1.0; } - if (elem_marker[i] == SBElementType::CUT && include_cut_cell == false) + if (elem_marker[i] >= SBElementType::CUT && include_cut_cell == false) { mat(i) = 1.0; } } @@ -319,9 +341,9 @@ void ShiftedFaceMarker::ListShiftedFaceDofs2(const Array &elem_marker, FaceElementTransformations *tr = pmesh.GetBdrFaceTransformations(i); if (tr != NULL) { - if (elem_marker[tr->Elem1No] == SBElementType::CUT) + if (elem_marker[tr->Elem1No] >= SBElementType::CUT) { - pfes_sltn.GetFaceDofs(pmesh.GetBdrFace(i), dofs); + pfes_sltn->GetFaceDofs(pmesh.GetBdrFace(i), dofs); sface_dof_list.Append(dofs); } } diff --git a/miniapps/shifted/marking.hpp b/miniapps/shifted/marking.hpp index 46853780b5..e1cd24f7ee 100644 --- a/miniapps/shifted/marking.hpp +++ b/miniapps/shifted/marking.hpp @@ -22,30 +22,37 @@ namespace mfem class ShiftedFaceMarker { protected: - ParMesh &pmesh; - ParGridFunction &ls_func; - ParFiniteElementSpace &pfes_sltn; - bool include_cut_cell; + ParMesh &pmesh; // Mesh whose elements have to be marked. + ParFiniteElementSpace *pfes_sltn; // FESpace associated with the solution. + + // Indicates whether cut-cells will be included in assembly. + const bool include_cut_cell; + // Indicates whether all the elements have been marked at-least once. + bool initial_marking_done; // Marking of face dofs by using an averaged continuous GridFunction. - const bool func_dof_marking = false; - + const bool func_dof_marking = true; // Alternative implementation of ListShiftedFaceDofs(). void ListShiftedFaceDofs2(const Array &elem_marker, Array &sface_dof_list) const; +private: + int level_set_index; + public: /// Element type related to shifted boundaries (not interfaces). - enum SBElementType {INSIDE, OUTSIDE, CUT}; + /// For more than 1 level-set, we set the marker to CUT+level_set_index + /// to discern between different level-sets. + enum SBElementType {INSIDE = 0, OUTSIDE = 1, CUT = 2}; - ShiftedFaceMarker(ParMesh &pm, ParGridFunction &ls, - ParFiniteElementSpace &space_sltn, + ShiftedFaceMarker(ParMesh &pm, ParFiniteElementSpace &pfes, bool include_cut_cell_) - : pmesh(pm), ls_func(ls), pfes_sltn(space_sltn), - include_cut_cell(include_cut_cell_) { } + : pmesh(pm), pfes_sltn(&pfes), + include_cut_cell(include_cut_cell_), initial_marking_done(false), + level_set_index(0) { } /// Mark all the elements in the mesh using the @a SBElementType - void MarkElements(Array &elem_marker) const; + void MarkElements(const ParGridFunction &ls_func, Array &elem_marker); /// List dofs associated with the surrogate boundary. /// If @a include_cut_cell = false, the surrogate boundary includes faces diff --git a/miniapps/shifted/sbm_aux.hpp b/miniapps/shifted/sbm_aux.hpp index 4b03986a48..acae9e0f0f 100644 --- a/miniapps/shifted/sbm_aux.hpp +++ b/miniapps/shifted/sbm_aux.hpp @@ -9,30 +9,59 @@ // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. -#include "../../mfem.hpp" -#include -#include +#include "mfem.hpp" using namespace std; using namespace mfem; +double point_inside_trigon(const Vector px, Vector p1, Vector p2, Vector p3) +{ + Vector v0 = p1; + Vector v1 = p2; v1 -=p1; + Vector v2 = p3; v2 -=p1; + double p, q; + p = ((px(0)*v2(1)-px(1)*v2(0))-(v0(0)*v2(1)-v0(1)*v2(0))) / + (v1(0)*v2(1)-v1(1)*v2(0)); + q = -((px(0)*v1(1)-px(1)*v1(0))-(v0(0)*v1(1)-v0(1)*v1(0))) / + (v1(0)*v2(1)-v1(1)*v2(0)); + + return (p > 0 && q > 0 && 1-p-q > 0) ? -1.0 : 1.0; +} + +// 1 is inside the doughnut, -1 is outside. +double doughnut_cheese(const Vector &coord) +{ + // map [0,1] to [-1,1]. + double x = 2*coord(0)-1.0, y = 2*coord(1)-1.0, z = 2*coord(2)-1.0; + + bool doughnut; + const double R = 0.8, r = 0.15; + const double t = R - std::sqrt(x*x + y*y); + doughnut = t*t + z*z - r*r <= 0; + + bool cheese; + x = 3.0*x, y = 3.0*y, z = 3.0*z; + cheese = (x*x + y*y - 4.0) * (x*x + y*y - 4.0) + + (z*z - 1.0) * (z*z - 1.0) + + (y*y + z*z - 4.0) * (y*y + z*z - 4.0) + + (x*x - 1.0) * (x*x - 1.0) + + (z*z + x*x - 4.0) * (z*z + x*x - 4.0) + + (y*y - 1.0) * (y*y - 1.0) - 15.0 <= 0.0; + + return (doughnut || cheese) ? 1.0 : -1.0; +} + /// Analytic distance to the 0 level set. Positive value if the point is inside /// the domain, and negative value if outside. double dist_value(const Vector &x, const int type) { - double ring_radius = 0.2; if (type == 1 || type == 2) // circle of radius 0.2 - centered at 0.5, 0.5 { - double dx = x(0) - 0.5, - dy = x(1) - 0.5, - rv = dx*dx + dy*dy; - if (x.Size() == 3) - { - double dz = x(2) - 0.5; - rv += dz*dz; - } - rv = rv > 0 ? pow(rv, 0.5) : 0; - return rv - ring_radius; // positive is the domain + const double ring_radius = 0.2; + Vector xc(x.Size()); + xc = 0.5; + xc -= x; + return xc.Norml2() - ring_radius; // positive is the domain } else if (type == 3) // walls at y = 0.0 { @@ -65,6 +94,38 @@ double dist_value(const Vector &x, const int type) if (0.3 <= xc && xc <= 0.8 && 0.15 <= yc && yc <= 0.2) { return 1.0; } return -1.0; } + else if (type == 5) // square of side 0.2 centered at 0.75, 0.25 + { + double square_side = 0.2; + Vector xc(x.Size()); + xc = 0.75; xc(1) = 0.25; + xc -= x; + if (abs(xc(0)) > 0.5*square_side || abs(xc(1)) > 0.5*square_side) + { + return 1.0; + } + else + { + return -1.0; + } + return 0.0; + } + else if (type == 6) // Triangle + { + Vector p1(x.Size()), p2(x.Size()), p3(x.Size()); + p1(0) = 0.25; p1(1) = 0.4; + p2(0) = 0.1; p2(1) = 0.1; + p3(0) = 0.4; p3(1) = 0.1; + return point_inside_trigon(x, p1, p2, p3); + } + else if (type == 7) // circle of radius 0.2 - centered at 0.5, 0.6 + { + Vector xc(x.Size()); + xc = 0.5; xc(1) = 0.6; + xc -= x; + return xc.Norml2() - 0.2; + } + else if (type == 8) { return doughnut_cheese(x); } else { MFEM_ABORT(" Function type not implement yet."); @@ -72,7 +133,7 @@ double dist_value(const Vector &x, const int type) return 0.; } -/// Level set coefficient - +1 inside the domain, -1 outside, 0 at the boundary. +/// Level set coefficient: +1 inside the true domain, -1 outside. class Dist_Level_Set_Coefficient : public Coefficient { private: @@ -87,8 +148,34 @@ public: Vector x(3); T.Transform(ip, x); double dist = dist_value(x, type); - if (dist >= 0.) { return 1.; } - else { return -1.; } + return (dist >= 0.0) ? 1.0 : -1.0; + } +}; + +/// Combination of level sets: +1 inside the true domain, -1 outside. +class Combo_Level_Set_Coefficient : public Coefficient +{ +private: + Array dls; + +public: + Combo_Level_Set_Coefficient() : Coefficient() { } + + void Add_Level_Set_Coefficient(Dist_Level_Set_Coefficient &dls_) + { dls.Append(&dls_); } + + int GetNLevelSets() { return dls.Size(); } + + virtual double Eval(ElementTransformation &T, const IntegrationPoint &ip) + { + MFEM_VERIFY(dls.Size() > 0, + "Add at least 1 Dist_level_Set_Coefficient to the Combo."); + double dist = dls[0]->Eval(T, ip); + for (int j = 1; j < dls.Size(); j++) + { + dist = min(dist, dls[j]->Eval(T, ip)); + } + return (dist >= 0.0) ? 1.0 : -1.0; } }; @@ -127,10 +214,10 @@ public: } }; -/// Boundary conditions -double dirichlet_velocity_circle(const Vector &x) +/// Boundary conditions - Dirichlet +double homogeneous(const Vector &x) { - return 0.; + return 0.0; } double dirichlet_velocity_xy_exponent(const Vector &x) @@ -144,6 +231,38 @@ double dirichlet_velocity_xy_sinusoidal(const Vector &x) return 1./(M_PI*M_PI)*std::sin(M_PI*x(0)*x(1)); } +/// Boundary conditions - Neumann +/// Normal vector for level_set_type = 1. Circle centered at [0.5 , 0.5] +void normal_vector_1(const Vector &x, Vector &p) +{ + p.SetSize(x.Size()); + p(0) = x(0)-0.5; + p(1) = x(1)-0.5; // center of circle at [0.5, 0.5] + p /= p.Norml2(); + p *= -1; +} + +/// Normal vector for level_set_type = 7. Circle centered at [0.5 , 0.6] +void normal_vector_2(const Vector &x, Vector &p) +{ + p.SetSize(x.Size()); + p(0) = x(0)-0.5; + p(1) = x(1)-0.6; // center of circle at [0.5, 0.6] + p /= p.Norml2(); + p *= -1; +} + +/// Neumann condition for exponent based solution +double traction_xy_exponent(const Vector &x) +{ + double xy_p = 2; + Vector gradient(2); + gradient(0) = xy_p*x(0); + gradient(1) = xy_p*x(1); + Vector normal(2); + normal_vector_1(x, normal); + return 1.0*(gradient*normal); +} /// `f` for the Poisson problem (-nabla^2 u = f). double rhs_fun_circle(const Vector &x) diff --git a/miniapps/shifted/sbm_solver.cpp b/miniapps/shifted/sbm_solver.cpp index 75d23a846d..fce2ba2d11 100644 --- a/miniapps/shifted/sbm_solver.cpp +++ b/miniapps/shifted/sbm_solver.cpp @@ -9,9 +9,7 @@ // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. -#include "marking.hpp" #include "sbm_solver.hpp" -#include "mfem.hpp" namespace mfem { @@ -20,6 +18,8 @@ double ShiftedFunctionCoefficient::Eval(ElementTransformation & T, const IntegrationPoint & ip, const Vector &D) { + if (constantcoefficient) { return constant; } + Vector transip; T.Transform(ip, transip); for (int i = 0; i < D.Size(); i++) @@ -30,6 +30,21 @@ double ShiftedFunctionCoefficient::Eval(ElementTransformation & T, return Function(transip); } +void ShiftedVectorFunctionCoefficient::Eval(Vector &V, + ElementTransformation & T, + const IntegrationPoint & ip, + const Vector &D) +{ + Vector transip; + T.Transform(ip, transip); + for (int i = 0; i < D.Size(); i++) + { + transip(i) += D(i); + } + + Function(transip, V); +} + void SBM2DirichletIntegrator::AssembleFaceMatrix( const FiniteElement &el1, const FiniteElement &el2, FaceElementTransformations &Trans, DenseMatrix &elmat) @@ -72,13 +87,13 @@ void SBM2DirichletIntegrator::AssembleFaceMatrix( { // 1 is inside and 2 is cut or 1 is a boundary element. if (marker1 == ShiftedFaceMarker::SBElementType::INSIDE && - (marker2 == ShiftedFaceMarker::SBElementType::CUT || + (cut_marker.Find(marker2) != -1 || Trans.ElementType == ElementTransformation::BDR_FACE)) { elem1f = true; } // 1 is cut, 2 is inside - else if (marker1 == ShiftedFaceMarker::SBElementType::CUT && + else if (cut_marker.Find(marker1) != -1 && marker2 == ShiftedFaceMarker::SBElementType::INSIDE) { if (Trans.Elem2No >= NEproc) { return; } @@ -92,7 +107,7 @@ void SBM2DirichletIntegrator::AssembleFaceMatrix( else { // 1 is cut and 2 is outside or 1 is a boundary element. - if (marker1 == ShiftedFaceMarker::SBElementType::CUT && + if (cut_marker.Find(marker1) != -1 && (marker2 == ShiftedFaceMarker::SBElementType::OUTSIDE || Trans.ElementType == ElementTransformation::BDR_FACE)) { @@ -100,7 +115,7 @@ void SBM2DirichletIntegrator::AssembleFaceMatrix( } // 1 is outside, 2 is cut else if (marker1 == ShiftedFaceMarker::SBElementType::OUTSIDE && - marker2 == ShiftedFaceMarker::SBElementType::CUT) + cut_marker.Find(marker2) != -1) { if (Trans.Elem2No >= NEproc) { return; } elem1f = false; @@ -243,11 +258,8 @@ void SBM2DirichletIntegrator::AssembleFaceMatrix( } vD->Eval(D, Trans, ip); - double nor_dot_d = nor*D; - // If we are clipping inside the domain, ntilde and d vector should be - // aligned. - if (!include_cut_cell && nor_dot_d < 0) { nor *= -1; } - if (include_cut_cell && nor_dot_d > 0) { nor *= -1; } + // Make sure the normal vector is pointing outside the domain. + if (!elem1f) { nor *= -1; } if (elem1f) { @@ -381,14 +393,14 @@ void SBM2DirichletLFIntegrator::AssembleRHSElementVect( { // 1 is inside and 2 is cut or 1 is a boundary element. if ( marker1 == ShiftedFaceMarker::SBElementType::INSIDE && - (marker2 == ShiftedFaceMarker::SBElementType::CUT || + (marker2 == ls_cut_marker || Tr.ElementType == ElementTransformation::BDR_FACE)) { elem1f = true; ndof = ndof1; } // 1 is cut, 2 is inside - else if (marker1 == ShiftedFaceMarker::SBElementType::CUT && + else if (marker1 == ls_cut_marker && marker2 == ShiftedFaceMarker::SBElementType::INSIDE) { if (Tr.Elem2No >= NEproc) { return; } @@ -403,7 +415,7 @@ void SBM2DirichletLFIntegrator::AssembleRHSElementVect( else { // 1 is cut and 2 is outside or 1 is a boundary element. - if (marker1 == ShiftedFaceMarker::SBElementType::CUT && + if (marker1 == ls_cut_marker && (marker2 == ShiftedFaceMarker::SBElementType::OUTSIDE || Tr.ElementType == ElementTransformation::BDR_FACE)) { @@ -412,7 +424,7 @@ void SBM2DirichletLFIntegrator::AssembleRHSElementVect( } // 1 is outside, 2 is cut else if (marker1 == ShiftedFaceMarker::SBElementType::OUTSIDE && - marker2 == ShiftedFaceMarker::SBElementType::CUT) + marker2 == ls_cut_marker) { if (Tr.Elem2No >= NEproc) { return; } elem1f = false; @@ -553,11 +565,8 @@ void SBM2DirichletLFIntegrator::AssembleRHSElementVect( } vD->Eval(D, Tr, ip); - double nor_dot_d = nor*D; - if (!include_cut_cell && nor_dot_d < 0) { nor *= -1; } - if (include_cut_cell && nor_dot_d > 0) { nor *= -1; } - // note here that if we are clipping outside the domain, we will have to - // flip the sign if nor_dot_d is positive. + // Make sure the normal vector is pointing outside the domain. + if (!elem1f) { nor *= -1; } double hinvdx; @@ -641,4 +650,457 @@ void SBM2DirichletLFIntegrator::AssembleRHSElementVect( } } + +void SBM2NeumannIntegrator::AssembleFaceMatrix( + const FiniteElement &el1, const FiniteElement &el2, + FaceElementTransformations &Trans, DenseMatrix &elmat) +{ + int dim, ndof1, ndof2, ndof, ndoftotal; + double w; + DenseMatrix temp_elmat; + + dim = el1.GetDim(); + ndof1 = el1.GetDof(); + ndof2 = el2.GetDof(); + ndoftotal = Trans.ElementType == ElementTransformation::BDR_FACE ? + ndof1 : ndof1 + ndof2; + + elmat.SetSize(ndoftotal); + elmat = 0.0; + + bool elem1f = true; // flag indicating whether Trans.Elem1No is part of the + // surrogate domain or not. + int elem1 = Trans.Elem1No, + elem2 = Trans.Elem2No, + marker1 = (*elem_marker)[elem1]; + + int marker2; + + if (Trans.Elem2No >= NEproc) + { + marker2 = (*elem_marker)[NEproc+par_shared_face_count]; + par_shared_face_count++; + } + else if (Trans.ElementType == ElementTransformation::BDR_FACE) + { + marker2 = marker1; + } + else + { + marker2 = (*elem_marker)[elem2]; + } + + if (!include_cut_cell) + { + // 1 is inside and 2 is cut or 1 is a boundary element. + if (marker1 == ShiftedFaceMarker::SBElementType::INSIDE && + (cut_marker.Find(marker2) != -1 || + Trans.ElementType == ElementTransformation::BDR_FACE)) + { + elem1f = true; + } + // 1 is cut, 2 is inside + else if (cut_marker.Find(marker1) != -1 && + marker2 == ShiftedFaceMarker::SBElementType::INSIDE) + { + if (Trans.Elem2No >= NEproc) { return; } + elem1f = false; + } + else + { + return; + } + } + else + { + // 1 is cut and 2 is outside or 1 is a boundary element. + if (cut_marker.Find(marker1) != -1 && + (marker2 == ShiftedFaceMarker::SBElementType::OUTSIDE || + Trans.ElementType == ElementTransformation::BDR_FACE)) + { + elem1f = true; + } + // 1 is outside, 2 is cut + else if (marker1 == ShiftedFaceMarker::SBElementType::OUTSIDE && + cut_marker.Find(marker2) != -1) + { + if (Trans.Elem2No >= NEproc) { return; } + elem1f = false; + } + else + { + return; + } + } + + ndof = elem1f ? ndof1 : ndof2; + + temp_elmat.SetSize(ndof); + temp_elmat = 0.; + + nor.SetSize(dim); + nh.SetSize(dim); + ni.SetSize(dim); + adjJ.SetSize(dim); + + shape.SetSize(ndof); + dshape.SetSize(ndof, dim); + dshapedn.SetSize(ndof); + Vector wrk = shape; + + const IntegrationRule *ir = IntRule; + if (ir == NULL) + { + int order = elem1f ? 4*el1.GetOrder() : 4*el2.GetOrder(); + ir = &IntRules.Get(Trans.GetGeometryType(), order); + } + + Array dkphi_dxk; + DenseMatrix grad_phys; + Vector Factorial; + Array grad_phys_dir; + + if (nterms > 0) + { + if (elem1f) + { + el1.ProjectGrad(el1, *Trans.Elem1, grad_phys); + } + else + { + el2.ProjectGrad(el2, *Trans.Elem2, grad_phys); + } + + DenseMatrix grad_work; + grad_phys_dir.SetSize(dim); // NxN matrices for derivative in each direction + for (int i = 0; i < dim; i++) + { + grad_phys_dir[i] = new DenseMatrix(ndof, ndof); + grad_phys_dir[i]->CopyRows(grad_phys, i*ndof, (i+1)*ndof-1); + } + + DenseMatrix grad_phys_work = grad_phys; + grad_phys_work.SetSize(ndof, ndof*dim); + + dkphi_dxk.SetSize(nterms); + + for (int i = 0; i < nterms; i++) + { + int sz1 = pow(dim, i+1); + dkphi_dxk[i] = new DenseMatrix(ndof, ndof*sz1*dim); + int loc_col_per_dof = sz1; + int tot_col_per_dof = loc_col_per_dof*dim; + for (int k = 0; k < dim; k++) + { + grad_work.SetSize(ndof, ndof*sz1); + // grad_work[k] has derivative in kth direction for each DOF. + // grad_work[0] has d^2phi/dx^2 and d^2phi/dxdy terms and + // grad_work[1] has d^2phi/dydx and d^2phi/dy2 terms for each dof + if (i == 0) + { + Mult(*grad_phys_dir[k], grad_phys_work, grad_work); + } + else + { + Mult(*grad_phys_dir[k], *dkphi_dxk[i-1], grad_work); + } + // Now we must place columns for each dof together so that they are + // in order: d^2phi/dx^2, d^2phi/dxdy, d^2phi/dydx, d^2phi/dy2. + for (int j = 0; j < ndof; j++) + { + for (int d = 0; d < loc_col_per_dof; d++) + { + Vector col; + grad_work.GetColumn(j*loc_col_per_dof+d, col); + dkphi_dxk[i]->SetCol(j*tot_col_per_dof+k*loc_col_per_dof+d, col); + } + } + } + } + + for (int i = 0; i < grad_phys_dir.Size(); i++) + { + delete grad_phys_dir[i]; + } + + Factorial.SetSize(nterms); + Factorial(0) = 1; + for (int i = 1; i < nterms; i++) + { + Factorial(i) = Factorial(i-1)*(i+1); + } + } + + + DenseMatrix q_hess_dn(dim, ndof); + Vector q_hess_dn_work(q_hess_dn.GetData(), ndof*dim); + Vector q_hess_dot_d_nhat(ndof); + + Vector D(vD->GetVDim()); + Vector Nhat(vN->GetVDim()); + // Assemble: + for (int p = 0; p < ir->GetNPoints(); p++) + { + const IntegrationPoint &ip = ir->IntPoint(p); + + // Set the integration point in the face and the neighboring elements + Trans.SetAllIntPoints(&ip); + + // Access the neighboring elements' integration points + // Note: eip2 will only contain valid data if Elem2 exists + const IntegrationPoint &eip1 = Trans.GetElement1IntPoint(); + const IntegrationPoint &eip2 = Trans.GetElement2IntPoint(); + + if (dim == 1) + { + nor(0) = 2*eip1.x - 1.0; + } + else + { + // Note: this normal accounts for the weight of the surface transformation + // Jacobian i.e. nor = nhat*det(J) + CalcOrtho(Trans.Jacobian(), nor); + } + vD->Eval(D, Trans, ip); + vN->Eval(Nhat, Trans, ip, D); + + // Make sure the normal vector is pointing outside the domain. + if (!elem1f) { nor *= -1; } + + if (elem1f) + { + el1.CalcShape(eip1, shape); + el1.CalcDShape(eip1, dshape); + w = ip.weight/Trans.Elem1->Weight(); + CalcAdjugate(Trans.Elem1->Jacobian(), adjJ); + } + else + { + el1.CalcShape(eip2, shape); + el1.CalcDShape(eip2, dshape); + w = ip.weight/Trans.Elem2->Weight(); + CalcAdjugate(Trans.Elem2->Jacobian(), adjJ); + } + + ni.Set(w, nor); // nor/det(J) + adjJ.Mult(ni, nh); + dshape.Mult(nh, dshapedn); //dphi/dn * Jinv * nor + + // - - Term 2 + AddMult_a_VWt(-1., shape, dshapedn, temp_elmat); + + // MultTranspose(shape, T1_wrk); + + DenseMatrix T2; + Vector T2_wrk; + for (int j = 0; j < i+1; j++) + { + int sz2 = pow(dim, i-j); + T2.SetSize(dim, ndof*sz2); + T2_wrk.SetDataAndSize(T2.GetData(), dim*ndof*sz2); + T1.MultTranspose(D, T2_wrk); + T1 = T2; + } + Vector q_hess_dot_d_work(ndof); + T1.MultTranspose(Nhat, q_hess_dot_d_work); + q_hess_dot_d_work *= 1./Factorial(i); + q_hess_dot_d_nhat += q_hess_dot_d_work; + } + + wrk = q_hess_dot_d_nhat; + wrk *= ip.weight * n_dot_ntilde; + + AddMult_a_VWt(1., shape, wrk, temp_elmat); + int offset = elem1f ? 0 : ndof1; + elmat.CopyMN(temp_elmat, offset, offset); + } //p < ir->GetNPoints() + + for (int i = 0; i < dkphi_dxk.Size(); i++) + { + delete dkphi_dxk[i]; + } +} + +void SBM2NeumannLFIntegrator::AssembleRHSElementVect( + const FiniteElement &el, ElementTransformation &Tr, Vector &elvect) +{ + mfem_error("SBM2NeumannLFIntegrator::AssembleRHSElementVect"); +} + +void SBM2NeumannLFIntegrator::AssembleRHSElementVect( + const FiniteElement &el, FaceElementTransformations &Tr, Vector &elvect) +{ + AssembleRHSElementVect(el, el, Tr, elvect); +} + +void SBM2NeumannLFIntegrator::AssembleRHSElementVect( + const FiniteElement &el1, const FiniteElement &el2, + FaceElementTransformations &Tr, Vector &elvect) +{ + int dim, ndof1, ndof2, ndof, ndoftotal; + double w; + Vector temp_elvect; + + dim = el1.GetDim(); + ndof1 = el1.GetDof(); + ndof2 = el2.GetDof(); + ndoftotal = ndof1 + ndof2; + if (Tr.Elem2No >= NEproc || + Tr.ElementType == ElementTransformation::BDR_FACE) + { + ndoftotal = ndof1; + } + + elvect.SetSize(ndoftotal); + elvect = 0.0; + + bool elem1f = true; + int elem1 = Tr.Elem1No, + elem2 = Tr.Elem2No, + marker1 = (*elem_marker)[elem1]; + + int marker2; + if (Tr.Elem2No >= NEproc) + { + marker2 = (*elem_marker)[NEproc+par_shared_face_count]; + par_shared_face_count++; + } + else if (Tr.ElementType == ElementTransformation::BDR_FACE) + { + marker2 = marker1; + } + else + { + marker2 = (*elem_marker)[elem2]; + } + if (!include_cut_cell) + { + // 1 is inside and 2 is cut or 1 is a boundary element. + if ( marker1 == ShiftedFaceMarker::SBElementType::INSIDE && + (marker2 == ls_cut_marker || + Tr.ElementType == ElementTransformation::BDR_FACE)) + { + elem1f = true; + ndof = ndof1; + } + // 1 is cut, 2 is inside + else if (marker1 == ls_cut_marker && + marker2 == ShiftedFaceMarker::SBElementType::INSIDE) + { + if (Tr.Elem2No >= NEproc) { return; } + elem1f = false; + ndof = ndof2; + } + else + { + return; + } + } + else + { + // 1 is cut and 2 is outside or 1 is a boundary element. + if (marker1 == ls_cut_marker && + (marker2 == ShiftedFaceMarker::SBElementType::OUTSIDE || + Tr.ElementType == ElementTransformation::BDR_FACE)) + { + elem1f = true; + ndof = ndof1; + } + // 1 is outside, 2 is cut + else if (marker1 == ShiftedFaceMarker::SBElementType::OUTSIDE && + marker2 == ls_cut_marker) + { + if (Tr.Elem2No >= NEproc) { return; } + elem1f = false; + ndof = ndof2; + } + else + { + return; + } + } + + temp_elvect.SetSize(ndof); + temp_elvect = 0.0; + + nor.SetSize(dim); + shape.SetSize(ndof); + + const IntegrationRule *ir = IntRule; + if (ir == NULL) + { + // a simple choice for the integration order; is this OK? + int order = elem1f ? 4*el1.GetOrder() : 4*el2.GetOrder(); + ir = &IntRules.Get(Tr.GetGeometryType(), order); + } + + Vector D(vD->GetVDim()); + Vector Nhat(vN->GetVDim()); + Vector wrk = shape; + for (int p = 0; p < ir->GetNPoints(); p++) + { + const IntegrationPoint &ip = ir->IntPoint(p); + + // Set the integration point in the face and the neighboring element + Tr.SetAllIntPoints(&ip); + + // Access the neighboring element's integration point + const IntegrationPoint &eip = Tr.GetElement1IntPoint(); + const IntegrationPoint &eip1 = Tr.GetElement1IntPoint(); + const IntegrationPoint &eip2 = Tr.GetElement2IntPoint(); + + if (dim == 1) + { + nor(0) = 2*eip.x - 1.0; + } + else + { + CalcOrtho(Tr.Jacobian(), nor); + } + vD->Eval(D, Tr, ip); + vN->Eval(Nhat, Tr, ip, D); + + // Make sure the normal vector is pointing outside the domain. + if (!elem1f) { nor *= -1; } + + if (elem1f) + { + el1.CalcShape(eip1, shape); + w = ip.weight * uN->Eval(Tr, ip, D); + } + else + { + el2.CalcShape(eip2, shape); + w = ip.weight * uN->Eval(Tr, ip, D); + } + + double n_dot_ntilde = nor*Nhat; + wrk.Set(n_dot_ntilde*w, shape); + // Function; + double constant = 0.0; + bool constantcoefficient; public: ShiftedFunctionCoefficient(std::function F) - : Function(std::move(F)) { } + : Function(std::move(F)), constantcoefficient(false) { } + ShiftedFunctionCoefficient(double constant_) + : constant(constant_), constantcoefficient(true) { } - virtual double Eval(ElementTransformation &T, - const IntegrationPoint &ip) + virtual double Eval(ElementTransformation &T, const IntegrationPoint &ip) { - Vector D(1); + if (constantcoefficient) { return constant; } + + Vector D(T.GetSpaceDim()); D = 0.; return (this)->Eval(T, ip, D); } @@ -42,6 +48,32 @@ public: const Vector &D); }; +class ShiftedVectorFunctionCoefficient : public VectorCoefficient +{ +protected: + std::function Function; + +public: + ShiftedVectorFunctionCoefficient(int dim, + std::function F) + : VectorCoefficient(dim), Function(std::move(F)) { } + + using VectorCoefficient::Eval; + virtual void Eval(Vector &V, ElementTransformation &T, + const IntegrationPoint &ip) + { + Vector D(vdim); + D = 0.; + return (this)->Eval(V, T, ip, D); + } + + /// Evaluate the coefficient at @a ip + @a D. + void Eval(Vector &V, + ElementTransformation &T, + const IntegrationPoint &ip, + const Vector &D); +}; + /// BilinearFormIntegrator for the high-order extension of shifted boundary /// method. /// A(u, w) = - @@ -63,12 +95,14 @@ protected: bool include_cut_cell; // include element cut by true boundary int nterms; // Number of terms in addition to the gradient // term from Taylor expansion that should be included. (0 by default). - int NEproc; //Number of elements on the current MPI rank + int NEproc; // Number of elements on the current MPI rank int par_shared_face_count; // + Array cut_marker; // Array with marker values for cut-cell + // corresponding to the level set that BilinearForm applies to. // these are not thread-safe! - Vector shape, dshapedn, dshapephysdn, nor, nh, ni; - DenseMatrix jmat, dshape, dshapephys, adjJ; + Vector shape, dshapedn, nor, nh, ni; + DenseMatrix dshape, dshapephys, adjJ; public: @@ -76,6 +110,7 @@ public: const double a, VectorCoefficient &vD_, Array &elem_marker_, + Array &cut_marker_, bool include_cut_cell_ = false, int nterms_ = 0) : alpha(a), vD(&vD_), @@ -83,7 +118,8 @@ public: include_cut_cell(include_cut_cell_), nterms(nterms_), NEproc(pmesh->GetNE()), - par_shared_face_count(0) { } + par_shared_face_count(0), + cut_marker(cut_marker_) { } using BilinearFormIntegrator::AssembleFaceMatrix; virtual void AssembleFaceMatrix(const FiniteElement &el1, @@ -115,32 +151,36 @@ protected: ShiftedFunctionCoefficient *uD; double alpha; // Nitsche parameter VectorCoefficient *vD; // Distance function coefficient - Array *elem_marker; //marker indicating whether element is inside, + Array *elem_marker; // marker indicating whether element is inside, //cut, or outside the domain. bool include_cut_cell; // include element cut by true boundary int nterms; // Number of terms in addition to the gradient // term from Taylor expansion that should be included. (0 by default). - int NEproc; //Number of elements on the current MPI rank + int NEproc; // Number of elements on the current MPI rank int par_shared_face_count; // + int ls_cut_marker; // Flag used for the cut-cell corresponding to the + // level set. // these are not thread-safe! Vector shape, dshape_dd, dshape_dn, nor, nh, ni; - DenseMatrix dshape, mq, adjJ; + DenseMatrix dshape, adjJ; public: SBM2DirichletLFIntegrator(const ParMesh *pmesh, ShiftedFunctionCoefficient &u, - const double a, + const double alpha_, VectorCoefficient &vD_, Array &elem_marker_, bool include_cut_cell_ = false, - int nterms_ = 0) - : uD(&u), alpha(a), vD(&vD_), + int nterms_ = 0, + int ls_cut_marker_ = ShiftedFaceMarker::SBElementType::CUT) + : uD(&u), alpha(alpha_), vD(&vD_), elem_marker(&elem_marker_), include_cut_cell(include_cut_cell_), nterms(nterms_), NEproc(pmesh->GetNE()), - par_shared_face_count(0) { } + par_shared_face_count(0), + ls_cut_marker(ls_cut_marker_) { } virtual void AssembleRHSElementVect(const FiniteElement &el, ElementTransformation &Tr, @@ -154,6 +194,126 @@ public: Vector &elvect); }; + +/// BilinearFormIntegrator for Neumann boundaries using the shifted boundary +/// method. +/// A(u,w) = <[nabla u + nabla(nabla u).d + h.o.t.].nhat(n.nhat),w>- +/// where h.o.t are the high-order terms due to Taylor expansion for nabla u, +/// nhat is the normal vector at the true boundary, n is the normal vector at +/// the surrogate boundary. Since this interior face integrator is applied to +/// the surrogate boundary (see marking.hpp for notes on how the surrogate faces +/// are determined and elements are marked), this integrator adds contribution +/// to only the element that is adjacent to that face (Trans.Elem1 or +/// Trans.Elem2) and is part of the surrogate domain. +class SBM2NeumannIntegrator : public BilinearFormIntegrator +{ +protected: + ShiftedVectorFunctionCoefficient *vN; // Normal function coefficient + VectorCoefficient *vD; // Distance function coefficient + Array *elem_marker; // Marker indicating whether element is inside, + //cut, or outside the domain. + bool include_cut_cell; + int nterms; // Number of terms in addition to the gradient + // term from Taylor expansion that should be included. (0 by default). + int NEproc; // Number of elements on the current MPI rank + int par_shared_face_count; // + Array cut_marker; + + + // these are not thread-safe! + Vector shape, dshapedn, nor, nh, ni; + DenseMatrix dshape, adjJ; + + +public: + SBM2NeumannIntegrator(const ParMesh *pmesh, + VectorCoefficient &vD_, + ShiftedVectorFunctionCoefficient &vN_, + Array &elem_marker_, + Array &cut_marker_, + bool include_cut_cell_ = false, + int nterms_ = 1) + : vN(&vN_), vD(&vD_), + elem_marker(&elem_marker_), + include_cut_cell(include_cut_cell_), + nterms(nterms_), + NEproc(pmesh->GetNE()), + par_shared_face_count(0), + cut_marker(cut_marker_) { } + + using BilinearFormIntegrator::AssembleFaceMatrix; + virtual void AssembleFaceMatrix(const FiniteElement &el1, + const FiniteElement &el2, + FaceElementTransformations &Trans, + DenseMatrix &elmat); + + bool GetTrimFlag() const { return include_cut_cell; } + + virtual ~SBM2NeumannIntegrator() { } +}; + +/// LinearFormIntegrator for Neumann boundaries using the shifted boundary +/// method. +/// (u, w) = +/// where nhat is the normal vector at the true boundary, n is the normal vector +/// at the surrogate boundary, and t_n is the traction boundary condition. +/// Since this interior face integrator is applied to the surrogate boundary +/// (see marking.hpp for notes on how the surrogate faces are determined and +/// elements are marked), this integrator adds contribution to only the element +/// that is adjacent to that face (Trans.Elem1 or Trans.Elem2) and is part of +/// the surrogate domain. +/// Note that t_N is evaluated at the true boundary using the distance function +/// and ShiftedFunctionCoefficient, i.e. t_N(x_true) = t_N(x_surrogate + D), +/// where x_surrogate is the location of the integration point on the surrogate +/// boundary and D is the distance vector from the surrogate boundary to the +/// true boundary. +class SBM2NeumannLFIntegrator : public LinearFormIntegrator +{ +protected: + ShiftedVectorFunctionCoefficient *vN; // Normal function coefficient + ShiftedFunctionCoefficient *uN; // Neumann condition on true boundary + VectorCoefficient *vD; // Distance function coefficient + Array *elem_marker; // Marker indicating whether element is inside, + int nterms; // Number of terms in addition to the gradient + // term from Taylor expansion that should be included. (0 by default). + bool include_cut_cell; + int NEproc; // Number of elements on the current MPI rank + int par_shared_face_count; + int ls_cut_marker; + + // these are not thread-safe! + Vector shape, nor; + +public: + SBM2NeumannLFIntegrator(const ParMesh *pmesh, + ShiftedFunctionCoefficient &u, + VectorCoefficient &vD_, + ShiftedVectorFunctionCoefficient &vN_, + Array &elem_marker_, + int nterms_ = 0, + bool include_cut_cell_ = false, + int ls_cut_marker_ = ShiftedFaceMarker::SBElementType::CUT) + : vN(&vN_), uN(&u), vD(&vD_), + elem_marker(&elem_marker_), + nterms(nterms_), + include_cut_cell(include_cut_cell_), + NEproc(pmesh->GetNE()), + par_shared_face_count(0), + ls_cut_marker(ls_cut_marker_) { } + + virtual void AssembleRHSElementVect(const FiniteElement &el, + ElementTransformation &Tr, + Vector &elvect); + virtual void AssembleRHSElementVect(const FiniteElement &el, + FaceElementTransformations &Tr, + Vector &elvect); + virtual void AssembleRHSElementVect(const FiniteElement &el1, + const FiniteElement &el2, + FaceElementTransformations &Tr, + Vector &elvect); + bool GetTrimFlag() const { return include_cut_cell; } +}; + } // namespace mfem #endif diff --git a/miniapps/solvers/plor_solvers.cpp b/miniapps/solvers/plor_solvers.cpp index cc31f36dad..0df6715d8e 100644 --- a/miniapps/solvers/plor_solvers.cpp +++ b/miniapps/solvers/plor_solvers.cpp @@ -76,6 +76,7 @@ int main(int argc, char *argv[]) int ser_ref_levels = 1, par_ref_levels = 1; int order = 3; const char *fe = "h"; + const char *device_config = "cpu"; bool visualization = true; OptionsParser args(argc, argv); @@ -90,8 +91,13 @@ int main(int argc, char *argv[]) args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable GLVis visualization."); + args.AddOption(&device_config, "-d", "--device", + "Device configuration string, see Device::Configure()."); args.ParseCheck(); + Device device(device_config); + device.Print(); + bool H1 = false, ND = false, RT = false, L2 = false; if (string(fe) == "h") { H1 = true; } else if (string(fe) == "n") { ND = true; } diff --git a/tests/gitlab/README.md b/tests/gitlab/README.md index 8bd267c7b3..bccdcc3c90 100644 --- a/tests/gitlab/README.md +++ b/tests/gitlab/README.md @@ -13,10 +13,7 @@ This directory contains utility scripts related to GitLab testing at LLNL. perform testing. While designed to be used in CI context, this script can also be used - standalone on LLNL's LC in order to reproduce a similar build. The script - uses environment variables for configuration (a place for improvement), such - as "BUILD_ROOT", "HOST_CONFIG", "SPEC", etc. Some are mandatory, while - others have default values. + standalone on LLNL's LC in order to reproduce a similar build. Please refer to tests/gitlab/reproduce-ci-jobs-interactively.md for details. * `get_mfem_uberenv` sets uberenv up for use with MFEM, notably to install TPLs diff --git a/tests/gitlab/build_and_test b/tests/gitlab/build_and_test index 95c820b201..09e27cb53e 100755 --- a/tests/gitlab/build_and_test +++ b/tests/gitlab/build_and_test @@ -22,13 +22,13 @@ function usage() echo "" echo "Syntax:" echo "> ${script_name} --spec \"spack spec\" [--deps-only] [--data]" - echo " [--build-root /path/to/build/resource]" + echo " [--data-dir=/path/to/mfem/data]" echo "" echo "> ${script_name} --build-only [--data]" - echo " [--build-root /path/to/build/resource]" + echo " [--data-dir=/path/to/mfem/data]" echo "" echo "> ${script_name} --test-only [--data]" - echo " [--build-root /path/to/build/resource]" + echo " [--data-dir=/path/to/mfem/data]" echo "" echo "Options:" echo " --spec" @@ -53,24 +53,20 @@ function usage() echo " data directory is not present in the parent of the mfem root directory." echo " Note: default behavior is to run data tests if data dir is present." echo "" - echo " --build-root=/path/to/build/resource" - echo " The script will use this directory to find the external resource" - echo " needed, e.g. the data directory. Defaults to the parent location" - echo " of the MFEM clone." + echo " --data-dir=/path/to/mfem/data" + echo " Path to a clone of the MFEM/data repo: https://github.com/mfem/data" + echo " The default path is: '../data'." echo "" } -hostname="$(hostname)" project_dir="$(pwd)" mode="" -build_root="" spec="" +data_dir="" with_data=false sys_type=${SYS_TYPE:-""} -py_env_path=${PYTHON_ENVIRONMENT_PATH:-""} -ci_context=${CI:-""} threads=${THREADS:-""} @@ -93,8 +89,8 @@ do with_data=true shift # past argument ;; - --build-root) - build_root="$2" + --data-dir) + data_dir="$2" shift # past argument shift # past value ;; @@ -134,14 +130,16 @@ then # otherwise. if [[ -d "/dev/shm" && "${mode}" != "--deps-only" ]] then - prefix="/dev/shm/${hostname}/${CI_PIPELINE_ID:-"NONE"}_${RANDOM}" + prefix="/dev/shm/${USER}_${CI_PIPELINE_ID:-"NONE"}_${RANDOM}" mkdir -p ${prefix} - echo ${spec} > spec.txt + # clean up ${prefix} at exit: + trap 'rm -rf "${prefix}"' EXIT prefix_opt="--prefix=${prefix}" fi + echo ${spec} > spec.txt echo "Fetching uberenv." - tests/gitlab/get_mfem_uberenv || ( echo "Error fetching Uberenv" && exit 1 ); + tests/gitlab/get_mfem_uberenv || { echo "Error fetching Uberenv"; exit 1; } echo "Removing existing configuration" make distclean @@ -172,20 +170,35 @@ then exit 1 fi - # Build and Data Directories - if [[ -z ${build_root} ]] + # Setup the MFEM/data repository directory + # Some additional unit tests are enabled when '../data' is present + if [[ -z "${data_dir}" ]] then - # By default, build_root is the project parent dir. - build_root="${project_dir}/.." + # By default, data_dir is ../data. + data_dir="../data" else - # build_root is specified, so we need to link its content into the + # data_dir is specified, so we need to link its content into the # project parent dir. - ln -sf ${build_root}/data ${project_dir}/../ + if [[ -e "../data" ]]; then + if [[ -L "../data" ]]; then + echo "'../data' link already exists. Deleting." + rm "../data" + else + echo "Error: '../data' already exists and it's NOT a link" + exit 1 + fi + fi + ln -sf "${data_dir}" "../data" + fi + # The PUMI examples expect the PUMI datafiles to be in 'data/pumi' + if [[ -d "../data/pumi" ]]; then + ln -sf "../../data/pumi" "data" fi - if [[ "$with_data" == "true" && ! -d ${build_root}/data ]] + if [[ "$with_data" == "true" && ! -d "../data" ]] then - echo "ERROR: ${build_root}/data not found while asking for --data". + echo "ERROR: '$data_dir' is not a directory while asking for --data" + exit 1 fi fi @@ -194,9 +207,15 @@ if [[ "${mode}" != "--deps-only" ]] then echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "~ Project Dir: ${project_dir}" - echo "~ Build Root: ${build_root}" + echo "~ Data Dir: ${data_dir}" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo "~~~~~ MFEM configuration" + echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + + make info + echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "~~~~~ Building MFEM" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" diff --git a/tests/gitlab/reproduce-ci-jobs-interactively.md b/tests/gitlab/reproduce-ci-jobs-interactively.md index 9c6d59f5ea..e06c9110e6 100644 --- a/tests/gitlab/reproduce-ci-jobs-interactively.md +++ b/tests/gitlab/reproduce-ci-jobs-interactively.md @@ -66,12 +66,12 @@ those files were generated, otherwise `make all` would just regenerate them. **NOTE** -The `build_and_test` script behaves slightly differently between CI context and -elsewhere (depending on environment variable $CI). In CI, and if launched on -quartz, ruby or corona, the script will build and install dependencies in +When `build_and_test` needs to build the dependencies, i.e. (a) `--deps-only` is +used, or (b) none of the `--XXX-only` options is used, then the script behaves +slightly differently. In case (b), it will build and install dependencies in `/dev/shm` for better performance. However, this is only valid if we don’t want the installation to persist. Installation will happen locally to the uberenv -directory if not in CI context. +directory in case (a), i.e. if `--deps-only` is used. ### Option #2: Calling uberenv directly diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 7fb3e36e1c..fd4e7f5ec1 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -61,6 +61,7 @@ set(UNIT_TESTS_SRCS fem/test_face_permutation.cpp fem/test_fe.cpp fem/test_get_value.cpp + fem/test_getderivative.cpp fem/test_intrules.cpp fem/test_intruletypes.cpp fem/test_inversetransform.cpp diff --git a/tests/unit/ceed/test_ceed.cpp b/tests/unit/ceed/test_ceed.cpp index d2d5cc3ca8..168952bad8 100644 --- a/tests/unit/ceed/test_ceed.cpp +++ b/tests/unit/ceed/test_ceed.cpp @@ -41,6 +41,47 @@ void velocity_function(const Vector &x, Vector &v) } } +// Vector valued quantity to convect +void quantity(const Vector &x, Vector &u) +{ + int dim = x.Size(); + + switch (dim) + { + case 1: u(0) = x[0]*x[0]; break; + case 2: u(0) = x[0]*x[0]; u(1) = x[1]*x[1]; break; + case 3: u(0) = x[0]*x[0]; u(1) = x[1]*x[1]; u(2) = x[2]*x[2]; break; + } +} + +// Quantity after explicit convect +// (u \cdot \nabla) v +void convected_quantity(const Vector &x, Vector &u) +{ + double a, b, c; + + int dim = x.Size(); + switch (dim) + { + case 1: + u(0) = 2.*x[0]*(x[0]*x[0]+1.0); + break; + case 2: + a = sqrt(2./3.); + b = sqrt(1./3.); + u(0) = 2.*a*x[0]*(x[0]*x[0]+1.0); + u(1) = 2.*b*x[1]*(x[0]*x[0]+1.0); + break; + case 3: + a = sqrt(3./6.); + b = sqrt(2./6.); + c = sqrt(1./6.); + u(0) = 2.*a*x[0]*(x[0]*x[0]+1.0); + u(1) = 2.*b*x[1]*(x[0]*x[0]+1.0); + u(2) = 2.*c*x[2]*(x[0]*x[0]+1.0); + } +} + std::string getString(AssemblyLevel assembly) { switch (assembly) @@ -270,7 +311,7 @@ void test_ceed_operator(const char* input, int order, delete vcoeff; } -void test_ceed_nloperator(const char* input, int order, +void test_ceed_nloperator(const char* mesh_filename, int order, const CeedCoeffType coeff_type, const NLProblem pb, const AssemblyLevel assembly) { @@ -278,9 +319,9 @@ void test_ceed_nloperator(const char* input, int order, "coeff_type: " + getString(coeff_type) + "\n" + "pb: " + getString(pb) + "\n" + "order: " + std::to_string(order) + "\n" + - "mesh: " + input; + "mesh: " + mesh_filename; INFO(section); - Mesh mesh(input, 1, 1); + Mesh mesh(mesh_filename, 1, 1); mesh.EnsureNodes(); int dim = mesh.Dimension(); H1_FECollection fec(order, dim); @@ -328,6 +369,56 @@ void test_ceed_nloperator(const char* input, int order, delete vcoeff; } +// This function specifically tests convection of a vector valued quantity and +// using a custom integration rule. The integration rule is chosen s.t. in +// combination with an appropriate order, it can represent the analytical +// polynomial functions correctly. +void test_ceed_convection(const char* mesh_filename, int order, + const AssemblyLevel assembly) +{ + Mesh mesh(mesh_filename, 1, 1); + mesh.EnsureNodes(); + int dim = mesh.Dimension(); + H1_FECollection fec(order, dim); + + VectorFunctionCoefficient velocity_coeff(dim, velocity_function); + + FiniteElementSpace fes(&mesh, &fec, dim); + BilinearForm conv_op(&fes); + + IntegrationRules rules(0, Quadrature1D::GaussLobatto); + const IntegrationRule &ir = rules.Get(fes.GetFE(0)->GetGeomType(), + 2 * order - 1); + + ConvectionIntegrator *conv_integ = new ConvectionIntegrator(velocity_coeff, 1); + conv_integ->SetIntRule(&ir); + conv_op.AddDomainIntegrator(conv_integ); + conv_op.SetAssemblyLevel(assembly); + conv_op.Assemble(); + + GridFunction q(&fes), r(&fes), ex(&fes); + + VectorFunctionCoefficient quantity_coeff(dim, quantity); + q.ProjectCoefficient(quantity_coeff); + + VectorFunctionCoefficient convected_quantity_coeff(dim, convected_quantity); + ex.ProjectCoefficient(convected_quantity_coeff); + + r = 0.0; + conv_op.Mult(q, r); + + LinearForm f(&fes); + VectorDomainLFIntegrator *vlf_integ = new VectorDomainLFIntegrator( + convected_quantity_coeff); + vlf_integ->SetIntRule(&ir); + f.AddDomainIntegrator(vlf_integ); + f.Assemble(); + + r -= f; + + REQUIRE(r.Norml2() < 1e-12); +} + TEST_CASE("CEED mass & diffusion", "[CEED]") { auto assembly = GENERATE(AssemblyLevel::PARTIAL,AssemblyLevel::NONE); @@ -348,15 +439,22 @@ TEST_CASE("CEED convection", "[CEED],[Convection]") auto assembly = GENERATE(AssemblyLevel::PARTIAL,AssemblyLevel::NONE); auto coeff_type = GENERATE(CeedCoeffType::VecConst,CeedCoeffType::VecGrid, CeedCoeffType::VecQuad); - auto pb = GENERATE(Problem::Convection); - auto order = GENERATE(1); auto mesh = GENERATE("../../data/inline-quad.mesh", "../../data/inline-hex.mesh", "../../data/star-q2.mesh", "../../data/fichera-q2.mesh", "../../data/amr-quad.mesh", "../../data/fichera-amr.mesh"); - test_ceed_operator(mesh, order, coeff_type, pb, assembly); + Problem pb = Problem::Convection; + + // Test that the CEED and MFEM integrators give the same answer + int low_order = 1; + test_ceed_operator(mesh, low_order, coeff_type, pb, assembly); + + // Apply the CEED convection integrator applied to a vector quantity, check + // that we get the exact answer (with sufficiently high polynomial degree) + int high_order = 4; + test_ceed_convection(mesh, high_order, assembly); } // test case TEST_CASE("CEED non-linear convection", "[CEED],[NLConvection]") diff --git a/tests/unit/fem/test_derefine.cpp b/tests/unit/fem/test_derefine.cpp index 9ceb3a6846..92aa47e004 100644 --- a/tests/unit/fem/test_derefine.cpp +++ b/tests/unit/fem/test_derefine.cpp @@ -91,12 +91,8 @@ TEST_CASE("Derefine") // Derefine by setting 0 error on the fine elements in coarse element 2. Table coarse_to_fine_; - Table ref_type_to_matrix; - Array coarse_to_ref_type; - Array ref_type_to_geom; const CoarseFineTransformations &rtrans = mesh.GetRefinementTransforms(); - rtrans.GetCoarseToFineMap(mesh, coarse_to_fine_, coarse_to_ref_type, - ref_type_to_matrix, ref_type_to_geom); + rtrans.MakeCoarseToFineTable(coarse_to_fine_); Array tabrow; Vector local_err(mesh.GetNE()); diff --git a/tests/unit/fem/test_get_value.cpp b/tests/unit/fem/test_get_value.cpp index 7b3da161bf..19a78ac11c 100644 --- a/tests/unit/fem/test_get_value.cpp +++ b/tests/unit/fem/test_get_value.cpp @@ -2495,6 +2495,12 @@ TEST_CASE("3D GetVectorValue", Vector dgv_gvv_val(dim); dgv_gvv_val = 0.0; Vector dgi_gvv_val(dim); dgi_gvv_val = 0.0; + Vector nd_gvf_val(dim); nd_gvf_val = 0.0; + Vector rt_gvf_val(dim); rt_gvf_val = 0.0; + DenseMatrix nd_gvf_vals; + DenseMatrix rt_gvf_vals; + DenseMatrix tr; + SECTION("Domain Evaluation 3D") { std::cout << "Domain Evaluation 3D" << std::endl; @@ -2519,6 +2525,12 @@ TEST_CASE("3D GetVectorValue", double dgv_gvv_err = 0.0; double dgi_gvv_err = 0.0; + double nd_gvf_err = 0.0; + double rt_gvf_err = 0.0; + + nd_x.GetVectorFieldValues(e, ir, nd_gvf_vals, tr); + rt_x.GetVectorFieldValues(e, ir, rt_gvf_vals, tr); + for (int j=0; j 0 && h1_gfc_dist > tol) { std::cout << e << ":" << j << " h1 gfc (" @@ -2681,6 +2702,26 @@ TEST_CASE("3D GetVectorValue", << dgi_gvv_val[2] << ") " << dgi_gvv_dist << std::endl; } + if (log > 0 && nd_gvf_dist > tol) + { + std::cout << e << ":" << j << " nd gvf (" + << f_val[0] << "," << f_val[1] << "," + << f_val[2] << ") vs. (" + << nd_gvf_val[0] << "," + << nd_gvf_val[1] << "," + << nd_gvf_val[2] << ") " + << nd_gvf_dist << std::endl; + } + if (log > 0 && rt_gvf_dist > tol) + { + std::cout << e << ":" << j << " rt gvf (" + << f_val[0] << "," << f_val[1] << "," + << f_val[2] << ") vs. (" + << rt_gvf_val[0] << "," + << rt_gvf_val[1] << "," + << rt_gvf_val[2] << ") " + << rt_gvf_dist << std::endl; + } } h1_gfc_err /= ir.GetNPoints(); @@ -2697,6 +2738,9 @@ TEST_CASE("3D GetVectorValue", dgv_gvv_err /= ir.GetNPoints(); dgi_gvv_err /= ir.GetNPoints(); + nd_gvf_err /= ir.GetNPoints(); + rt_gvf_err /= ir.GetNPoints(); + REQUIRE( h1_gfc_err == MFEM_Approx(0.0)); REQUIRE( nd_gfc_err == MFEM_Approx(0.0)); REQUIRE( rt_gfc_err == MFEM_Approx(0.0)); @@ -2710,6 +2754,9 @@ TEST_CASE("3D GetVectorValue", REQUIRE( l2_gvv_err == MFEM_Approx(0.0)); REQUIRE(dgv_gvv_err == MFEM_Approx(0.0)); REQUIRE(dgi_gvv_err == MFEM_Approx(0.0)); + + REQUIRE( nd_gvf_err == MFEM_Approx(0.0)); + REQUIRE( rt_gvf_err == MFEM_Approx(0.0)); } } diff --git a/tests/unit/fem/test_getderivative.cpp b/tests/unit/fem/test_getderivative.cpp new file mode 100644 index 0000000000..90685b498d --- /dev/null +++ b/tests/unit/fem/test_getderivative.cpp @@ -0,0 +1,76 @@ +// Copyright (c) 2010-2021, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-806117. +// +// This file is part of the MFEM library. For more information and source code +// availability visit https://mfem.org. +// +// MFEM is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +#include "unit_tests.hpp" +#include "mfem.hpp" + +using namespace mfem; + +double func(const Vector &coord) +{ + if (coord.Size() == 1) { return std::sin(coord(0)); } + if (coord.Size() == 2) { return std::sin(coord(0)*coord(1)); } + return std::sin(coord(0)*coord(1)*coord(2)); +} + +#ifdef MFEM_USE_MPI + +// Compares serial vs parallel result of GetDerivative. +TEST_CASE("GetDerivative", "[Parallel]") +{ + for (int dimension = 1; dimension <= 3; ++dimension) + { + int num_procs; + MPI_Comm_size(MPI_COMM_WORLD, &num_procs); + int myid; + MPI_Comm_rank(MPI_COMM_WORLD, &myid); + + Mesh mesh; + if (dimension == 1) + { + mesh = Mesh::MakeCartesian1D(100, 1.0); + } + else if (dimension == 2) + { + mesh = Mesh::LoadFromFile("../../data/star-mixed-p2.mesh"); + } + else + { + mesh = Mesh::LoadFromFile("../../data/fichera-mixed-p2.mesh"); + } + for (int i = 0; i < 2; i++) { mesh.UniformRefinement(); } + ParMesh pmesh(MPI_COMM_WORLD, mesh); + + FunctionCoefficient x_coeff(func); + H1_FECollection fec(3, dimension); + + // Serial. + FiniteElementSpace fes(&mesh, &fec); + GridFunction gf(&fes), gf_grad(&fes); + gf.ProjectCoefficient(x_coeff); + + // Parallel. + ParFiniteElementSpace pfes(&pmesh, &fec); + ParGridFunction pgf(&pfes), pgf_grad(&pfes); + pgf.ProjectCoefficient(x_coeff); + + ConstantCoefficient zero(0.0); + for (int d = 0; d < dimension; d++) + { + gf.GetDerivative(1, d, gf_grad); + pgf.GetDerivative(1, d, pgf_grad); + REQUIRE(gf_grad.ComputeL2Error(zero) - + pgf_grad.ComputeL2Error(zero) == MFEM_Approx(0.0)); + } + } +} + +#endif diff --git a/tests/unit/fem/test_oscillation.cpp b/tests/unit/fem/test_oscillation.cpp new file mode 100644 index 0000000000..2c52f94b2e --- /dev/null +++ b/tests/unit/fem/test_oscillation.cpp @@ -0,0 +1,326 @@ +// Copyright (c) 2010-2021, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-806117. +// +// This file is part of the MFEM library. For more information and source code +// availability visit https://mfem.org. +// +// MFEM is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +#include "mfem.hpp" +#include "unit_tests.hpp" + +#include +#include + +using namespace mfem; + +#if defined(MFEM_USE_MPI) + +namespace testhelper_osc +{ +double SmoothSolutionX(const mfem::Vector& x) +{ + return x(0); +} + +double SmoothSolutionY(const mfem::Vector& x) +{ + return x(1); +} + +double SmoothSolutionZ(const mfem::Vector& x) +{ + return x(2); +} + +double NonsmoothSolutionX(const mfem::Vector& x) +{ + return std::abs(x(0)-0.5); +} + +double NonsmoothSolutionY(const mfem::Vector& x) +{ + return std::abs(x(1)-0.5); +} + +double NonsmoothSolutionZ(const mfem::Vector& x) +{ + return std::abs(x(2)-0.5); +} +} + +TEST_CASE("Data Oscillation on 2D NCMesh", + "[NCMesh], [Parallel]") +{ + // Setup + const auto order = GENERATE(1, 3, 5); + Mesh mesh = Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL); + + // Make the mesh NC + mesh.EnsureNCMesh(); + { + Array elements_to_refine(1); + elements_to_refine[0] = 1; + mesh.GeneralRefinement(elements_to_refine, 1, 0); + } + + auto pmesh = new ParMesh(MPI_COMM_WORLD, mesh); + mesh.Clear(); + + SECTION("Perfect Approximation X") + { + FunctionCoefficient u_analytic(testhelper_osc::SmoothSolutionX); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.PreprocessMesh(*pmesh); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc == MFEM_Approx(0.0)); + } + + SECTION("Perfect Approximation Y") + { + FunctionCoefficient u_analytic(testhelper_osc::SmoothSolutionY); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.PreprocessMesh(*pmesh); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc == MFEM_Approx(0.0)); + } + + SECTION("Nonsmooth Approximation X") + { + FunctionCoefficient u_analytic(testhelper_osc::NonsmoothSolutionX); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.SetThreshold(1e-3); + coeffrefiner.PreprocessMesh(*pmesh); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc <= 1e-3); + } + + SECTION("Nonsmooth Approximation Y") + { + FunctionCoefficient u_analytic(testhelper_osc::NonsmoothSolutionY); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.SetThreshold(1e-3); + coeffrefiner.PreprocessMesh(*pmesh); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc <= 1e-3); + } + + delete pmesh; +} + +TEST_CASE("Data Oscillation on 2D NCMesh embedded in 3D", + "[NCMesh], [Parallel]") +{ + // Setup + const auto order = GENERATE(1, 3, 5); + const auto max_it = GENERATE(1, 2, 4); + + // Manually construct embedded mesh + std::array vertices = + { + 0.0,0.0,0.0, + 0.0,1.0,0.0, + 1.0,1.0,0.0, + 1.0,0.0,0.0 + }; + + std::array element_indices = + { + 0,1,2,3 + }; + + std::array element_attributes = + { + 1 + }; + + std::array boundary_indices = + { + 0,1, + 1,2, + 2,3, + 3,0 + }; + + std::array boundary_attributes = + { + 1, + 1, + 1, + 1 + }; + + auto mesh = new Mesh( + vertices.data(), 4, + element_indices.data(), Geometry::SQUARE, + element_attributes.data(), 1, + boundary_indices.data(), Geometry::SEGMENT, + boundary_attributes.data(), 4, + 2, 3 + ); + mesh->UniformRefinement(); + mesh->Finalize(); + + // Make the mesh NC + mesh->EnsureNCMesh(); + { + Array elements_to_refine(1); + elements_to_refine[0] = 1; + mesh->GeneralRefinement(elements_to_refine, 1, 0); + } + + auto pmesh = new ParMesh(MPI_COMM_WORLD, *mesh); + delete mesh; + + SECTION("Perfect Approximation X") + { + FunctionCoefficient u_analytic(testhelper_osc::SmoothSolutionX); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc == MFEM_Approx(0.0)); + } + + SECTION("Perfect Approximation Y") + { + FunctionCoefficient u_analytic(testhelper_osc::SmoothSolutionY); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc == MFEM_Approx(0.0)); + } + + SECTION("Nonsmooth Approximation X") + { + FunctionCoefficient u_analytic(testhelper_osc::NonsmoothSolutionX); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.SetThreshold(1e-3); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc <= 1e-3); + } + + SECTION("Nonsmooth Approximation Y") + { + FunctionCoefficient u_analytic(testhelper_osc::NonsmoothSolutionY); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.SetThreshold(1e-3); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc <= 1e-3); + } + + delete pmesh; +} + +TEST_CASE("Data Oscillation on 3D NCMesh", + "[NCMesh], [Parallel]") +{ + // Setup + const auto order = GENERATE(1, 3, 5); + int max_it = 2; + Mesh mesh = Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON); + + // Make the mesh NC + mesh.EnsureNCMesh(); + { + Array elements_to_refine(1); + elements_to_refine[0] = 1; + mesh.GeneralRefinement(elements_to_refine, 1, 0); + } + + auto pmesh = new ParMesh(MPI_COMM_WORLD, mesh); + mesh.Clear(); + + SECTION("Perfect Approximation X") + { + FunctionCoefficient u_analytic(testhelper_osc::SmoothSolutionX); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc == MFEM_Approx(0.0)); + } + + SECTION("Perfect Approximation Y") + { + FunctionCoefficient u_analytic(testhelper_osc::SmoothSolutionY); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc == MFEM_Approx(0.0)); + } + + SECTION("Perfect Approximation Z") + { + FunctionCoefficient u_analytic(testhelper_osc::SmoothSolutionZ); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc == MFEM_Approx(0.0)); + } + + SECTION("Nonsmooth Approximation X") + { + FunctionCoefficient u_analytic(testhelper_osc::NonsmoothSolutionX); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.SetThreshold(1e-3); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc <= 1e-3); + } + + SECTION("Nonsmooth Approximation Y") + { + FunctionCoefficient u_analytic(testhelper_osc::NonsmoothSolutionY); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.SetThreshold(1e-3); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc <= 1e-3); + } + + SECTION("Nonsmooth Approximation Z") + { + FunctionCoefficient u_analytic(testhelper_osc::NonsmoothSolutionZ); + + CoefficientRefiner coeffrefiner(u_analytic,order); + coeffrefiner.SetThreshold(1e-3); + coeffrefiner.PreprocessMesh(*pmesh, max_it); + double osc = coeffrefiner.GetOsc(); + + REQUIRE(osc <= 1e-3); + } + + delete pmesh; +} + +#endif diff --git a/tests/unit/linalg/test_hypre_vector.cpp b/tests/unit/linalg/test_hypre_vector.cpp new file mode 100644 index 0000000000..98bdbcdc18 --- /dev/null +++ b/tests/unit/linalg/test_hypre_vector.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2010-2021, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-806117. +// +// This file is part of the MFEM library. For more information and source code +// availability visit https://mfem.org. +// +// MFEM is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +#include "unit_tests.hpp" +#include "mfem.hpp" + +namespace mfem +{ + +#ifdef MFEM_USE_MPI + +TEST_CASE("HypreParVector I/O", "[Parallel], [HypreParVector]") +{ + // Create a test vector (two entries per rank) with entries increasing + // sequentially. Write the vector to a file, read it into another vector, and + // make sure we get the same answer. + + int world_size, rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + int size_per_rank = 2; + + HYPRE_BigInt glob_size = world_size*size_per_rank; + std::vector col; + if (HYPRE_AssumedPartitionCheck()) + { + int offset = rank*size_per_rank; + col = {offset, offset + size_per_rank}; + } + else + { + col.resize(world_size+1); + for (int i=0; i