From 70afffe36e039be7ad9de205ec772b4acbb782d8 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 27 Jun 2024 12:45:34 +0200 Subject: [PATCH 01/70] Update create-release-archives.sh (fix typo in naming) --- tools/create-release-archives.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/create-release-archives.sh b/tools/create-release-archives.sh index ff284a65..4ae8c892 100755 --- a/tools/create-release-archives.sh +++ b/tools/create-release-archives.sh @@ -12,6 +12,6 @@ declare -a branches=("master" "OpenFOAMv1806" "OpenFOAM10" "OpenFOAM9" "OpenFOAM mkdir -p release-archives for i in "${branches[@]}" do - archive_name=openefoam-adapter-"${adapter_version}"-"${i}" + archive_name=openfoam-adapter-"${adapter_version}"-"${i}" git archive --format=tar.gz --prefix="${archive_name}"/ "${remote_name}"/"${i}" >"release-archives/${archive_name}".tar.gz done From de988ea239806970cdbc014af01d40f4d51d8234 Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Thu, 22 Aug 2024 08:58:50 -0300 Subject: [PATCH 02/70] Use setup-openfoam action in workflows (#338) --- .github/workflows/build-custom.yml | 145 +++++--------------------- .github/workflows/build.yml | 22 +--- .github/workflows/install-openfoam.sh | 6 -- changelog-entries/338.md | 1 + 4 files changed, 33 insertions(+), 141 deletions(-) delete mode 100755 .github/workflows/install-openfoam.sh create mode 100644 changelog-entries/338.md diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 7287a92c..9b0a9158 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -20,22 +20,22 @@ on: required: true type: choice options: - - OpenFOAMv2406 - - OpenFOAMv2312 - - OpenFOAMv2306 - - OpenFOAMv2212 - - OpenFOAMv2206 - - OpenFOAMv2112 - - OpenFOAMv2106 - - OpenFOAMv2012 - - OpenFOAMv2006 - - OpenFOAMv1912 - - OpenFOAM10 - - OpenFOAM9 - - OpenFOAM8 - - OpenFOAM7 - - OpenFOAM6 - - OpenFOAM5 + - 2406 + - 2312 + - 2306 + - 2212 + - 2206 + - 2112 + - 2106 + - 2012 + - 2006 + - 1912 + - 10 + - 9 + - 8 + - 7 + - 6 + - 5 versionpreCICE: description: 'Version of preCICE to build with' default: 'v3.0.0' @@ -83,102 +83,13 @@ jobs: uses: precice/setup-precice-action@main with: precice-version: ${{ github.event.inputs.versionpreCICE }} - - name: Install OpenFOAM (no cache) - id: installOpenFOAM - run: | - case "${{ github.event.inputs.versionOpenFOAM }}" in - OpenFOAMv2406) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2406-dev - echo "{openfoam_exec}={/usr/bin/openfoam2406}" >> $GITHUB_OUTPUT;; - OpenFOAMv2312) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2312-dev - echo "{openfoam_exec}={/usr/bin/openfoam2312}" >> $GITHUB_OUTPUT;; - OpenFOAMv2306) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2306-dev - echo "{openfoam_exec}={/usr/bin/openfoam2306}" >> $GITHUB_OUTPUT;; - OpenFOAMv2212) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2212-dev - echo "{openfoam_exec}={/usr/bin/openfoam2212}" >> $GITHUB_OUTPUT;; - OpenFOAMv2206) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2206-dev - echo "{openfoam_exec}={/usr/bin/openfoam2206}" >> $GITHUB_OUTPUT;; - OpenFOAMv2112) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2112-dev - echo "{openfoam_exec}={/usr/bin/openfoam2112}" >> $GITHUB_OUTPUT;; - OpenFOAMv2106) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2106-dev - echo "{openfoam_exec}={/usr/bin/openfoam2106}" >> $GITHUB_OUTPUT;; - OpenFOAMv2012) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2012-dev - echo "{openfoam_exec}={/usr/bin/openfoam2012}" >> $GITHUB_OUTPUT;; - OpenFOAMv2006) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam2006-dev - echo "{openfoam_exec}={/usr/bin/openfoam2006}" >> $GITHUB_OUTPUT;; - OpenFOAMv1912) - wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash - sudo apt-get install openfoam1912-dev - echo "{openfoam_exec}={/usr/bin/openfoam1912}" >> $GITHUB_OUTPUT;; - OpenFOAM10) - sudo sh -c "wget -O - https://dl.openfoam.org/gpg.key | apt-key add -" - sudo add-apt-repository http://dl.openfoam.org/ubuntu - sudo apt-get update - sudo apt-get -y install openfoam10 - echo "{openfoam_exec}={. /opt/openfoam10/etc/bashrc &&}" >> $GITHUB_OUTPUT;; - OpenFOAM9) - sudo sh -c "wget -O - https://dl.openfoam.org/gpg.key | apt-key add -" - sudo add-apt-repository http://dl.openfoam.org/ubuntu - sudo apt-get update - sudo apt-get -y install openfoam9 - echo "{openfoam_exec}={. /opt/openfoam9/etc/bashrc &&}" >> $GITHUB_OUTPUT;; - OpenFOAM8) - sudo sh -c "wget -O - https://dl.openfoam.org/gpg.key | apt-key add -" - sudo add-apt-repository http://dl.openfoam.org/ubuntu - sudo apt-get update - sudo apt-get -y install openfoam8 - echo "{openfoam_exec}={. /opt/openfoam8/etc/bashrc &&}" >> $GITHUB_OUTPUT;; - OpenFOAM7) - sudo sh -c "wget -O - https://dl.openfoam.org/gpg.key | apt-key add -" - sudo add-apt-repository http://dl.openfoam.org/ubuntu - sudo apt-get update - sudo apt-get -y install openfoam7 - echo "{openfoam_exec}={. /opt/openfoam7/etc/bashrc &&}" >> $GITHUB_OUTPUT;; - OpenFOAM6) - echo "OpenFOAM 6 is only available on Ubuntu 18.04 or older." - sudo sh -c "wget -O - https://dl.openfoam.org/gpg.key | apt-key add -" - sudo add-apt-repository http://dl.openfoam.org/ubuntu - sudo apt-get update - sudo apt-get -y install openfoam6 - echo "{openfoam_exec}={. /opt/openfoam6/etc/bashrc &&}" >> $GITHUB_OUTPUT;; - OpenFOAM5) - echo "OpenFOAM 5 is only available on Ubuntu 18.04 or older." - sudo sh -c "wget -O - https://dl.openfoam.org/gpg.key | apt-key add -" - sudo add-apt-repository http://dl.openfoam.org/ubuntu - sudo apt-get update - sudo apt-get -y install openfoam5 - echo "{openfoam_exec}={. /opt/openfoam5/etc/bashrc &&}" >> $GITHUB_OUTPUT;; - *) - echo "I cannot find ${{ github.event.inputs.refAdapter }} in my known options." - exit 1;; - esac - - name: Sanity check - run: | - ls /usr/bin/openfoam* || echo "No /usr/bin/openfoam* directories." - ls /opt/openfoam* || echo "No /opt/openfoam* directories." - echo "GitHub Actions step output openfoam_exec:" - echo ${{steps.installOpenFOAM.outputs.openfoam_exec}} + - name: Set up OpenFOAM + uses: gerlero/setup-openfoam@v1 + with: + openfoam-version: ${{ github.event.inputs.versionOpenFOAM }} - name: Build OpenFOAM-preCICE adapter run: | - echo "OpenFOAM environment: ${{steps.installOpenFOAM.outputs.openfoam_exec}}" - ${{steps.installOpenFOAM.outputs.openfoam_exec}} ./Allwmake + ./Allwmake - name: Get tutorials run: | git clone https://github.com/precice/tutorials.git --branch ${{ github.event.inputs.branchTutorials }} --depth 1 @@ -187,10 +98,10 @@ jobs: if ${{ github.event.inputs.runTutorialHeatedPlate }} then cd tutorials/flow-over-heated-plate/fluid-openfoam - ${{steps.installOpenFOAM.outputs.openfoam_exec}} ./run.sh | tee fluid-openfoam.log 2>&1 & + ./run.sh | tee fluid-openfoam.log 2>&1 & PIDfluid=$! cd ../solid-openfoam - ${{steps.installOpenFOAM.outputs.openfoam_exec}} ./run.sh | tee solid-openfoam.log 2>&1 + ./run.sh | tee solid-openfoam.log 2>&1 wait $PIDfluid fi - name: Run tutorial quickstart @@ -198,17 +109,17 @@ jobs: if ${{ github.event.inputs.runTutorialQuickstart }} then cd tutorials/quickstart/fluid-openfoam - if [ "${{ github.event.inputs.versionOpenFOAM }}" == "OpenFOAM5" ] + if [ "${{ github.event.inputs.versionOpenFOAM }}" == 5 ] then echo "Manually changing from pimpleFoam to pimpleDyMFoam for compatibility with OpenFOAM 5." sed -i 's/pimpleFoam/pimpleDyMFoam/g' system/controlDict fi - if [ "${{ github.event.inputs.versionOpenFOAM }}" == "OpenFOAM9" ] + if [ "${{ github.event.inputs.versionOpenFOAM }}" == 9 ] then echo "Manually changing from solver to motionSolver in dynamicMeshDict for compatibility with OpenFOAM 9." sed -i 's/solver/motionSolver/g' constant/dynamicMeshDict fi - ${{steps.installOpenFOAM.outputs.openfoam_exec}} ./run.sh | tee fluid-openfoam.log 2>&1 & + ./run.sh | tee fluid-openfoam.log 2>&1 & PIDfluid=$! cd ../solid-cpp cmake . && make && ./run.sh | tee solid-cpp.log 2>&1 @@ -219,10 +130,10 @@ jobs: if ${{ github.event.inputs.runTutorialPartitionedPipe }} then cd tutorials/partitioned-pipe/fluid1-openfoam-pimplefoam - ${{steps.installOpenFOAM.outputs.openfoam_exec}} ./run.sh | tee fluid1-openfoam-pimplefoam.log 2>&1 & + ./run.sh | tee fluid1-openfoam-pimplefoam.log 2>&1 & PIDfluid=$! cd ../fluid2-openfoam-pimplefoam - ${{steps.installOpenFOAM.outputs.openfoam_exec}} ./run.sh | tee fluid2-openfoam-pimplefoam.log 2>&1 & + ./run.sh | tee fluid2-openfoam-pimplefoam.log 2>&1 & wait $PIDfluid fi - name: Archive logs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5a79a0b..dc280ae7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,23 +25,9 @@ jobs: steps: - name: Check out repository uses: actions/checkout@v4 - - name: Install action dependencies - run: | - apt-get update -qq - apt-get install -qq sudo wget - - name: Setup caches - uses: actions/cache@v4 - id: cache-id + - name: Set up OpenFOAM + uses: gerlero/setup-openfoam@v1 with: - path: ${{ runner.temp }}/cache-directory - key: ${{ runner.os }}-cache-dependencies-v1.0 - - name: Install OpenFOAM (considering cache) - uses: airvzxf/cache-anything-new-action@v1.0.1 - with: - script: 'install-openfoam.sh' - is_cached: ${{ steps.cache-id.outputs.cache-hit }} - cache: ${{ runner.temp }}/cache-directory - snapshot: '/' - exclude: '/boot /data /dev /mnt /proc /run /sys' + openfoam-version: 2406 - name: Build OpenFOAM-preCICE adapter - run: /usr/bin/openfoam2406 ./Allwmake + run: ./Allwmake diff --git a/.github/workflows/install-openfoam.sh b/.github/workflows/install-openfoam.sh deleted file mode 100755 index 6a97a0e1..00000000 --- a/.github/workflows/install-openfoam.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -e - -# Install OpenFOAM v2406 -wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash -sudo apt-get update -y -sudo apt-get install -y openfoam2406-dev diff --git a/changelog-entries/338.md b/changelog-entries/338.md new file mode 100644 index 00000000..92cbb7e0 --- /dev/null +++ b/changelog-entries/338.md @@ -0,0 +1 @@ +- Use the [setup-openfoam](https://github.com/marketplace/actions/setup-openfoam) action to install OpenFOAM within CI workflows [#338](https://github.com/precice/openfoam-adapter/pull/338). From 20b4617ed6120fd3b6e6ab98de41fd849eea6662 Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Fri, 23 Aug 2024 03:30:51 -0300 Subject: [PATCH 03/70] Fix typo (#339) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc280ae7..a6856640 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build with OpenFOAM v2404 +name: Build with OpenFOAM v2406 on: push: branches-ignore: From 7832d7bfc47bd1abfac9d53318d3483f54930d4b Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:35:01 +0100 Subject: [PATCH 04/70] Fix different dimensions error / Checkpointing issue #158 (#344) --- Adapter.C | 72 ++++++++++++++++++++++++++++++++++++---- Adapter.H | 4 +++ changelog-entries/344.md | 1 + 3 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 changelog-entries/344.md diff --git a/Adapter.C b/Adapter.C index d3a10962..f5ec1bf8 100644 --- a/Adapter.C +++ b/Adapter.C @@ -3,6 +3,7 @@ #include "Utilities.H" #include "IOstreams.H" +#include using namespace Foam; @@ -437,6 +438,7 @@ void preciceAdapter::Adapter::execute() // Read checkpoint if required if (requiresReadingCheckpoint()) { + pruneCheckpointedFields(); readCheckpoint(); } @@ -895,12 +897,12 @@ void preciceAdapter::Adapter::setupCheckpointing() DEBUG(adapterInfo("Adding in checkpointed fields...")); #undef doLocalCode -#define doLocalCode(GeomField) \ - /* Checkpoint registered GeomField objects */ \ - for (const word& obj : mesh_.sortedNames()) \ - { \ - addCheckpointField(mesh_.thisDb().getObjectPtr(obj)); \ - DEBUG(adapterInfo("Checkpoint " + obj + " : " #GeomField)); \ +#define doLocalCode(GeomFieldType) \ + /* Checkpoint registered GeomFieldType objects */ \ + for (const word& obj : mesh_.sortedNames()) \ + { \ + addCheckpointField(mesh_.thisDb().getObjectPtr(obj)); \ + DEBUG(adapterInfo("Checkpoint " + obj + " : " #GeomFieldType)); \ } doLocalCode(volScalarField); @@ -923,6 +925,64 @@ void preciceAdapter::Adapter::setupCheckpointing() ACCUMULATE_TIMER(timeInCheckpointingSetup_); } +void preciceAdapter::Adapter::pruneCheckpointedFields() +{ + // Check if checkpointed fields exist in OpenFOAM registry + // If not, remove them from the checkpointed fields vector + + word fieldName; + uint index; + std::vector regFields; + std::vector toRemoveIndices; + +#undef doLocalCode +#define doLocalCode(GeomFieldType, GeomField_, GeomFieldCopies_) \ + regFields.clear(); \ + toRemoveIndices.clear(); \ + index = 0; \ + /* Iterate through fields in OpenFOAM registry */ \ + for (const word& fieldName : mesh_.sortedNames()) \ + { \ + regFields.push_back(fieldName); \ + } \ + /* Iterate through checkpointed fields */ \ + for (GeomFieldType * fieldObj : GeomFieldCopies_) \ + { \ + fieldName = fieldObj->name(); \ + if (std::find(regFields.begin(), regFields.end(), fieldName) == regFields.end()) \ + { \ + toRemoveIndices.push_back(index); \ + } \ + index += 1; \ + } \ + if (!toRemoveIndices.empty()) \ + { \ + /* Iterate in reverse to avoid index shifting */ \ + for (auto it = toRemoveIndices.rbegin(); it != toRemoveIndices.rend(); ++it) \ + { \ + index = *it; \ + DEBUG(adapterInfo("Removed " #GeomFieldType " : " + GeomFieldCopies_.at(index)->name() + " from the checkpointed fields list.")); \ + GeomField_.erase(GeomField_.begin() + index); \ + delete GeomFieldCopies_.at(index); \ + GeomFieldCopies_.erase(GeomFieldCopies_.begin() + index); \ + } \ + } + + doLocalCode(volScalarField, volScalarFields_, volScalarFieldCopies_); + doLocalCode(volVectorField, volVectorFields_, volVectorFieldCopies_); + doLocalCode(volTensorField, volTensorFields_, volTensorFieldCopies_); + doLocalCode(volSymmTensorField, volSymmTensorFields_, volSymmTensorFieldCopies_); + + doLocalCode(surfaceScalarField, surfaceScalarFields_, surfaceScalarFieldCopies_); + doLocalCode(surfaceVectorField, surfaceVectorFields_, surfaceVectorFieldCopies_); + doLocalCode(surfaceTensorField, surfaceTensorFields_, surfaceTensorFieldCopies_); + + doLocalCode(pointScalarField, pointScalarFields_, pointScalarFieldCopies_); + doLocalCode(pointVectorField, pointVectorFields_, pointVectorFieldCopies_); + doLocalCode(pointTensorField, pointTensorFields_, pointTensorFieldCopies_); + +#undef doLocalCode +} // All mesh checkpointed fields diff --git a/Adapter.H b/Adapter.H index 2e9cd606..b82efa20 100644 --- a/Adapter.H +++ b/Adapter.H @@ -294,6 +294,10 @@ private: //- Configure the checkpointing void setupCheckpointing(); + //- Remove checkpointed fields which are not used by OpenFOAM anymore + void pruneCheckpointedFields(); + + //- Make a copy of the runTime object void storeCheckpointTime(); diff --git a/changelog-entries/344.md b/changelog-entries/344.md new file mode 100644 index 00000000..3e05a373 --- /dev/null +++ b/changelog-entries/344.md @@ -0,0 +1 @@ +- Fixed a bug where a field was checkpointed, but doesn't exist anymore in OpenFOAM registry, causing a crash when reloading last timestep into OpenFOAM. This is done by pruning checkpointed fields that do not (anymore) appear in the registry of objects at that timestep before reading the checkpoint. [#344](https://github.com/precice/openfoam-adapter/pull/344) \ No newline at end of file From 17db60bf645fdc9ad673be7869a5d9b639f71127 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Mon, 27 Jan 2025 14:31:28 +0100 Subject: [PATCH 05/70] Update the default OpenFOAM version to v2412 (#347) --- .github/workflows/build-custom.yml | 1 + .github/workflows/build.yml | 4 ++-- changelog-entries/347.md | 1 + docs/openfoam-support.md | 12 ++++++++---- 4 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 changelog-entries/347.md diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 9b0a9158..06a63bce 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -20,6 +20,7 @@ on: required: true type: choice options: + - 2412 - 2406 - 2312 - 2306 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6856640..8f67ee3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build with OpenFOAM v2406 +name: Build with OpenFOAM v2412 on: push: branches-ignore: @@ -28,6 +28,6 @@ jobs: - name: Set up OpenFOAM uses: gerlero/setup-openfoam@v1 with: - openfoam-version: 2406 + openfoam-version: 2412 - name: Build OpenFOAM-preCICE adapter run: ./Allwmake diff --git a/changelog-entries/347.md b/changelog-entries/347.md new file mode 100644 index 00000000..8ffbf5fe --- /dev/null +++ b/changelog-entries/347.md @@ -0,0 +1 @@ +- Updated default OpenFOAM version to v2412 [#347](https://github.com/precice/openfoam-adapter/pull/347) \ No newline at end of file diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index dbad735a..a09eed5c 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -7,14 +7,14 @@ summary: Recent OpenFOAM.com versions work out-of-the-box. Recent OpenFOAM.org v ## How to get OpenFOAM -The easiest way to start is to get binary packages for your Linux distribution. For example, to [get OpenFOAM v2406 on Ubuntu](https://develop.openfoam.com/Development/openfoam/-/wikis/precompiled/debian#precompiled-packages-debianubuntu): +The easiest way to start is to get binary packages for your Linux distribution. For example, to [get OpenFOAM v2412 on Ubuntu](https://develop.openfoam.com/Development/openfoam/-/wikis/precompiled/debian#precompiled-packages-debianubuntu): ```bash # Add the signing key, add the repository, update: wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash -# Install OpenFOAM v2406: -sudo apt-get install openfoam2406-dev +# Install OpenFOAM v2412: +sudo apt-get install openfoam2412-dev ``` As these steps change your `.profile`, you need to log out and in again to make OpenFOAM fully discoverable. @@ -23,10 +23,14 @@ As these steps change your `.profile`, you need to log out and in again to make OpenFOAM is a project with long history and many forks, of which we try to support as many as possible. Since several HPC systems only provide older versions, we try to also support a wide range of versions. +{% warning %} +Currently, and since v1.3.0 of the adapter (first one for preCICE v3), only the default variant of the adapter for the OpenCFD / ESI is available. We are working on supporting further versions again. +{% endwarning %} + We provide version-specific [release archives](https://github.com/precice/openfoam-adapter/releases/latest) and respective Git branches for: - OpenCFD / ESI (openfoam.com) - main focus: - - [OpenFOAM v1812-v2406](https://github.com/precice/openfoam-adapter) or newer + - [OpenFOAM v1812-v2412](https://github.com/precice/openfoam-adapter) or newer - OpenFOAM v2212 and newer is only supported since v1.2.2 of the adapter. - [OpenFOAM v1612-v1806](https://github.com/precice/openfoam-adapter/tree/OpenFOAMv1806) (not tested) - OpenFOAM Foundation (openfoam.org) - secondary, consider experimental: From 9c736f368d9488a1e051b1f1930ab522975a28c6 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 30 Jan 2025 10:19:57 +0100 Subject: [PATCH 06/70] Add missing literature entries in docs --- docs/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 106bb92a..ae64d8fc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -64,7 +64,7 @@ It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia The fluid-structure interaction module was developed in close collaboration between Gerasimos Chourdakis and Derek Risseeuw (TU Delft), in the context of the [master's thesis of the latter](http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123) [4]. We would also like to thank David Schneider (Univ. Siegen / TUM) and Maximilian Müller (TU Braunschweig) for sharing the code and experience of their similar previous work. -The fluid-fluid coupling module was added by Gerasimos Chourdakis, in the context of his dissertation. [#67](https://github.com/precice/openfoam-adapter/pull/67) +The fluid-fluid coupling module was added by Gerasimos Chourdakis, in the context of his dissertation. [#67](https://github.com/precice/openfoam-adapter/pull/67). Further contributions in this direction by Markus Mühlhäußer ([master's thesis](https://mediatum.ub.tum.de/node?id=1696254&change_language=en) [5], [related publication](https://mediatum.ub.tum.de/node?id=1732401&change_language=en) [6]). The adapter is [easily extensible](https://precice.org/adapter-openfoam-extend.html). @@ -78,6 +78,10 @@ The adapter is [easily extensible](https://precice.org/adapter-openfoam-extend.h [4] Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019. +[5] Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022. + +[6] Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023. [DOI: 10.23967/c.coupled.2023.014](https://doi.org/10.23967/c.coupled.2023.014) + {% disclaimer %} This offering is not approved or endorsed by OpenCFD Limited, producer and distributor of the OpenFOAM software via www.openfoam.com, and owner of the OPENFOAM® and OpenCFD® trade marks. {% enddisclaimer %} From 11378fa47713c37ccbe81692e0442c6ebb6273b7 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 30 Jan 2025 11:30:15 +0100 Subject: [PATCH 07/70] Make readData and writeData optional (#348) --- Adapter.C | 4 ++-- changelog-entries/348.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog-entries/348.md diff --git a/Adapter.C b/Adapter.C index f5ec1bf8..7146b1c9 100644 --- a/Adapter.C +++ b/Adapter.C @@ -139,7 +139,7 @@ bool preciceAdapter::Adapter::configFileRead() } DEBUG(adapterInfo(" writeData : ")); - auto writeData = interfaceDict.get("writeData"); + auto writeData = interfaceDict.lookupOrDefault("writeData", wordList()); for (auto writeDatum : writeData) { interfaceConfig.writeData.push_back(writeDatum); @@ -147,7 +147,7 @@ bool preciceAdapter::Adapter::configFileRead() } DEBUG(adapterInfo(" readData : ")); - auto readData = interfaceDict.get("readData"); + auto readData = interfaceDict.lookupOrDefault("readData", wordList()); for (auto readDatum : readData) { interfaceConfig.readData.push_back(readDatum); diff --git a/changelog-entries/348.md b/changelog-entries/348.md new file mode 100644 index 00000000..11201c5e --- /dev/null +++ b/changelog-entries/348.md @@ -0,0 +1 @@ +- Added support for preciceDict configuration files that don't specify an `readData` or `writeData` entry at an interface. [#348](https://github.com/precice/openfoam-adapter/pull/348) \ No newline at end of file From a672e2c33b0403ac8e9597d4dd844d29db8e3a21 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 12 Feb 2025 17:57:13 +0100 Subject: [PATCH 08/70] README.md: Update badges --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index caa2ba96..2189134a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ # OpenFOAM-preCICE adapter -[![Ask questions in the forum](https://img.shields.io/badge/community-discourse_forum-orange?link=https%3A%2F%2Fprecice.discourse.group%2F)](https://precice.discourse.group/) -[![Build with OpenFOAM v2404](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml) +[![Ask questions in the forum](https://img.shields.io/badge/community-forum-orange?link=https%3A%2F%2Fprecice.discourse.group%2F)](https://precice.discourse.group/) +[![Release](https://img.shields.io/github/release/precice/openfoam-adapter.svg)](https://github.com/precice/openfoam-adapter/releases/latest) +[![SoftwareHeritage](https://archive.softwareheritage.org/badge/origin/https://github.com/precice/openfoam-adapter/)](https://archive.softwareheritage.org/browse/origin/?origin_url=https://github.com/precice/openfoam-adapter) +[![Cite](https://img.shields.io/badge/cite-literature_guide-d45815)](https://precice.org/fundamentals-literature-guide.html) [![Changelog](https://img.shields.io/badge/Keep%20a%20Changelog--555.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmMTVkMzAiIHZpZXdCb3g9IjAgMCAxODcgMTg1Ij48cGF0aCBkPSJNNjIgN2MtMTUgMy0yOCAxMC0zNyAyMmExMjIgMTIyIDAgMDAtMTggOTEgNzQgNzQgMCAwMDE2IDM4YzYgOSAxNCAxNSAyNCAxOGE4OSA4OSAwIDAwMjQgNCA0NSA0NSAwIDAwNiAwbDMtMSAxMy0xYTE1OCAxNTggMCAwMDU1LTE3IDYzIDYzIDAgMDAzNS01MiAzNCAzNCAwIDAwLTEtNWMtMy0xOC05LTMzLTE5LTQ3LTEyLTE3LTI0LTI4LTM4LTM3QTg1IDg1IDAgMDA2MiA3em0zMCA4YzIwIDQgMzggMTQgNTMgMzEgMTcgMTggMjYgMzcgMjkgNTh2MTJjLTMgMTctMTMgMzAtMjggMzhhMTU1IDE1NSAwIDAxLTUzIDE2bC0xMyAyaC0xYTUxIDUxIDAgMDEtMTItMWwtMTctMmMtMTMtNC0yMy0xMi0yOS0yNy01LTEyLTgtMjQtOC0zOWExMzMgMTMzIDAgMDE4LTUwYzUtMTMgMTEtMjYgMjYtMzMgMTQtNyAyOS05IDQ1LTV6TTQwIDQ1YTk0IDk0IDAgMDAtMTcgNTQgNzUgNzUgMCAwMDYgMzJjOCAxOSAyMiAzMSA0MiAzMiAyMSAyIDQxLTIgNjAtMTRhNjAgNjAgMCAwMDIxLTE5IDUzIDUzIDAgMDA5LTI5YzAtMTYtOC0zMy0yMy01MWE0NyA0NyAwIDAwLTUtNWMtMjMtMjAtNDUtMjYtNjctMTgtMTIgNC0yMCA5LTI2IDE4em0xMDggNzZhNTAgNTAgMCAwMS0yMSAyMmMtMTcgOS0zMiAxMy00OCAxMy0xMSAwLTIxLTMtMzAtOS01LTMtOS05LTEzLTE2YTgxIDgxIDAgMDEtNi0zMiA5NCA5NCAwIDAxOC0zNSA5MCA5MCAwIDAxNi0xMmwxLTJjNS05IDEzLTEzIDIzLTE2IDE2LTUgMzItMyA1MCA5IDEzIDggMjMgMjAgMzAgMzYgNyAxNSA3IDI5IDAgNDJ6bS00My03M2MtMTctOC0zMy02LTQ2IDUtMTAgOC0xNiAyMC0xOSAzN2E1NCA1NCAwIDAwNSAzNGM3IDE1IDIwIDIzIDM3IDIyIDIyLTEgMzgtOSA0OC0yNGE0MSA0MSAwIDAwOC0yNCA0MyA0MyAwIDAwLTEtMTJjLTYtMTgtMTYtMzEtMzItMzh6bS0yMyA5MWgtMWMtNyAwLTE0LTItMjEtN2EyNyAyNyAwIDAxLTEwLTEzIDU3IDU3IDAgMDEtNC0yMCA2MyA2MyAwIDAxNi0yNWM1LTEyIDEyLTE5IDI0LTIxIDktMyAxOC0yIDI3IDIgMTQgNiAyMyAxOCAyNyAzM3MtMiAzMS0xNiA0MGMtMTEgOC0yMSAxMS0zMiAxMXptMS0zNHYxNGgtOFY2OGg4djI4bDEwLTEwaDExbC0xNCAxNSAxNyAxOEg5NnoiLz48L3N2Zz4K)](https://github.com/precice/openfoam-adapter/blob/develop/CHANGELOG.md) [![License](https://img.shields.io/github/license/precice/openfoam-adapter.svg)](https://github.com/precice/openfoam-adapter/blob/master/LICENSE) +[![Build with OpenFOAM v2404](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml) ## Start here From da20016737e8076bf09d3717dc8c34550cfa3516 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Fri, 14 Mar 2025 22:38:30 +0100 Subject: [PATCH 09/70] Document differences between OpenFOAM versions (#351) --- changelog-entries/351.md | 1 + docs/openfoam-support.md | 169 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 changelog-entries/351.md diff --git a/changelog-entries/351.md b/changelog-entries/351.md new file mode 100644 index 00000000..0c041c2b --- /dev/null +++ b/changelog-entries/351.md @@ -0,0 +1 @@ +- Documented relevant differences between OpenFOAM versions, to make porting between versions easier. [#351](https://github.com/precice/openfoam-adapter/pull/351) \ No newline at end of file diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index a09eed5c..a2e8c541 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -19,6 +19,10 @@ sudo apt-get install openfoam2412-dev As these steps change your `.profile`, you need to log out and in again to make OpenFOAM fully discoverable. +## Supported OpenFOAM solvers + +We support mainstream OpenFOAM solvers such as pimpleFoam and solids4Foam for FSI, buoyantPimpleFoam, buoyantSimpleFoam, and laplacianFoam for CHT, or pimpleFoam and sonicLiquidFoam for FF. Our community has, additionally, tried the adapter with multiple different solvers that support function objects. + ## Supported OpenFOAM versions OpenFOAM is a project with long history and many forks, of which we try to support as many as possible. Since several HPC systems only provide older versions, we try to also support a wide range of versions. @@ -31,28 +35,175 @@ We provide version-specific [release archives](https://github.com/precice/openfo - OpenCFD / ESI (openfoam.com) - main focus: - [OpenFOAM v1812-v2412](https://github.com/precice/openfoam-adapter) or newer - - OpenFOAM v2212 and newer is only supported since v1.2.2 of the adapter. - [OpenFOAM v1612-v1806](https://github.com/precice/openfoam-adapter/tree/OpenFOAMv1806) (not tested) - OpenFOAM Foundation (openfoam.org) - secondary, consider experimental: - [OpenFOAM 10](https://github.com/precice/openfoam-adapter/tree/OpenFOAM10) - - Several [changes to the tutorials](https://github.com/precice/tutorials/tree/OpenFOAM10) are also needed, read the [discussion](https://github.com/precice/tutorials/pull/283). - - Same limitations as for OpenFOAM 9. - [OpenFOAM 9](https://github.com/precice/openfoam-adapter/tree/OpenFOAM9) - - Rename `solver` to `motionSolver` in `constant/dynamicMeshDict`. - - Modify also `residualControl` to `outerCorrectorResidualControl` in `system/fvSolution`. - - Limitations in adjustable time step size ([#261](https://github.com/precice/openfoam-adapter/issues/261)). - [OpenFOAM 8](https://github.com/precice/openfoam-adapter/tree/OpenFOAM8) - [OpenFOAM 7](https://github.com/precice/openfoam-adapter/tree/OpenFOAM7) - [OpenFOAM 6](https://github.com/precice/openfoam-adapter/tree/OpenFOAM6) - - Modify also `residualControl` to `outerResidualControl` in `system/fvSolution`. - [OpenFOAM 5.x](https://github.com/precice/openfoam-adapter/tree/OpenFOAM5) - [OpenFOAM 4.0/4.1](https://github.com/precice/openfoam-adapter/tree/OpenFOAM4) (not tested) Known not supported versions: OpenFOAM v1606+ or older, OpenFOAM 3 or older, foam-extend (any version). -## Supported OpenFOAM solvers +## Differences between OpenFOAM versions + +We take into account the following relevant differences between OpenFOAM versions, compared to the latest OpenCFD version. This list is important for maintainers of version-specific ports of the adapter. + +### OpenFOAM v1612-v1806 + +- **clockValue:** The `clockValue.H` header is not available (used in `preciceAdapterFunctionObject.H`). + - Disable the timers feature, controlled by the `ADAPTER_ENABLE_TIMINGS` preprocessor variable (set in `Allwmake`). +- **dictionary access:** In dictionaries (essentially: in the `preciceDict`), some methods are not available. These are mainly used in the configuration step in `Adapter.C`, but also in `ForceBase.C`. + - Replace `preciceDict.get("...")` with `static_cast(preciceDict.lookup("...")`. + - Replace `preciceDict.get("...")` with `static_cast(preciceDict.lookup("..."))`. + - Replace `preciceDict.get("...")` with `static_cast(preciceDict.lookup("..."))`. + - Replace `preciceDict.findDict("...")` with `preciceDict.subDictPtr("...")`. +- **Db access:** In the macro that deals with adding checkpointed fields, some methods are not available. + - Replace `mesh_.sortedNames()` with `mesh_.lookupClass().sortedToc()`. + - Replace `mesh_.thisDb().getObjectPtr(obj)` with `&(const_cast(mesh_.thisDb().lookupObject(obj)))`. +- **Pointers:** `std::unique_ptr` is not available. + - Replace `std::unique_ptr` with `Foam::autoPtr`. + - Replace calls to pointer `.reset()`, such as `ForceOwning_.reset(new volVectorField(`, with `ForceOwning_ = new volVectorField(`. Adjust the number of closing parentheses. + - Replace calls to pointer `.get();`, such as `ForceOwning_.get()`, with `ForceOwning_.ptr()`. + +### OpenFOAM 4 and 5 + +OpenFOAM 4 was [released](https://openfoam.org/release/4/) in June 2016 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-4.x), [Doxygen](https://cpp.openfoam.org/v4)). + +OpenFOAM 5 was [released](https://openfoam.org/release/5/) in July 2017 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-5.x), [Doxygen](https://cpp.openfoam.org/v5)). + +Same as in OpenFOAM v1612-v1806. + +### OpenFOAM 6 + +OpenFOAM 6 was [released](https://openfoam.org/release/6/) in July 2018 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-6), [Doxygen](https://cpp.openfoam.org/v6)). Compared to OpenFOAM 5: + +- **adjustTimeStep:** In the function objects interface, `adjustTimeStep()` was renamed to `setTimeStep()`. + - Replace `adjustTimeStep()` with `setTimeStep()`, both for the function object and the adapter members. +- **setDeltaT:** The `Time` method `setDeltaT(..., false)` was replaced by `setDeltaTNoAdjust(...)`. + - Replace `setDeltaT(timestepSolver_, false)` with `setDeltaTNoAdjust(timestepSolver_)`. +- **Case files:** + - In `system/fvSolution`, replace `residualControl` with `outerResidualControl`. + +Related work in the adapter: [PR #33](https://github.com/precice/openfoam-adapter/pull/33). + +### OpenFOAM 7 + +OpenFOAM 7 was [released](https://openfoam.org/release/7/) in July 2019 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-7), [Doxygen](https://cpp.openfoam.org/v7)). Compared to OpenFOAM 6: + +- **fileName:** `fileName::DIRECTORY` was renamed to `fileType::directory`. + - Replace this in the `Adapter.C`. + +Related work in the adapter: [PR #91](https://github.com/precice/openfoam-adapter/pull/91). + +### OpenFOAM 8 + +OpenFOAM 8 was [released](https://openfoam.org/release/8/) in July 2020 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-8), [Doxygen](https://cpp.openfoam.org/v8)). Compared to OpenFOAM 7: + +- **Function objects:** Function objects changed behavior, and the `execute()` method is now also executed at start. See the [adapter issue #179](https://github.com/precice/openfoam-adapter/issues/179) and [PR #180](https://github.com/precice/openfoam-adapter/pull/180). + - In the beginning of `preciceAdapterFunctionObject::read()`, add `this->executeAtStart_ = false;`. +- **Thermophysical models:** Some properties were moved from the previous `TurbulenceModels` and `transportModels` to the new `ThermophysicalTransportModels` and `MomentumTransportModels`. + - In `Make/options`, replace the following include paths: + - Remove the include paths to `transportModels/incompressible`, `transportModels/compressible`, `transportModels/twoPhaseMixture`, `transportModels/interfaceProperties`, `transportModels/immiscibleIncompressibleTwoPhaseMixture`, `TurbulenceModels/turbulenceModels`, `TurbulenceModels/compressible`, `TurbulenceModels/incompressible`. + - Add include paths to `ThermophysicalTransportModels`, `MomentumTransportModels/momentumTransportModels`, `MomentumTransportModels/compressible`, `MomentumTransportModels/incompressible`, `transportModels`. + - Remove the linking paths to `compressibleTurbulenceModels`, `incompressibleTurbulenceModels`. + - Add linking paths to `thermophysicalTransportModels`, `fluidThermoMomentumTransportModels`, `incompressibleMomentumTransportModels`. + - In `CHT/KappaEffective.H`: + - Remove the header inclusions `turbulentFluidThermoModel.H`, `turbulentTransportModel.H`. + - Include the headers `fluidThermoMomentumTransportModel.H`, `kinematicMomentumTransportModel.H`, `thermophysicalTransportModel.H`. + - Remove the variable declarations for `turbulence_` (2x), `nameRho_`, `nameCp_`, `namePr_`, `nameAlphat_`, `rho_`, `Cp_`, `Pr_` from the compressible and incompressible variants. + - In the incompressible variant's constructor signature, remove the `nameRho`, `nameCp`, `namePr`, `nameAlphat`. + - Add a new declaration: `const Foam::thermophysicalTransportModel& thermophysicalTransportModel_;` for each one of the `turbulence_` removed. + - In `CHT/KappaEffective.C`: + - Replace all instances of `turbulence_` with `thermophysicalTransportModel_` and update the respective comments. + - Replace `compressible::turbulenceModel` and `incompressible::turbulenceModel` with `thermophysicalTransportModel`. + - Replace `turbulenceModel::propertiesName` with `thermophysicalTransportModel::typeName`. + - In the incompressible variant, remove all lines of code working with `nameRho_`, `nameCp_`, `namePr_`, `nameAlphat_` from the constructor (which ends up with an empty body) and the `extract()` method. Replace the usage of `kappaEff_temp` with `thermophysicalTransportModel_.kappaEff()().boundaryField()[patchID]`. + - Replace the `const dictionary& CHTDict` with `const dictionary CHTDict`. + - In `CHT/HeatFlux.C` and in `CHT/HeatTransferCoefficient.C`: + - Replace `KappaEff_Incompressible(mesh, nameRho, nameCp, namePr, nameAlphat)` with `KappaEff_Incompressible(mesh)`. + - In `FSI/ForceBase.H`: + - Remove the header inclusions `immiscibleIncompressibleTwoPhaseMixture.H`, `turbulentFluidThermoModel.H`, `turbulentTransportModel.H`. + - Include the headers `fluidThermoMomentumTransportModel.H`, `kinematicMomentumTransportModel.H`. + - In `FSI/ForceBase.C`, in the `ForceBase::devRhoReff()`: + - Replace `compressible::turbulenceModel` with `compressible::momentumTransportModel`. + - Replace `incompressible::turbulenceModel` with `incompressible::momentumTransportModel`. + - Replace `cmpTurbModel::propertiesName` with `cmpTurbModel::typeName`. + - Replace `turb(mesh_.lookupObject<>())` with `turb = mesh_.lookupObject<>()` (where `const cmpTurbModel& turb`). + - Replace `turb.devRhoReff()` with `turb.devTau()`. + - Replace `turb.devReff()` with `turb.devSigma()`. + - Replace `U(mesh_.lookupObject<>())` with `U = mesh_.lookupObject<>()` (where `const volVectorField& U`). + - In `FSI/ForceBase.C`, in the `ForceBase::mu()`: + - Remove the line `typedef immiscibleIncompressibleTwoPhaseMixture iitpMixture;`. + - Replace the type `iitpMixture` with `fluidThermo` and rename the respective object from `mixture` to `thermo`. + - Replace `"mixture"` in the lookups with `basicThermo::dictName`. + +Related work in the adapter: [PR #130](https://github.com/precice/openfoam-adapter/pull/130). + +### OpenFOAM 9 + +OpenFOAM 9 was [released](https://openfoam.org/release/9/) in July 2021 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-9), [Doxygen](https://cpp.openfoam.org/v9)). Compared to OpenFOAM 8: + +- **Function objects:** The `setTimeStep()` method was removed. + - Remove the method from the `preciceAdapterFunctionObject.H` and `preciceAdapterFunctionObject.C`. Adjust the comments for the equivalent method in `Adapter.H`. + - **Impact:** See limitations in adjustable time step size ([#261](https://github.com/precice/openfoam-adapter/issues/261)). +- **Thermophysical models:** Different libraries need to be linked and different headers need to be included: + - In `Make/options`, remove the linking of `fluidThermoMomentumTransportModels` and `incompressibleMomentumTransportModels`. + - In `Make/options`, link to `transportModels`, `momentumTransportModels`, `incompressibleMomentumTransportModels`, `compressibleMomentumTransportModels`. + - In `CHT/KappaEffective.H`, replace the headers `fluidThermoMomentumTransportModel.H` and `kinematicMomentumTransportModel.H` with `momentumTransportModel.H`. + - In `FSI/ForceBase.H`, replace the header `fluidThermoMomentumTransportModel.H` with `compressibleMomentumTransportModel.H`. + - In `FSI/ForceBase.C`, add the header `fluidThermo.H`. + - In `FSI/ForceBase.C`, replace: + - `compressible::momentumTransportModel` with `compressibleMomentumTransportModel` + - `incompressible::momentumTransportModel` with `incompressibleMomentumTransportModel`. Further down, replace the usage `const incompressible::momentumTransportModel&` with `const icoTurbModel&`. +- **Triangulation:** In `Adapter.C`: + - Replace the header `faceTriangulation.H` with `polygonTriangulate.H`. +- **Case files:** + - In `constant/dynamicMeshDict`, replace `solver` with `motionSolver`. + - In `system/fvSolution`, replace `residualControl` with `outerCorrectorResidualControl`. This was already renamed to `outerResidualControl` in OpenFOAM 6. + +Related work in the adapter: [PR #221](https://github.com/precice/openfoam-adapter/pull/221). + +### OpenFOAM 10 + +OpenFOAM 10 was [released](https://openfoam.org/release/10/) in July 2022 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-10), [Doxygen](https://cpp.openfoam.org/v10)). Compared to OpenFOAM 9: + +- **Function objects:** The method `fields()` has been made pure virtual, meaning it needs to be implemented. + - In `preciceAdapterFunctionObject.H`, implement it as (more of a workaround to avoid implementation): + + ```c++ + virtual wordList fields() const + { + return wordList::null(); + } + ``` -We support mainstream OpenFOAM solvers such as pimpleFoam and solids4Foam for FSI, buoyantPimpleFoam, buoyantSimpleFoam, and laplacianFoam for CHT, or pimpleFoam and sonicLiquidFoam for FF. Our community has, additionally, tried the adapter with multiple different solvers that support function objects. +- **Physical properties and models:** The `physicalProperties` was replaced by `transportModels`. + - In `Make/options`, replace `physicalProperties` with `transportModels` in the included paths and in the linked libraries. + - In `FSI/ForceBase.H`, replace `kinematicMomentumTransportModel.H` with `incompressibleMomentumTransportModel.H`. + - In `FSI/ForceBase.C`, replace `basicThermo::dictName` with `physicalProperties::typeName`. +- **Fields `V0` and `V00`:** The fields `V0` and `V00` have been removed or renamed (not sure to what). + - In `Adapter.C`, method `preciceAdapter::Adapter::setupMeshVolCheckpointing()`, guard each of these two fields with a `if (mesh_.foundObject("V0"))` (and `if (... ("V00"))`, respectively). Alternatively, remove the implementation of this method altogether. + - **Impact:** This might lead to stability issues in FSI simulations with implicit coupling. Check your results. +- **Case files:** Several [changes to the tutorials](https://github.com/precice/tutorials/tree/OpenFOAM10) are needed, read the [discussion](https://github.com/precice/tutorials/pull/283). + +### OpenFOAM 11 + +OpenFOAM 11 was [released](https://openfoam.org/release/11/) in July 2023 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-11), [Doxygen](https://cpp.openfoam.org/v11)). We do not yet know which are relevant changes. Please help us port the adapter to OpenFOAM 11. + +Related work in the adapter: [PR #310](https://github.com/precice/openfoam-adapter/pull/310). + +### OpenFOAM 12 + +OpenFOAM 12 was [released](https://openfoam.org/release/12/) in July 2024 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-12), [Doxygen](https://cpp.openfoam.org/v12)). We do not yet know which are relevant changes. Please help us port the adapter to OpenFOAM 12. + +### foam-extend + +We have not yet documented all related changes that would make the adapter work for foam-extend. See the repository [`precice/foam-extend-adapter`](https://github.com/precice/foam-extend-adapter) for the latest state and please contribute to this page. + +Related work in the adapter: [PR #302](https://github.com/precice/openfoam-adapter/pull/302). ## Notes on OpenFOAM features From 411dede4af30d8dbd0c0a6957f3d23ffd633f857 Mon Sep 17 00:00:00 2001 From: Benjamin Rodenberg Date: Thu, 20 Mar 2025 11:29:21 +0100 Subject: [PATCH 10/70] Set `ADAPTER_TARGET_DIR` and `ADAPTER_CFLAGS` (`ADAPTER_PREP_FLAGS`) via environment variables (#352) Co-authored-by: Gerasimos Chourdakis --- Allwmake | 8 ++++---- Make/options | 2 +- changelog-entries/352.md | 2 ++ docs/get.md | 10 +++++----- 4 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 changelog-entries/352.md diff --git a/Allwmake b/Allwmake index 2480fe64..ec7df980 100755 --- a/Allwmake +++ b/Allwmake @@ -6,7 +6,7 @@ set -e -u # Optional: Preprocessor flags # "-DADAPTER_DEBUG_MODE" enables debug messages # "-DADAPTER_ENABLE_TIMINGS" enables time measurements -ADAPTER_PREP_FLAGS="${ADAPTER_PREP_FLAGS:-}" +ADAPTER_CFLAGS="${PRECICE_OPENFOAM_CFLAGS:-}" # Build command and options # In order to compile with multiple threads, set the environment variable WM_NCOMPPROCS, @@ -17,7 +17,7 @@ adapter_build_command(){ } # Where should the adapter be built? Default: "${FOAM_USER_LIBBIN}" -ADAPTER_TARGET_DIR="${FOAM_USER_LIBBIN:-}" +ADAPTER_TARGET_DIR="${PRECICE_OPENFOAM_TARGET_DIR:-$FOAM_USER_LIBBIN}" # More information for compatibility with OpenFOAM DOC_COMPATIBILITY="https://precice.org/adapter-openfoam-support.html" @@ -38,7 +38,7 @@ warning() { log "Building the OpenFOAM-preCICE adapter..." # Export the environment variables -export ADAPTER_PREP_FLAGS +export ADAPTER_CFLAGS export ADAPTER_TARGET_DIR # Check if pkg-config is available and get the config for preCICE @@ -90,7 +90,7 @@ fi log "" log "The adapter will be built into ${ADAPTER_TARGET_DIR}" -log "Additional preprocessor/compiler options: ${ADAPTER_PREP_FLAGS}" +log "Additional preprocessor/compiler options: ${ADAPTER_CFLAGS}" # Run wmake (build the adapter) and check the exit code log "" diff --git a/Make/options b/Make/options index 5104d7f6..af0f6292 100644 --- a/Make/options +++ b/Make/options @@ -14,7 +14,7 @@ EXE_INC = \ -I$(LIB_SRC)/triSurface/lnInclude \ $(ADAPTER_PKG_CONFIG_CFLAGS) \ -I../ \ - $(ADAPTER_PREP_FLAGS) + $(ADAPTER_CFLAGS) LIB_LIBS = \ -lfiniteVolume \ diff --git a/changelog-entries/352.md b/changelog-entries/352.md new file mode 100644 index 00000000..08789e44 --- /dev/null +++ b/changelog-entries/352.md @@ -0,0 +1,2 @@ +- Renamed `ADAPTER_PREP_FLAGS` to `ADAPTER_CFLAGS` and set it from `PRECICE_OPENFOAM_CFLAGS`. [#352](https://github.com/precice/openfoam-adapter/pull/352) +- Set `ADAPTER_TARGET_DIT` from `PRECICE_OPENFOAM_TARGET_DIR`. [#352](https://github.com/precice/openfoam-adapter/pull/352) diff --git a/docs/get.md b/docs/get.md index 5460106d..33537bff 100644 --- a/docs/get.md +++ b/docs/get.md @@ -17,12 +17,12 @@ To build the adapter, you need to install a few dependencies and then execute th The adapter also requires [pkg-config](https://linux.die.net/man/1/pkg-config) to [link to preCICE](https://precice.org/installation-linking.html). This is a very common dependency on Linux and is usually already installed. -You can set compile flags by either changing the `ADAPTER_PREP_FLAGS` variable in the `Allwmake` script, or directly setting the value of `ADAPTER_PREP_FLAGS` as an environment variable. -To do so, `export ADAPTER_PREP_FLAGS="-D -D"` before compiling the adapter. +The `Allwmake` script uses two environment variables: -Adding the `-DADAPTER_DEBUG_MODE` flag to the `ADAPTER_PREP_FLAGS` activates additional debug messages. You may also change the target directory or specify the number of threads to use for the compilation. See the comments in `Allwmake` for more. - -Adding the `-DADAPTER_ENABLE_TIMINGS` flag to the `ADAPTER_PREP_FLAGS` activates time measurements for several regions of the adapter, printed at the end of the simulation output (available since v1.2.0). +* `PRECICE_OPENFOAM_TARGET_DIR`: Where the `libpreciceAdapterFunctionObject.so` will be installed. Defaults to `FOAM_USER_LIBBIN`. +* `PRECICE_OPENFOAM_CFLAGS`: Additional flags passed to wmake and used by the preprocessor/compiler. Defaults to an empty string. + * Adding `-DADAPTER_DEBUG_MODE` flag activates additional debug messages. + * Adding the `-DADAPTER_ENABLE_TIMINGS` flag activates time measurements for several regions of the adapter, printed at the end of the simulation output. If you are building the adapter often, you may want to build it in parallel. You can set the environment variable `WM_NCOMPPROCS` to the number of parallel threads you want WMake to use. From 5dc0ce070898f8ed041b56604059134126832bbd Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 20 Mar 2025 14:40:58 +0100 Subject: [PATCH 11/70] Document running actions locally and update custom build CI options (#350) --- .github/workflows/.gitignore | 2 ++ .github/workflows/build-custom.input | 8 ++++++ .github/workflows/build-custom.yml | 9 +++--- CONTRIBUTING.md | 20 ++++++++++++++ changelog-entries/350.md | 1 + docs/openfoam-support.md | 41 ++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/.gitignore create mode 100644 .github/workflows/build-custom.input create mode 100644 changelog-entries/350.md diff --git a/.github/workflows/.gitignore b/.github/workflows/.gitignore new file mode 100644 index 00000000..72890765 --- /dev/null +++ b/.github/workflows/.gitignore @@ -0,0 +1,2 @@ +.secrets +.artifacts diff --git a/.github/workflows/build-custom.input b/.github/workflows/build-custom.input new file mode 100644 index 00000000..5b234156 --- /dev/null +++ b/.github/workflows/build-custom.input @@ -0,0 +1,8 @@ +virtualEnvironment="ubuntu-22.04" +refAdapter="develop" +versionOpenFOAM="2412" +versionpreCICE="v3.1.2" +runTutorialHeatedPlate="false" +runTutorialQuickstart="false" +runTutorialPartitionedPipe="false" +branchTutorials="develop" diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 06a63bce..7b9ef1e7 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -4,13 +4,13 @@ on: inputs: virtualEnvironment: description: 'Virtual Environment' - default: 'ubuntu-18.04' + default: 'ubuntu-22.04' required: true type: choice options: + - ubuntu-24.04 - ubuntu-22.04 - ubuntu-20.04 - - ubuntu-18.04 refAdapter: description: 'Ref (branch/tag/commit) of the OpenFOAM adapter to build' default: 'develop' @@ -35,11 +35,9 @@ on: - 9 - 8 - 7 - - 6 - - 5 versionpreCICE: description: 'Version of preCICE to build with' - default: 'v3.0.0' + default: 'v3.1.2' required: true runTutorialHeatedPlate: description: Run tutorial flow-over-heated-plate @@ -123,6 +121,7 @@ jobs: ./run.sh | tee fluid-openfoam.log 2>&1 & PIDfluid=$! cd ../solid-cpp + apt-get update && apt-get -y install cmake cmake . && make && ./run.sh | tee solid-cpp.log 2>&1 wait $PIDfluid fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b09fa65b..c11da3cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,26 @@ There are also a few additional workflows that can be triggered manually: Members of the repository can trigger these workflows in the "Actions" tab. +### Running the CI locally + +You can also run GitHub Actions locally with [act](https://github.com/nektos/act). + +List the workflows with `act --list`. To simulate a push event, run `act push`. Some workflows might work on GitHub Actions but not on act, in which case you could try running specific workflows with, for example, `act -j 'build'`. + +To trigger the custom build workflow: + +1. Switch to the directory `.github/workflows/` +2. Edit the inputs in the `build-custom.input` file. +3. [Generate a GitHub access token](https://github.com/settings/personal-access-tokens) and add it to a `.secrets` file with content `GITHUB_TOKEN=`. + This, as well as setting an `--artifact-server-path` to a local directory are needed for the `upload-artifact` action, even though build artifacts are only stored locally (see updates in a [related issue](https://github.com/nektos/act/issues/329)). +4. Start the build using: + + ```shell + act -W build-custom.yml --input-file build-custom.input --secret-file .secrets --artifact-server-path $PWD/.artifacts + ``` + +Find valid combinations of Ubuntu and OpenFOAM versions in the [OpenFOAM support page](https://precice.org/adapter-openfoam-support.html). + ## System tests For non-trivial pull requests, we also need to execute [system regression tests](https://precice.org/dev-docs-system-tests.html), diff --git a/changelog-entries/350.md b/changelog-entries/350.md new file mode 100644 index 00000000..b16222ba --- /dev/null +++ b/changelog-entries/350.md @@ -0,0 +1 @@ +- Documented running the CI locally, including hints on which OpenFOAM versions are available on which platforms. [#350](https://github.com/precice/openfoam-adapter/pull/350) \ No newline at end of file diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index a2e8c541..db41562c 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -47,6 +47,47 @@ We provide version-specific [release archives](https://github.com/precice/openfo Known not supported versions: OpenFOAM v1606+ or older, OpenFOAM 3 or older, foam-extend (any version). +## Availability of OpenFOAM packages + +We use the Ubuntu repositories of each OpenFOAM vendor for testing. Quick reference for maintainers: + +- OpenCFD / ESI (openfoam.com): + - [PPA repository](https://dl.openfoam.com/repos/deb/) + - More details on the [OpenFOAM documentation](https://develop.openfoam.com/Development/openfoam/-/wikis/precompiled). +- OpenFOAM Foundation (openfoam.org): + - [PPA repository](https://dl.openfoam.org/ubuntu/) + - More details on the [OpenFOAM documentation](https://dl.openfoam.org/). +- foam-extend: + - The project distributes pre-built Debian packages as release artifacts on [SourceForge](https://sourceforge.net/projects/foam-extend/files/). + +The following table gives an overview, including versions that we do not support, but which might be useful for future or historical reference. + +| version | 24.04 | 22.04 | 20.04 | 18.04 | +| --- | --- | --- | --- | --- | +| (com) v2412 | x | x | x | x | +| (com) v2406 | x | x | x | x | +| (com) v2312 | x | x | x | x | +| (com) v2306 | x | x | x | x | +| (com) v2212 | x | x | x | x | +| (com) v2206 | - | x | x | x | +| (com) v2112 | - | x | x | x | +| (com) v2106 | - | x | x | x | +| (com) v2012 | - | x | x | x | +| (com) v2006 | - | x | x | x | +| (com) v1912 | - | x | x | x | +| (com) older | - | - | - | - | +| (org) 12 | x | x | x | - | +| (org) 11 | x | x | x | - | +| (org) 10 | - | x | x | x | +| (org) 9 | - | x | x | x | +| (org) 8 | - | - | x | x | +| (org) 7 | - | - | x | x | +| (org) 6 | - | - | - | x | +| (org) 5 | - | - | - | - | +| (org) 4 | - | - | - | - | +| (ext) 5.0 | - | x | x | - | +| (ext) 4.1 | - | - | - | x | + ## Differences between OpenFOAM versions We take into account the following relevant differences between OpenFOAM versions, compared to the latest OpenCFD version. This list is important for maintainers of version-specific ports of the adapter. From c39547f1190b7cec3ad865e2a96c64015ce33566 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 20 Mar 2025 16:23:13 +0100 Subject: [PATCH 12/70] Add missing headers in Interface.C (#355) --- Interface.C | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Interface.C b/Interface.C index 55d59c53..6db28eb3 100644 --- a/Interface.C +++ b/Interface.C @@ -1,3 +1,6 @@ +#include +#include + #include "Interface.H" #include "Utilities.H" #include "faceTriangulation.H" From fc16b305bd43f455b9ce044acadd42c56b52fdf4 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Fri, 21 Mar 2025 14:08:26 +0100 Subject: [PATCH 13/70] Add missing overrides (#357) --- CHT/SinkTemperature.H | 4 ++-- CHT/Temperature.H | 4 ++-- FF/Alpha.H | 8 ++++---- FF/AlphaGradient.H | 8 ++++---- FF/Phi.H | 8 ++++---- FF/Pressure.H | 4 ++-- FF/PressureGradient.H | 4 ++-- FF/Temperature.H | 4 ++-- FF/TemperatureGradient.H | 8 ++++---- FF/Velocity.H | 4 ++-- FSI/DisplacementDelta.H | 2 +- 11 files changed, 29 insertions(+), 29 deletions(-) diff --git a/CHT/SinkTemperature.H b/CHT/SinkTemperature.H index ab15bce7..c22e5fc9 100644 --- a/CHT/SinkTemperature.H +++ b/CHT/SinkTemperature.H @@ -26,10 +26,10 @@ public: const std::string nameT); //- Write the sink temperature values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; //- Read the sink temperature values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) final; bool isLocationTypeSupported(const bool meshConnectivity) const final; diff --git a/CHT/Temperature.H b/CHT/Temperature.H index a374d64d..de1982e0 100644 --- a/CHT/Temperature.H +++ b/CHT/Temperature.H @@ -27,10 +27,10 @@ public: const std::string nameT); //- Write the temperature values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; //- Read the temperature values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) final; bool isLocationTypeSupported(const bool meshConnectivity) const final; diff --git a/FF/Alpha.H b/FF/Alpha.H index 7e5feb99..faa0c9bf 100644 --- a/FF/Alpha.H +++ b/FF/Alpha.H @@ -26,15 +26,15 @@ public: const std::string nameAlpha); //- Write the Alpha values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; //- Read the Alpha values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) final; - bool isLocationTypeSupported(const bool meshConnectivity) const override; + bool isLocationTypeSupported(const bool meshConnectivity) const final; //- Get the name of the current data field - std::string getDataName() const override; + std::string getDataName() const final; }; } diff --git a/FF/AlphaGradient.H b/FF/AlphaGradient.H index b538f009..04905b7d 100644 --- a/FF/AlphaGradient.H +++ b/FF/AlphaGradient.H @@ -25,15 +25,15 @@ public: const std::string nameAlpha); //- Write the Alpha gradient values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; //- Read the Alpha gradient values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) final; - bool isLocationTypeSupported(const bool meshConnectivity) const override; + bool isLocationTypeSupported(const bool meshConnectivity) const final; //- Get the name of the current data field - std::string getDataName() const override; + std::string getDataName() const final; }; } diff --git a/FF/Phi.H b/FF/Phi.H index 0b42c6ad..a89f6515 100644 --- a/FF/Phi.H +++ b/FF/Phi.H @@ -25,15 +25,15 @@ public: const std::string namePhi); //- Write the Phi values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; //- Read the Phi values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) final; - bool isLocationTypeSupported(const bool meshConnectivity) const override; + bool isLocationTypeSupported(const bool meshConnectivity) const final; //- Get the name of the current data field - std::string getDataName() const override; + std::string getDataName() const final; }; } diff --git a/FF/Pressure.H b/FF/Pressure.H index ecd06a02..67f82b8f 100644 --- a/FF/Pressure.H +++ b/FF/Pressure.H @@ -26,10 +26,10 @@ public: const std::string nameP); //- Write the pressure values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; //- Read the pressure values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) final; bool isLocationTypeSupported(const bool meshConnectivity) const final; diff --git a/FF/PressureGradient.H b/FF/PressureGradient.H index 47db220e..27b8b03f 100644 --- a/FF/PressureGradient.H +++ b/FF/PressureGradient.H @@ -25,10 +25,10 @@ public: const std::string nameP); //- Write the pressure gradient values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) override; //- Read the pressure gradient values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) override; bool isLocationTypeSupported(const bool meshConnectivity) const final; diff --git a/FF/Temperature.H b/FF/Temperature.H index da6daad9..3e6ef322 100644 --- a/FF/Temperature.H +++ b/FF/Temperature.H @@ -25,10 +25,10 @@ public: const std::string nameT); //- Write the temperature values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) override; //- Read the temperature values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) override; bool isLocationTypeSupported(const bool meshConnectivity) const override; diff --git a/FF/TemperatureGradient.H b/FF/TemperatureGradient.H index c7259a7b..58c2e4d0 100644 --- a/FF/TemperatureGradient.H +++ b/FF/TemperatureGradient.H @@ -25,15 +25,15 @@ public: const std::string nameT); //- Write the Temperature gradient values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; //- Read the Temperature gradient values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) final; - bool isLocationTypeSupported(const bool meshConnectivity) const override; + bool isLocationTypeSupported(const bool meshConnectivity) const final; //- Get the name of the current data field - std::string getDataName() const override; + std::string getDataName() const final; }; } diff --git a/FF/Velocity.H b/FF/Velocity.H index c6706821..a610ea56 100644 --- a/FF/Velocity.H +++ b/FF/Velocity.H @@ -32,10 +32,10 @@ public: bool fluxCorrection = false); //- Write the velocity values into the buffer - std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; //- Read the velocity values from the buffer - void read(double* buffer, const unsigned int dim); + void read(double* buffer, const unsigned int dim) final; bool isLocationTypeSupported(const bool meshConnectivity) const final; diff --git a/FSI/DisplacementDelta.H b/FSI/DisplacementDelta.H index 65066b77..b817618c 100644 --- a/FSI/DisplacementDelta.H +++ b/FSI/DisplacementDelta.H @@ -44,7 +44,7 @@ public: bool isLocationTypeSupported(const bool meshConnectivity) const final; //- Get the name of the current data field - std::string getDataName() const; + std::string getDataName() const final; //- We need to initialize the cell-based vector and the interpolation object // in case we want to use the faceCenter location for the coupling From af29bbb63b14a5229fea8bcfef3be61a1139f11e Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 25 Mar 2025 15:51:15 +0100 Subject: [PATCH 14/70] Update system tests workflow variables (#360) --- .github/workflows/system-tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/system-tests.yaml b/.github/workflows/system-tests.yaml index 90fa4709..c83e8817 100644 --- a/.github/workflows/system-tests.yaml +++ b/.github/workflows/system-tests.yaml @@ -25,5 +25,5 @@ jobs: with: suites: openfoam_adapter_pr build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.reftutorials }},PRECICE_REF:v3.1.1,OPENFOAM_EXECUTABLE:openfoam2312,OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} - systests_branch: develop - loglevel: "DEBUG" + system_tests_branch: develop + log_level: "DEBUG" From 93e1809c6e35ec41e2ef7e249026499aadd7ff65 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 27 Mar 2025 19:20:09 +0100 Subject: [PATCH 15/70] System tests workflow: Pass PR number and add job summary (#361) --- .github/workflows/system-tests.yaml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/system-tests.yaml b/.github/workflows/system-tests.yaml index c83e8817..8379c4cd 100644 --- a/.github/workflows/system-tests.yaml +++ b/.github/workflows/system-tests.yaml @@ -9,14 +9,30 @@ jobs: if: ${{ github.event.label.name == 'trigger-system-tests' }} runs-on: ubuntu-latest outputs: - reftutorials: ${{ steps.reftutorials.outputs.shorthash }} + ref-tutorials: ${{ steps.ref-tutorials.outputs.shorthash }} steps: - - id: reftutorials + - id: ref-tutorials uses: nmbgeek/github-action-get-latest-commit@main with: owner: precice repo: tutorials branch: develop + - id: report-refs + name: Report Git refs + run: | + printf 'Tutorials: ${{ steps.ref-tutorials.outputs.shorthash }}\n ${{ steps.ref-tutorials.outputs.description }}\n----------\n' + - id: summary + name: Prepare Markdown summary + run: | + echo "### OpenFOAM adapter" >> $GITHUB_STEP_SUMMARY + echo "Reference (pull request): \`${{ github.event.pull_request.head.sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "Pull request number: [${{ github.event.number }}](https://github.com/precice/openfoam-adapter/pull/${{ github.event.number }})" >> $GITHUB_STEP_SUMMARY + echo "### Tutorials" >> $GITHUB_STEP_SUMMARY + echo "Reference (develop): [\`${{ steps.ref-tutorials.outputs.shorthash }}\`](https://github.com/precice/tutorials/commit/${{ steps.ref-tutorials.outputs.shorthash }})" >> $GITHUB_STEP_SUMMARY + echo "Description:" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.ref-tutorials.outputs.description }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY run-system-tests: if: ${{ github.event.label.name == 'trigger-system-tests' }} @@ -24,6 +40,6 @@ jobs: uses: precice/tutorials/.github/workflows/run_testsuite_workflow.yml@develop with: suites: openfoam_adapter_pr - build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.reftutorials }},PRECICE_REF:v3.1.1,OPENFOAM_EXECUTABLE:openfoam2312,OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} + build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:v3.1.1,OPENFOAM_EXECUTABLE:openfoam2312,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} system_tests_branch: develop log_level: "DEBUG" From e9820bc7d06a43244d98fda7efc84e537cc70085 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Mon, 7 Apr 2025 13:07:44 +0200 Subject: [PATCH 16/70] Update preCICE reference version (#365) --- .github/workflows/build-custom.input | 2 +- .github/workflows/build-custom.yml | 2 +- .github/workflows/system-tests.yaml | 2 +- changelog-entries/365.md | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog-entries/365.md diff --git a/.github/workflows/build-custom.input b/.github/workflows/build-custom.input index 5b234156..a90b3277 100644 --- a/.github/workflows/build-custom.input +++ b/.github/workflows/build-custom.input @@ -1,7 +1,7 @@ virtualEnvironment="ubuntu-22.04" refAdapter="develop" versionOpenFOAM="2412" -versionpreCICE="v3.1.2" +versionpreCICE="v3.2.0" runTutorialHeatedPlate="false" runTutorialQuickstart="false" runTutorialPartitionedPipe="false" diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 7b9ef1e7..8e18a3b2 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -37,7 +37,7 @@ on: - 7 versionpreCICE: description: 'Version of preCICE to build with' - default: 'v3.1.2' + default: 'v3.2.0' required: true runTutorialHeatedPlate: description: Run tutorial flow-over-heated-plate diff --git a/.github/workflows/system-tests.yaml b/.github/workflows/system-tests.yaml index 8379c4cd..914f1d6e 100644 --- a/.github/workflows/system-tests.yaml +++ b/.github/workflows/system-tests.yaml @@ -40,6 +40,6 @@ jobs: uses: precice/tutorials/.github/workflows/run_testsuite_workflow.yml@develop with: suites: openfoam_adapter_pr - build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:v3.1.1,OPENFOAM_EXECUTABLE:openfoam2312,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} + build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:v3.2.0,OPENFOAM_EXECUTABLE:openfoam2312,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} system_tests_branch: develop log_level: "DEBUG" diff --git a/changelog-entries/365.md b/changelog-entries/365.md new file mode 100644 index 00000000..6cc4d9ef --- /dev/null +++ b/changelog-entries/365.md @@ -0,0 +1 @@ +- Changed the reference version of preCICE used by the CI workflows to v3.2.0. [#365](https://github.com/precice/openfoam-adapter/pull/365) \ No newline at end of file From 566dc12b4fda9d332eb6c4979124664a9529de6d Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Mon, 7 Apr 2025 13:20:20 +0200 Subject: [PATCH 17/70] Avoid ambiguous type conversion when accessing tmp (#359) --- .../coupledPressure/coupledPressureFvPatchField.C | 2 +- .../coupledVelocity/coupledVelocityFvPatchField.C | 2 +- FF/Velocity.C | 2 +- changelog-entries/358.md | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog-entries/358.md diff --git a/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C b/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C index 320a6ca2..bdec7b37 100644 --- a/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C +++ b/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C @@ -99,7 +99,7 @@ void Foam::coupledPressureFvPatchField::updateCoeffs() const scalarField& phip = phi->boundaryField()[this->patch().index()]; const Foam::volVectorField* U = &db().lookupObject(uName_); const vectorField& Up = U->boundaryField()[this->patch().index()]; - const vectorField n = this->patch().nf(); + const vectorField n = this->patch().nf().cref(); int t0 = this->patch().boundaryMesh().mesh().time().startTimeIndex(); int t = this->patch().boundaryMesh().mesh().time().timeIndex(); diff --git a/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.C b/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.C index 1546477c..b340d964 100644 --- a/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.C +++ b/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.C @@ -95,7 +95,7 @@ void Foam::coupledVelocityFvPatchField::updateCoeffs() } const Foam::surfaceScalarField* phi = &db().lookupObject(phiName_); const scalarField& phip = phi->boundaryField()[this->patch().index()]; - const vectorField n = this->patch().nf(); + const vectorField n = this->patch().nf().cref(); int t0 = this->patch().boundaryMesh().mesh().time().startTimeIndex(); int t = this->patch().boundaryMesh().mesh().time().timeIndex(); diff --git a/FF/Velocity.C b/FF/Velocity.C index f9ecfe61..18470d73 100644 --- a/FF/Velocity.C +++ b/FF/Velocity.C @@ -92,7 +92,7 @@ std::size_t preciceAdapter::FF::Velocity::write(double* buffer, bool meshConnect if (fluxCorrection_) { scalarField phip = phi_->boundaryFieldRef()[patchID]; - vectorField n = U_->boundaryField()[patchID].patch().nf(); + const vectorField n = U_->boundaryField()[patchID].patch().nf().cref(); const scalarField& magS = U_->boundaryFieldRef()[patchID].patch().magSf(); UPatch = UPatch - n * (n & U_->boundaryField()[patchID]) + n * phip / magS; } diff --git a/changelog-entries/358.md b/changelog-entries/358.md new file mode 100644 index 00000000..f5752695 --- /dev/null +++ b/changelog-entries/358.md @@ -0,0 +1 @@ +- Fixed an issue preventing compiling the adapter with the Intel compiler [#358](https://github.com/precice/openfoam-adapter/pull/358). \ No newline at end of file From 462c5105f202fbe05b60fe7bf56e05653905ded9 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Fri, 11 Apr 2025 18:04:19 +0200 Subject: [PATCH 18/70] build.yml: Unpin runner version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f67ee3a..f12d82a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ on: - 'README.md' jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest container: precice/precice:nightly steps: - name: Check out repository From 5a293c44532bc4bdd293762c868577971b4b7576 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 31 Jul 2025 10:58:08 +0200 Subject: [PATCH 19/70] Add volume coupling citation details (#371) --- docs/README.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index ae64d8fc..4b37e51f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -66,21 +66,32 @@ The fluid-structure interaction module was developed in close collaboration betw The fluid-fluid coupling module was added by Gerasimos Chourdakis, in the context of his dissertation. [#67](https://github.com/precice/openfoam-adapter/pull/67). Further contributions in this direction by Markus Mühlhäußer ([master's thesis](https://mediatum.ub.tum.de/node?id=1696254&change_language=en) [5], [related publication](https://mediatum.ub.tum.de/node?id=1732401&change_language=en) [6]). +The volume coupling functionality was contributed by Tina Vladimirova, in the context of her [interdisciplinary project](https://mediatum.ub.tum.de/1734883) [7], based on previous work by various community contributors. + The adapter is [easily extensible](https://precice.org/adapter-openfoam-extend.html). ### Related literature -[1] Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25. [DOI: 10.51560/ofj.v3.88](https://doi.org/10.51560/ofj.v3.88) +[1] Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25.
+[DOI: 10.51560/ofj.v3.88](https://doi.org/10.51560/ofj.v3.88) + +[2] Gerasimos Chourdakis. A general OpenFOAM adapter for the coupling library preCICE. Master's thesis, Department of Informatics, Technical University of Munich, 2017.
+URL: https://mediatum.ub.tum.de/1462269 -[2] Gerasimos Chourdakis. A general OpenFOAM adapter for the coupling library preCICE. Master's thesis, Department of Informatics, Technical University of Munich, 2017. +[3] Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016.
+URL: https://www5.in.tum.de/pub/Cheung2016_Thesis.pdf -[3] Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016. +[4] Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019.
+URL: http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123 -[4] Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019. +[5] Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022.
+URL: https://mediatum.ub.tum.de/node?id=1696254&change_language=en -[5] Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022. +[6] Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023.
+[DOI: 10.23967/c.coupled.2023.014](https://doi.org/10.23967/c.coupled.2023.014) -[6] Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023. [DOI: 10.23967/c.coupled.2023.014](https://doi.org/10.23967/c.coupled.2023.014) +[7] Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023.
+URL: https://mediatum.ub.tum.de/1734883 {% disclaimer %} This offering is not approved or endorsed by OpenCFD Limited, producer and distributor of the OpenFOAM software via www.openfoam.com, and owner of the OPENFOAM® and OpenCFD® trade marks. From 0d749dd94b79d961bcda73ecc56a9fcd3b7ba822 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 31 Jul 2025 22:03:47 +0200 Subject: [PATCH 20/70] Simplify error handling (remove error-deferred workaround) (#364) --- Adapter.C | 625 +++++++++--------- Adapter.H | 5 +- CHT/KappaEffective.C | 7 +- .../coupledPressureFvPatchField.C | 7 +- FSI/DisplacementDelta.C | 4 +- FSI/ForceBase.C | 22 +- Interface.C | 9 +- Utilities.C | 20 +- changelog-entries/364.md | 1 + docs/config.md | 3 + 10 files changed, 313 insertions(+), 390 deletions(-) create mode 100644 changelog-entries/364.md diff --git a/Adapter.C b/Adapter.C index 7146b1c9..f6beccd3 100644 --- a/Adapter.C +++ b/Adapter.C @@ -16,412 +16,377 @@ preciceAdapter::Adapter::Adapter(const Time& runTime, const fvMesh& mesh) return; } -bool preciceAdapter::Adapter::configFileRead() +void preciceAdapter::Adapter::configFileRead() { - // We need a try-catch here, as if reading preciceDict fails, - // the respective exception will be reduced to a warning. - // See also comment in preciceAdapter::Adapter::configure(). - try - { - SETUP_TIMER(); - adapterInfo("Reading preciceDict...", "info"); - - // TODO: static is just a quick workaround to be able - // to find the dictionary also out of scope (e.g. in KappaEffective). - // We need a better solution. - static IOdictionary preciceDict( - IOobject( - "preciceDict", - runTime_.system(), - mesh_, - IOobject::MUST_READ_IF_MODIFIED, - IOobject::NO_WRITE)); - - // Read and display the preCICE configuration file name - preciceConfigFilename_ = preciceDict.get("preciceConfig"); - DEBUG(adapterInfo(" precice-config-file : " + preciceConfigFilename_)); - - // Read and display the participant name - participantName_ = preciceDict.get("participant"); - DEBUG(adapterInfo(" participant name : " + participantName_)); - - // Read and display the list of modules - DEBUG(adapterInfo(" modules requested : ")); - auto modules_ = preciceDict.get("modules"); - for (const auto& module : modules_) + SETUP_TIMER(); + adapterInfo("Reading preciceDict...", "info"); + + // TODO: static is just a quick workaround to be able + // to find the dictionary also out of scope (e.g. in KappaEffective). + // We need a better solution. + static IOdictionary preciceDict( + IOobject( + "preciceDict", + runTime_.system(), + mesh_, + IOobject::MUST_READ_IF_MODIFIED, + IOobject::NO_WRITE)); + + // Read and display the preCICE configuration file name + preciceConfigFilename_ = preciceDict.get("preciceConfig"); + DEBUG(adapterInfo(" precice-config-file : " + preciceConfigFilename_)); + + // Read and display the participant name + participantName_ = preciceDict.get("participant"); + DEBUG(adapterInfo(" participant name : " + participantName_)); + + // Read and display the list of modules + DEBUG(adapterInfo(" modules requested : ")); + auto modules_ = preciceDict.get("modules"); + for (const auto& module : modules_) + { + DEBUG(adapterInfo(" - " + module + "\n")); + + // Set the modules switches + if (module == "CHT") { - DEBUG(adapterInfo(" - " + module + "\n")); - - // Set the modules switches - if (module == "CHT") - { - CHTenabled_ = true; - } - - if (module == "FSI") - { - FSIenabled_ = true; - } - - if (module == "FF") - { - FFenabled_ = true; - } + CHTenabled_ = true; } - // Every interface is a subdictionary of "interfaces", - // each with an arbitrary name. Read all of them and create - // a list (here: pointer) of dictionaries. - const auto* interfaceDictPtr = preciceDict.findDict("interfaces"); - DEBUG(adapterInfo(" interfaces : ")); + if (module == "FSI") + { + FSIenabled_ = true; + } - // Check if we found any interfaces - // and get the details of each interface - if (!interfaceDictPtr) + if (module == "FF") { - adapterInfo(" Empty list of interfaces", "warning"); - return false; + FFenabled_ = true; } - else + } + + // Every interface is a subdictionary of "interfaces", + // each with an arbitrary name. Read all of them and create + // a list (here: pointer) of dictionaries. + const auto* interfaceDictPtr = preciceDict.findDict("interfaces"); + DEBUG(adapterInfo(" interfaces : ")); + + // Check if we found any interfaces + // and get the details of each interface + if (!interfaceDictPtr) + { + adapterInfo(" Empty list of interfaces", "error"); + return; + } + else + { + for (const entry& interfaceDictEntry : *interfaceDictPtr) { - for (const entry& interfaceDictEntry : *interfaceDictPtr) + if (interfaceDictEntry.isDict()) { - if (interfaceDictEntry.isDict()) + const dictionary& interfaceDict = interfaceDictEntry.dict(); + struct InterfaceConfig interfaceConfig; + + interfaceConfig.meshName = interfaceDict.get("mesh"); + DEBUG(adapterInfo(" - mesh : " + interfaceConfig.meshName)); + + // By default, assume "faceCenters" as locationsType + interfaceConfig.locationsType = interfaceDict.lookupOrDefault("locations", "faceCenters"); + DEBUG(adapterInfo(" locations : " + interfaceConfig.locationsType)); + + // By default, assume that no mesh connectivity is required (i.e. no nearest-projection mapping) + interfaceConfig.meshConnectivity = interfaceDict.lookupOrDefault("connectivity", false); + // Mesh connectivity only makes sense in case of faceNodes, check and raise a warning otherwise + if (interfaceConfig.meshConnectivity && (interfaceConfig.locationsType == "faceCenters" || interfaceConfig.locationsType == "volumeCenters" || interfaceConfig.locationsType == "volumeCentres")) { - const dictionary& interfaceDict = interfaceDictEntry.dict(); - struct InterfaceConfig interfaceConfig; - - interfaceConfig.meshName = interfaceDict.get("mesh"); - DEBUG(adapterInfo(" - mesh : " + interfaceConfig.meshName)); - - // By default, assume "faceCenters" as locationsType - interfaceConfig.locationsType = interfaceDict.lookupOrDefault("locations", "faceCenters"); - DEBUG(adapterInfo(" locations : " + interfaceConfig.locationsType)); - - // By default, assume that no mesh connectivity is required (i.e. no nearest-projection mapping) - interfaceConfig.meshConnectivity = interfaceDict.lookupOrDefault("connectivity", false); - // Mesh connectivity only makes sense in case of faceNodes, check and raise a warning otherwise - if (interfaceConfig.meshConnectivity && (interfaceConfig.locationsType == "faceCenters" || interfaceConfig.locationsType == "volumeCenters" || interfaceConfig.locationsType == "volumeCentres")) - { - DEBUG(adapterInfo("Mesh connectivity is not supported for faceCenters or volumeCenters. \n" - "Please configure the desired interface with the locationsType faceNodes. \n" - "Have a look in the adapter documentation for detailed information.", - "warning")); - return false; - } - DEBUG(adapterInfo(" connectivity : " + std::to_string(interfaceConfig.meshConnectivity))); - - DEBUG(adapterInfo(" patches : ")); - auto patches = interfaceDict.get("patches"); - for (auto patch : patches) - { - interfaceConfig.patchNames.push_back(patch); - DEBUG(adapterInfo(" - " + patch)); - } - - DEBUG(adapterInfo(" cellSets : ")); - auto cellSets = interfaceDict.lookupOrDefault("cellSets", wordList()); - - for (auto cellSet : cellSets) - { - interfaceConfig.cellSetNames.push_back(cellSet); - DEBUG(adapterInfo(" - " + cellSet)); - } - - if (!interfaceConfig.cellSetNames.empty() && !(interfaceConfig.locationsType == "volumeCenters" || interfaceConfig.locationsType == "volumeCentres")) - { - adapterInfo("Cell sets are not supported for locationType != volumeCenters. \n" - "Please configure the desired interface with the locationsType volumeCenters. \n" - "Have a look in the adapter documentation for detailed information.", - "warning"); - return false; - } - - DEBUG(adapterInfo(" writeData : ")); - auto writeData = interfaceDict.lookupOrDefault("writeData", wordList()); - for (auto writeDatum : writeData) - { - interfaceConfig.writeData.push_back(writeDatum); - DEBUG(adapterInfo(" - " + writeDatum)); - } - - DEBUG(adapterInfo(" readData : ")); - auto readData = interfaceDict.lookupOrDefault("readData", wordList()); - for (auto readDatum : readData) - { - interfaceConfig.readData.push_back(readDatum); - DEBUG(adapterInfo(" - " + readDatum)); - } - interfacesConfig_.push_back(interfaceConfig); + DEBUG(adapterInfo("Mesh connectivity is not supported for faceCenters or volumeCenters. \n" + "Please configure the desired interface with the locationsType faceNodes. \n" + "Have a look in the adapter documentation for detailed information.", + "error")); + return; } - } - } + DEBUG(adapterInfo(" connectivity : " + std::to_string(interfaceConfig.meshConnectivity))); - // NOTE: set the switch for your new module here + DEBUG(adapterInfo(" patches : ")); + auto patches = interfaceDict.get("patches"); + for (auto patch : patches) + { + interfaceConfig.patchNames.push_back(patch); + DEBUG(adapterInfo(" - " + patch)); + } - // If the CHT module is enabled, create it, read the - // CHT-specific options and configure it. - if (CHTenabled_) - { - CHT_ = new CHT::ConjugateHeatTransfer(mesh_); - if (!CHT_->configure(preciceDict)) - { - return false; - } - } + DEBUG(adapterInfo(" cellSets : ")); + auto cellSets = interfaceDict.lookupOrDefault("cellSets", wordList()); - // If the FSI module is enabled, create it, read the - // FSI-specific options and configure it. - if (FSIenabled_) - { - // Check for unsupported FSI with meshConnectivity - for (uint i = 0; i < interfacesConfig_.size(); i++) - { - if (interfacesConfig_.at(i).meshConnectivity == true) + for (auto cellSet : cellSets) { - adapterInfo( - "You have requested mesh connectivity (most probably for nearest-projection mapping) " - "and you have enabled the FSI module. " - "Mapping with connectivity information is not implemented for FSI, only for CHT-related fields. " - "warning"); - return false; + interfaceConfig.cellSetNames.push_back(cellSet); + DEBUG(adapterInfo(" - " + cellSet)); } - } - FSI_ = new FSI::FluidStructureInteraction(mesh_, runTime_); - if (!FSI_->configure(preciceDict)) - { - return false; + if (!interfaceConfig.cellSetNames.empty() && !(interfaceConfig.locationsType == "volumeCenters" || interfaceConfig.locationsType == "volumeCentres")) + { + adapterInfo("Cell sets are not supported for locationType != volumeCenters. \n" + "Please configure the desired interface with the locationsType volumeCenters. \n" + "Have a look in the adapter documentation for detailed information.", + "error"); + return; + } + + DEBUG(adapterInfo(" writeData : ")); + auto writeData = interfaceDict.lookupOrDefault("writeData", wordList()); + for (auto writeDatum : writeData) + { + interfaceConfig.writeData.push_back(writeDatum); + DEBUG(adapterInfo(" - " + writeDatum)); + } + + DEBUG(adapterInfo(" readData : ")); + auto readData = interfaceDict.lookupOrDefault("readData", wordList()); + for (auto readDatum : readData) + { + interfaceConfig.readData.push_back(readDatum); + DEBUG(adapterInfo(" - " + readDatum)); + } + interfacesConfig_.push_back(interfaceConfig); } } + } + + // NOTE: set the switch for your new module here - if (FFenabled_) + // If the CHT module is enabled, create it, read the + // CHT-specific options and configure it. + if (CHTenabled_) + { + CHT_ = new CHT::ConjugateHeatTransfer(mesh_); + if (!CHT_->configure(preciceDict)) { - FF_ = new FF::FluidFluid(mesh_); - if (!FF_->configure(preciceDict)) + adapterInfo("There was an error while configuring the CHT module", + "error"); + return; + } + } + + // If the FSI module is enabled, create it, read the + // FSI-specific options and configure it. + if (FSIenabled_) + { + // Check for unsupported FSI with meshConnectivity + for (uint i = 0; i < interfacesConfig_.size(); i++) + { + if (interfacesConfig_.at(i).meshConnectivity == true) { - return false; + adapterInfo( + "You have requested mesh connectivity (most probably for nearest-projection mapping) " + "and you have enabled the FSI module. " + "Mapping with connectivity information is not implemented for FSI, only for CHT-related fields. " + "error"); + return; } } - // NOTE: Create your module and read any options specific to it here + FSI_ = new FSI::FluidStructureInteraction(mesh_, runTime_); + if (!FSI_->configure(preciceDict)) + { + adapterInfo("There was an error while configuring the FSI module", + "error"); + return; + } + } - if (!CHTenabled_ && !FSIenabled_ && !FFenabled_) // NOTE: Add your new switch here + if (FFenabled_) + { + FF_ = new FF::FluidFluid(mesh_); + if (!FF_->configure(preciceDict)) { - adapterInfo("No module is enabled.", "error-deferred"); - return false; + adapterInfo("There was an error while configuring the FF module", + "error"); + return; } + } - // TODO: Loading modules should be implemented in more general way, - // in order to avoid code duplication. See issue #16 on GitHub. + // NOTE: Create your module and read any options specific to it here - ACCUMULATE_TIMER(timeInConfigRead_); - } - catch (const Foam::error& e) + if (!CHTenabled_ && !FSIenabled_ && !FFenabled_) // NOTE: Add your new switch here { - adapterInfo(e.message(), "error-deferred"); - return false; + adapterInfo("No module is enabled.", "error"); + return; } - return true; + // TODO: Loading modules should be implemented in more general way, + // in order to avoid code duplication. See issue #16 on GitHub. + + ACCUMULATE_TIMER(timeInConfigRead_); + + return; } void preciceAdapter::Adapter::configure() { // Read the adapter's configuration file - if (!configFileRead()) - { - // This method is called from the functionObject's read() method, - // which is called by the Foam::functionObjectList::read() method. - // All the exceptions triggered in this method are caught as - // warnings and the simulation continues simply without the - // functionObject. However, we want the simulation to exit with an - // error in case something is wrong. We store the information that - // there was an error and it will be handled by the first call to - // the functionObject's execute(), which can throw errors normally. - errorsInConfigure = true; + configFileRead(); - return; - } + // Check the timestep type (fixed vs adjustable) + DEBUG(adapterInfo("Checking the timestep type (fixed vs adjustable)...")); + adjustableTimestep_ = runTime_.controlDict().lookupOrDefault("adjustTimeStep", false); - try + if (adjustableTimestep_) { - // Check the timestep type (fixed vs adjustable) - DEBUG(adapterInfo("Checking the timestep type (fixed vs adjustable)...")); - adjustableTimestep_ = runTime_.controlDict().lookupOrDefault("adjustTimeStep", false); + DEBUG(adapterInfo(" Timestep type: adjustable.")); + } + else + { + DEBUG(adapterInfo(" Timestep type: fixed.")); + } - if (adjustableTimestep_) - { - DEBUG(adapterInfo(" Timestep type: adjustable.")); - } - else - { - DEBUG(adapterInfo(" Timestep type: fixed.")); - } + // Construct preCICE + SETUP_TIMER(); + DEBUG(adapterInfo("Creating the preCICE solver interface...")); + DEBUG(adapterInfo(" Number of processes: " + std::to_string(Pstream::nProcs()))); + DEBUG(adapterInfo(" MPI rank: " + std::to_string(Pstream::myProcNo()))); + precice_ = new precice::Participant(participantName_, preciceConfigFilename_, Pstream::myProcNo(), Pstream::nProcs()); + DEBUG(adapterInfo(" preCICE solver interface was created.")); - // Construct preCICE - SETUP_TIMER(); - DEBUG(adapterInfo("Creating the preCICE solver interface...")); - DEBUG(adapterInfo(" Number of processes: " + std::to_string(Pstream::nProcs()))); - DEBUG(adapterInfo(" MPI rank: " + std::to_string(Pstream::myProcNo()))); - precice_ = new precice::Participant(participantName_, preciceConfigFilename_, Pstream::myProcNo(), Pstream::nProcs()); - DEBUG(adapterInfo(" preCICE solver interface was created.")); + ACCUMULATE_TIMER(timeInPreciceConstruct_); - ACCUMULATE_TIMER(timeInPreciceConstruct_); + // Create interfaces + REUSE_TIMER(); + DEBUG(adapterInfo("Creating interfaces...")); + for (uint i = 0; i < interfacesConfig_.size(); i++) + { + std::string namePointDisplacement = FSIenabled_ ? FSI_->getPointDisplacementFieldName() : "default"; + std::string nameCellDisplacement = FSIenabled_ ? FSI_->getCellDisplacementFieldName() : "default"; + bool restartFromDeformed = FSIenabled_ ? FSI_->isRestartingFromDeformed() : false; - // Create interfaces - REUSE_TIMER(); - DEBUG(adapterInfo("Creating interfaces...")); - for (uint i = 0; i < interfacesConfig_.size(); i++) + Interface* interface = new Interface(*precice_, mesh_, interfacesConfig_.at(i).meshName, interfacesConfig_.at(i).locationsType, interfacesConfig_.at(i).patchNames, interfacesConfig_.at(i).cellSetNames, interfacesConfig_.at(i).meshConnectivity, restartFromDeformed, namePointDisplacement, nameCellDisplacement); + interfaces_.push_back(interface); + DEBUG(adapterInfo("Interface created on mesh " + interfacesConfig_.at(i).meshName)); + + DEBUG(adapterInfo("Adding coupling data writers...")); + for (uint j = 0; j < interfacesConfig_.at(i).writeData.size(); j++) { - std::string namePointDisplacement = FSIenabled_ ? FSI_->getPointDisplacementFieldName() : "default"; - std::string nameCellDisplacement = FSIenabled_ ? FSI_->getCellDisplacementFieldName() : "default"; - bool restartFromDeformed = FSIenabled_ ? FSI_->isRestartingFromDeformed() : false; + std::string dataName = interfacesConfig_.at(i).writeData.at(j); - Interface* interface = new Interface(*precice_, mesh_, interfacesConfig_.at(i).meshName, interfacesConfig_.at(i).locationsType, interfacesConfig_.at(i).patchNames, interfacesConfig_.at(i).cellSetNames, interfacesConfig_.at(i).meshConnectivity, restartFromDeformed, namePointDisplacement, nameCellDisplacement); - interfaces_.push_back(interface); - DEBUG(adapterInfo("Interface created on mesh " + interfacesConfig_.at(i).meshName)); + unsigned int inModules = 0; - DEBUG(adapterInfo("Adding coupling data writers...")); - for (uint j = 0; j < interfacesConfig_.at(i).writeData.size(); j++) + // Add CHT-related coupling data writers + if (CHTenabled_ && CHT_->addWriters(dataName, interface)) { - std::string dataName = interfacesConfig_.at(i).writeData.at(j); - - unsigned int inModules = 0; - - // Add CHT-related coupling data writers - if (CHTenabled_ && CHT_->addWriters(dataName, interface)) - { - inModules++; - } - - // Add FSI-related coupling data writers - if (FSIenabled_ && FSI_->addWriters(dataName, interface)) - { - inModules++; - } - - // Add FF-related coupling data writers - if (FFenabled_ && FF_->addWriters(dataName, interface)) - { - inModules++; - } + inModules++; + } - if (inModules == 0) - { - adapterInfo("I don't know how to write \"" + dataName - + "\". Maybe this is a typo or maybe you need to enable some adapter module?", - "error-deferred"); - } - else if (inModules > 1) - { - adapterInfo("It looks like more than one modules can write \"" + dataName - + "\" and I don't know how to choose. Try disabling one of the modules.", - "error-deferred"); - } + // Add FSI-related coupling data writers + if (FSIenabled_ && FSI_->addWriters(dataName, interface)) + { + inModules++; + } - // NOTE: Add any coupling data writers for your module here. - } // end add coupling data writers + // Add FF-related coupling data writers + if (FFenabled_ && FF_->addWriters(dataName, interface)) + { + inModules++; + } - DEBUG(adapterInfo("Adding coupling data readers...")); - for (uint j = 0; j < interfacesConfig_.at(i).readData.size(); j++) + if (inModules == 0) { - std::string dataName = interfacesConfig_.at(i).readData.at(j); + adapterInfo("I don't know how to write \"" + dataName + + "\". Maybe this is a typo or maybe you need to enable some adapter module?", + "error"); + } + else if (inModules > 1) + { + adapterInfo("It looks like more than one modules can write \"" + dataName + + "\" and I don't know how to choose. Try disabling one of the modules.", + "error"); + } - unsigned int inModules = 0; + // NOTE: Add any coupling data writers for your module here. + } // end add coupling data writers - // Add CHT-related coupling data readers - if (CHTenabled_ && CHT_->addReaders(dataName, interface)) inModules++; + DEBUG(adapterInfo("Adding coupling data readers...")); + for (uint j = 0; j < interfacesConfig_.at(i).readData.size(); j++) + { + std::string dataName = interfacesConfig_.at(i).readData.at(j); - // Add FSI-related coupling data readers - if (FSIenabled_ && FSI_->addReaders(dataName, interface)) inModules++; + unsigned int inModules = 0; - // Add FF-related coupling data readers - if (FFenabled_ && FF_->addReaders(dataName, interface)) inModules++; + // Add CHT-related coupling data readers + if (CHTenabled_ && CHT_->addReaders(dataName, interface)) inModules++; - if (inModules == 0) - { - adapterInfo("I don't know how to read \"" + dataName - + "\". Maybe this is a typo or maybe you need to enable some adapter module?", - "error-deferred"); - } - else if (inModules > 1) - { - adapterInfo("It looks like more than one modules can read \"" + dataName - + "\" and I don't know how to choose. Try disabling one of the modules.", - "error-deferred"); - } + // Add FSI-related coupling data readers + if (FSIenabled_ && FSI_->addReaders(dataName, interface)) inModules++; - // NOTE: Add any coupling data readers for your module here. - } // end add coupling data readers + // Add FF-related coupling data readers + if (FFenabled_ && FF_->addReaders(dataName, interface)) inModules++; - // Create the interface's data buffer - interface->createBuffer(); - } - ACCUMULATE_TIMER(timeInMeshSetup_); + if (inModules == 0) + { + adapterInfo("I don't know how to read \"" + dataName + + "\". Maybe this is a typo or maybe you need to enable some adapter module?", + "error"); + } + else if (inModules > 1) + { + adapterInfo("It looks like more than one modules can read \"" + dataName + + "\" and I don't know how to choose. Try disabling one of the modules.", + "error"); + } - // Initialize preCICE and exchange the first coupling data - initialize(); + // NOTE: Add any coupling data readers for your module here. + } // end add coupling data readers - // If checkpointing is required, specify the checkpointed fields - // and write the first checkpoint - if (requiresWritingCheckpoint()) - { - checkpointing_ = true; + // Create the interface's data buffer + interface->createBuffer(); + } + ACCUMULATE_TIMER(timeInMeshSetup_); - // Setup the checkpointing (find and add fields to checkpoint) - setupCheckpointing(); + // Initialize preCICE and exchange the first coupling data + initialize(); - // Write checkpoint (for the first iteration) - writeCheckpoint(); - } + // If checkpointing is required, specify the checkpointed fields + // and write the first checkpoint + if (requiresWritingCheckpoint()) + { + checkpointing_ = true; - // Adjust the timestep for the first iteration, if it is fixed - if (!adjustableTimestep_) - { - adjustSolverTimeStepAndReadData(); - } + // Setup the checkpointing (find and add fields to checkpoint) + setupCheckpointing(); - // If the solver tries to end before the coupling is complete, - // e.g. because the solver's endTime was smaller or (in implicit - // coupling) equal with the max-time specified in preCICE, - // problems may occur near the end of the simulation, - // as the function object may be called only once near the end. - // See the implementation of Foam::Time::run() for more details. - // To prevent this, we set the solver's endTime to "infinity" - // and let only preCICE control the end of the simulation. - // This has the side-effect of not triggering the end() method - // in any function object normally. Therefore, we trigger it - // when preCICE dictates to stop the coupling. - adapterInfo( - "Setting the solver's endTime to infinity to prevent early exits. " - "Only preCICE will control the simulation's endTime. " - "Any functionObject's end() method will be triggered by the adapter. " - "You may disable this behavior in the adapter's configuration.", - "info"); - const_cast(runTime_).setEndTime(GREAT); + // Write checkpoint (for the first iteration) + writeCheckpoint(); } - catch (const Foam::error& e) + + // Adjust the timestep for the first iteration, if it is fixed + if (!adjustableTimestep_) { - adapterInfo(e.message(), "error-deferred"); - errorsInConfigure = true; + adjustSolverTimeStepAndReadData(); } + // If the solver tries to end before the coupling is complete, + // e.g. because the solver's endTime was smaller or (in implicit + // coupling) equal with the max-time specified in preCICE, + // problems may occur near the end of the simulation, + // as the function object may be called only once near the end. + // See the implementation of Foam::Time::run() for more details. + // To prevent this, we set the solver's endTime to "infinity" + // and let only preCICE control the end of the simulation. + // This has the side-effect of not triggering the end() method + // in any function object normally. Therefore, we trigger it + // when preCICE dictates to stop the coupling. + adapterInfo( + "Setting the solver's endTime to infinity to prevent early exits. " + "Only preCICE will control the simulation's endTime. " + "Any functionObject's end() method will be triggered by the adapter. " + "You may disable this behavior in the adapter's configuration.", + "info"); + const_cast(runTime_).setEndTime(GREAT); + return; } void preciceAdapter::Adapter::execute() { - if (errorsInConfigure) - { - // Handle any errors during configure(). - // See the comments in configure() for details. - adapterInfo( - "There was a problem while configuring the adapter. " - "See the log for details.", - "error"); - } // The solver has already solved the equations for this timestep. // Now call the adapter's methods to perform the coupling. diff --git a/Adapter.H b/Adapter.H index b82efa20..18c7ce35 100644 --- a/Adapter.H +++ b/Adapter.H @@ -73,9 +73,6 @@ private: // Configuration parameters used in the Adapter - //- Remember if there were errors in the read() method - bool errorsInConfigure = false; - //- preCICE configuration file name Foam::word preciceConfigFilename_; @@ -245,7 +242,7 @@ private: // Configuration //- Read the adapter's configuration file - bool configFileRead(); + void configFileRead(); //- Check the adapter's configuration file bool configFileCheck(const std::string adapterConfigFileName); diff --git a/CHT/KappaEffective.C b/CHT/KappaEffective.C index 4ec5963a..3143b649 100644 --- a/CHT/KappaEffective.C +++ b/CHT/KappaEffective.C @@ -126,12 +126,7 @@ void preciceAdapter::CHT::KappaEff_Incompressible::extract(uint patchID, bool me } else { - WarningInFunction - << "The object alphat does not exist. " - << "An incompressible solver should create it explicitly " - << "(e.g. in the createFields.H)." - << "Assuming only the laminar part of the thermal diffusivity." - << nl; + adapterInfo("The object alphat does not exist. An incompressible solver should create it explicitly (e.g. in the createFields.H). Assuming only the laminar part of the thermal diffusivity.", "warning"); alphaEff = nu / Pr_.value(); } diff --git a/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C b/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C index bdec7b37..9b3a7665 100644 --- a/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C +++ b/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C @@ -50,12 +50,7 @@ Foam::coupledPressureFvPatchField::coupledPressureFvPatchField( { if (notNull(iF) && mapper.hasUnmapped()) { - WarningInFunction - << "On field " << iF.name() << " patch " << p.name() - << " patchField " << this->type() - << " : mapper does not map all values." << nl - << " To avoid this warning fully specify the mapping in derived" - << " patch fields." << endl; + adapterInfo("On field " + iF.name() + " patch " + p.name() + " patchField " + this->type() + " : mapper does not map all values. To avoid this warning, fully specify the mapping in derived patch fields.", "warning"); } } diff --git a/FSI/DisplacementDelta.C b/FSI/DisplacementDelta.C index a4bdd730..b1629de0 100644 --- a/FSI/DisplacementDelta.C +++ b/FSI/DisplacementDelta.C @@ -41,9 +41,7 @@ std::size_t preciceAdapter::FSI::DisplacementDelta::write(double* buffer, bool m * the outer for the locations and the inner for the dimensions. * See the preCICE writeBlockVectorData() implementation. */ - FatalErrorInFunction - << "Writing displacementDeltas is not supported." - << exit(FatalError); + adapterInfo("Writing displacementDelta is not supported.", "error"); return 0; } diff --git a/FSI/ForceBase.C b/FSI/ForceBase.C index 48297cdf..d46791d7 100644 --- a/FSI/ForceBase.C +++ b/FSI/ForceBase.C @@ -14,10 +14,7 @@ preciceAdapter::FSI::ForceBase::ForceBase( && solverType_.compare("compressible") != 0 && solverType_.compare("solid") != 0) { - FatalErrorInFunction - << "Force based calculations only support " - << "compressible, incompressible, or solid solver types." - << exit(FatalError); + adapterInfo("Force based calculations only support compressible, incompressible, or solid solver types.", "error"); } dataType_ = vector; @@ -82,9 +79,7 @@ Foam::tmp preciceAdapter::FSI::ForceBase::rho() const } else { - FatalErrorInFunction - << "Did not find the correct rho." - << exit(FatalError); + adapterInfo("Something went wrong while looking for a rho object for the force calculation.", "error"); return volScalarField::null(); } @@ -122,9 +117,7 @@ Foam::tmp preciceAdapter::FSI::ForceBase::mu() const } else { - FatalErrorInFunction - << "Did not find the correct mu." - << exit(FatalError); + adapterInfo("Something went wrong while looking for a mu object for the force calculation.", "error"); return volScalarField::null(); } @@ -169,10 +162,7 @@ std::size_t preciceAdapter::FSI::ForceBase::writeToBuffer(double* buffer, } else { - FatalErrorInFunction - << "Forces calculation does only support " - << "compressible or incompressible solver type." - << exit(FatalError); + adapterInfo("Calculating forces is only supported for solverType compressible or incompressible.", "error"); } // Viscous forces @@ -198,7 +188,5 @@ void preciceAdapter::FSI::ForceBase::readFromBuffer(double* buffer) const * the outer for the locations and the inner for the dimensions. * See the preCICE readBlockVectorData() implementation. */ - FatalErrorInFunction - << "Reading forces is not supported." - << exit(FatalError); + adapterInfo("Reading forces is not supported.", "error"); } diff --git a/Interface.C b/Interface.C index 6db28eb3..00dfe3f0 100644 --- a/Interface.C +++ b/Interface.C @@ -67,11 +67,10 @@ preciceAdapter::Interface::Interface( // Throw an error if the patch was not found if (patchID == -1) { - FatalErrorInFunction - << "ERROR: Patch '" - << patchNames.at(j) - << "' does not exist." - << exit(FatalError); + adapterInfo("Patch \"" + + patchNames.at(j) + "\" does not exist and therefore cannot be used as a coupling interface for mesh \"" + + meshName + "\". Check the system/preciceDict.", + "error"); } // Add the patch in the list diff --git a/Utilities.C b/Utilities.C index 2ed0af54..b1533d6d 100644 --- a/Utilities.C +++ b/Utilities.C @@ -29,7 +29,7 @@ void adapterInfo(const std::string message, const std::string level) // and exit the functionObject. // It will also exit the simulation, unless it // is called inside the functionObject's read(). - FatalErrorInFunction + FatalError << "\033[31m" // red color << "Error in the preCICE adapter: " << "\033[0m" // restore color @@ -38,24 +38,6 @@ void adapterInfo(const std::string message, const std::string level) << nl << exit(FatalError); } - else if (level.compare("error-deferred") == 0) - { - // Produce an warning message with red header. - // OpenFOAM degrades errors inside read() - // to warnings, stops the function object, but does - // not exit. We throw a warning which is described - // as an error, so that OpenFOAM does not exit, - // but the user still sees that this is the actual - // problem. We catch these errors and exit later. - WarningInFunction - << "\033[31m" // red color - << "Error (deferred - will exit later) in the preCICE adapter: " - << "\033[0m" // restore color - << nl - << message.c_str() - << nl - << nl; - } else if (level.compare("debug") == 0) { Pout << INFO_STR_ADAPTER diff --git a/changelog-entries/364.md b/changelog-entries/364.md new file mode 100644 index 00000000..b2f8dd15 --- /dev/null +++ b/changelog-entries/364.md @@ -0,0 +1 @@ +- Removed the error-deferred workaround, simplifying the error handling process in configuration. Configure simulations with the `errors strict` option for the adapter function object. [#364](https://github.com/precice/openfoam-adapter/pull/364) \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index ae0b7d4d..05585964 100644 --- a/docs/config.md +++ b/docs/config.md @@ -357,6 +357,7 @@ functions preCICE_Adapter { type preciceAdapterFunctionObject; + errors strict; // optional } } ``` @@ -365,6 +366,8 @@ This directs the solver to use the `preciceAdapterFunctionObject` function objec which is part of the `libpreciceAdapterFunctionObject.so` shared library. The name `preCICE_Adapter` can be arbitrary. It is important that the library is loaded outside the `functions` dictionary when you want to use the custom boundary conditions that we provide with the FF module. +The `errors strict` option is optional and [available since OpenFOAM v2012](https://www.openfoam.com/news/main-news/openfoam-v20-12/post-processing#post-processing-function-object-error-handling). Since the adapter is necessary to do a coupled simulation, this option instructs OpenFOAM to stop in case it faces issues with loading the adapter. For OpenFOAM versions that don't support this, remove the option. + If you are using other function objects in your simulation, add the preCICE adapter to the end of the list. The adapter will then be executed last, which is important, as the adapter also controls the end of the simulation. When the end of the simulation is detected, the adapter also triggers the `end()` method of all function objects. *** From 9ec23efe0fb2b1a95ddb02f5a4cd9bd98d01aeed Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Fri, 1 Aug 2025 17:28:02 +0200 Subject: [PATCH 21/70] Handle potential exceptions from preCICE calls (#366) Co-authored-by: David Schneider --- Adapter.C | 31 +++++++++++++++++++++++++++++-- Adapter.H | 6 ++++++ changelog-entries/366.md | 1 + 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 changelog-entries/366.md diff --git a/Adapter.C b/Adapter.C index f6beccd3..aaa983ef 100644 --- a/Adapter.C +++ b/Adapter.C @@ -223,6 +223,7 @@ void preciceAdapter::Adapter::configFileRead() } void preciceAdapter::Adapter::configure() +try { // Read the adapter's configuration file configFileRead(); @@ -384,8 +385,13 @@ void preciceAdapter::Adapter::configure() return; } +catch (const PreciceError& e) +{ + std::exit(EXIT_FAILURE); +} void preciceAdapter::Adapter::execute() +try { // The solver has already solved the equations for this timestep. @@ -466,14 +472,23 @@ void preciceAdapter::Adapter::execute() return; } +catch (const PreciceError& e) +{ + std::exit(EXIT_FAILURE); +} void preciceAdapter::Adapter::adjustTimeStep() +try { adjustSolverTimeStepAndReadData(); return; } +catch (const PreciceError& e) +{ + std::exit(EXIT_FAILURE); +} void preciceAdapter::Adapter::readCouplingData(double relativeReadTime) { @@ -511,9 +526,11 @@ void preciceAdapter::Adapter::initialize() SETUP_TIMER(); if (precice_->requiresInitialData()) + { + DEBUG(adapterInfo("Initializing preCICE data...")); writeCouplingData(); + } - DEBUG(adapterInfo("Initializing preCICE data...")); precice_->initialize(); preciceInitialized_ = true; ACCUMULATE_TIMER(timeInInitialize_); @@ -636,7 +653,7 @@ void preciceAdapter::Adapter::adjustSolverTimeStepAndReadData() else if (timestepSolverDetermined - precice_->getMaxTimeStepSize() > tolerance) { // In the last time-step, we adjust to dt = 0, but we don't need to trigger the warning here - if (precice_->isCouplingOngoing()) + if (isCouplingOngoing()) { adapterInfo( "The solver's timestep cannot be larger than the coupling timestep." @@ -1502,6 +1519,7 @@ void preciceAdapter::Adapter::writeVolCheckpoint() void preciceAdapter::Adapter::end() +try { // Throw a warning if the simulation exited before the coupling was complete if (NULL != precice_ && isCouplingOngoing()) @@ -1511,6 +1529,10 @@ void preciceAdapter::Adapter::end() return; } +catch (const PreciceError& e) +{ + std::exit(EXIT_FAILURE); +} void preciceAdapter::Adapter::teardown() { @@ -1670,6 +1692,7 @@ void preciceAdapter::Adapter::teardown() } preciceAdapter::Adapter::~Adapter() +try { teardown(); @@ -1695,3 +1718,7 @@ preciceAdapter::Adapter::~Adapter() return; } +catch (const PreciceError& e) +{ + std::exit(EXIT_FAILURE); +} diff --git a/Adapter.H b/Adapter.H index 18c7ce35..fcd6f0e8 100644 --- a/Adapter.H +++ b/Adapter.H @@ -24,6 +24,12 @@ // preCICE Solver Interface #include +#if PRECICE_VERSION_GREATER_EQUAL(3, 2, 0) +using PreciceError = ::precice::Error; +#else +using PreciceError = std::runtime_error; +#endif + namespace preciceAdapter { diff --git a/changelog-entries/366.md b/changelog-entries/366.md new file mode 100644 index 00000000..fc227e8c --- /dev/null +++ b/changelog-entries/366.md @@ -0,0 +1 @@ +- Added exception handling for preCICE API calls [#366](https://github.com/precice/openfoam-adapter/pull/366) \ No newline at end of file From f8c8ca04fa88bdb8c6a629af859421649675d3d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:31:31 +0200 Subject: [PATCH 22/70] Bump actions/checkout from 4 to 5 (#373) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-custom.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/check-format.yml | 2 +- .github/workflows/check-shell.yml | 2 +- .github/workflows/pre-commit.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 8e18a3b2..935ff479 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -75,7 +75,7 @@ jobs: echo "Run tutorial partitioned-pipe: ${{ github.event.inputs.runTutorialPartitionedPipe }}" echo "Tutorials branch: ${{ github.event.inputs.branchTutorials }}" - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.event.inputs.refAdapter }} - name: Install preCICE (no cache) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f12d82a8..f64d6f71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: container: precice/precice:nightly steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up OpenFOAM uses: gerlero/setup-openfoam@v1 with: diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index c765840e..f1779568 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -5,7 +5,7 @@ jobs: name: Check formatting (clang-format) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run clang-format style check for C/C++ programs. uses: jidicula/clang-format-action@main with: diff --git a/.github/workflows/check-shell.yml b/.github/workflows/check-shell.yml index dd5474f8..e1defe17 100644 --- a/.github/workflows/check-shell.yml +++ b/.github/workflows/check-shell.yml @@ -5,6 +5,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Lint shell scripts (shellcheck) uses: ludeeus/action-shellcheck@master diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index f87390de..9c16a774 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout preCICE - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup python uses: actions/setup-python@v5 with: From 7278ba584c8977107caa23a3d3f1f3ab443e95c2 Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Sun, 7 Sep 2025 10:09:06 -0300 Subject: [PATCH 23/70] Add OPI badge to README.md (#375) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2189134a..7bf5fc22 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Changelog](https://img.shields.io/badge/Keep%20a%20Changelog--555.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmMTVkMzAiIHZpZXdCb3g9IjAgMCAxODcgMTg1Ij48cGF0aCBkPSJNNjIgN2MtMTUgMy0yOCAxMC0zNyAyMmExMjIgMTIyIDAgMDAtMTggOTEgNzQgNzQgMCAwMDE2IDM4YzYgOSAxNCAxNSAyNCAxOGE4OSA4OSAwIDAwMjQgNCA0NSA0NSAwIDAwNiAwbDMtMSAxMy0xYTE1OCAxNTggMCAwMDU1LTE3IDYzIDYzIDAgMDAzNS01MiAzNCAzNCAwIDAwLTEtNWMtMy0xOC05LTMzLTE5LTQ3LTEyLTE3LTI0LTI4LTM4LTM3QTg1IDg1IDAgMDA2MiA3em0zMCA4YzIwIDQgMzggMTQgNTMgMzEgMTcgMTggMjYgMzcgMjkgNTh2MTJjLTMgMTctMTMgMzAtMjggMzhhMTU1IDE1NSAwIDAxLTUzIDE2bC0xMyAyaC0xYTUxIDUxIDAgMDEtMTItMWwtMTctMmMtMTMtNC0yMy0xMi0yOS0yNy01LTEyLTgtMjQtOC0zOWExMzMgMTMzIDAgMDE4LTUwYzUtMTMgMTEtMjYgMjYtMzMgMTQtNyAyOS05IDQ1LTV6TTQwIDQ1YTk0IDk0IDAgMDAtMTcgNTQgNzUgNzUgMCAwMDYgMzJjOCAxOSAyMiAzMSA0MiAzMiAyMSAyIDQxLTIgNjAtMTRhNjAgNjAgMCAwMDIxLTE5IDUzIDUzIDAgMDA5LTI5YzAtMTYtOC0zMy0yMy01MWE0NyA0NyAwIDAwLTUtNWMtMjMtMjAtNDUtMjYtNjctMTgtMTIgNC0yMCA5LTI2IDE4em0xMDggNzZhNTAgNTAgMCAwMS0yMSAyMmMtMTcgOS0zMiAxMy00OCAxMy0xMSAwLTIxLTMtMzAtOS01LTMtOS05LTEzLTE2YTgxIDgxIDAgMDEtNi0zMiA5NCA5NCAwIDAxOC0zNSA5MCA5MCAwIDAxNi0xMmwxLTJjNS05IDEzLTEzIDIzLTE2IDE2LTUgMzItMyA1MCA5IDEzIDggMjMgMjAgMzAgMzYgNyAxNSA3IDI5IDAgNDJ6bS00My03M2MtMTctOC0zMy02LTQ2IDUtMTAgOC0xNiAyMC0xOSAzN2E1NCA1NCAwIDAwNSAzNGM3IDE1IDIwIDIzIDM3IDIyIDIyLTEgMzgtOSA0OC0yNGE0MSA0MSAwIDAwOC0yNCA0MyA0MyAwIDAwLTEtMTJjLTYtMTgtMTYtMzEtMzItMzh6bS0yMyA5MWgtMWMtNyAwLTE0LTItMjEtN2EyNyAyNyAwIDAxLTEwLTEzIDU3IDU3IDAgMDEtNC0yMCA2MyA2MyAwIDAxNi0yNWM1LTEyIDEyLTE5IDI0LTIxIDktMyAxOC0yIDI3IDIgMTQgNiAyMyAxOCAyNyAzM3MtMiAzMS0xNiA0MGMtMTEgOC0yMSAxMS0zMiAxMXptMS0zNHYxNGgtOFY2OGg4djI4bDEwLTEwaDExbC0xNCAxNSAxNyAxOEg5NnoiLz48L3N2Zz4K)](https://github.com/precice/openfoam-adapter/blob/develop/CHANGELOG.md) [![License](https://img.shields.io/github/license/precice/openfoam-adapter.svg)](https://github.com/precice/openfoam-adapter/blob/master/LICENSE) [![Build with OpenFOAM v2404](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml) +[![OpenFOAM Package Index](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fexasim-project%2Fopi%2Frefs%2Fheads%2Fmain%2Fpkg%2Fprecice-adapter%2Fmetadata.json&query=%24.%5Bname%2Ctype%2Cversion%5D&label=opi)](https://github.com/exasim-project/opi) ## Start here From 42f320edaf1d40153eb7071dcbe242a17ec34f82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:01:24 +0200 Subject: [PATCH 24/70] Bump actions/setup-python from 5 to 6 (#376) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pre-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 9c16a774..53eef13b 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout preCICE uses: actions/checkout@v5 - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.10' check-latest: true From cf65114a25b17ceb3daaec82107fdc53043e6b71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:01:55 +0200 Subject: [PATCH 25/70] Bump actions/setup-python from 5 to 6 (#376) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From b6291bdc6a422c94e3c8a6cb044d78115116dd25 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 14 Oct 2025 00:29:36 +0200 Subject: [PATCH 26/70] Fix error message for reading stresses (#379) --- FSI/ForceBase.C | 8 +++++--- changelog-entries/379.md | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 changelog-entries/379.md diff --git a/FSI/ForceBase.C b/FSI/ForceBase.C index d46791d7..198eecaa 100644 --- a/FSI/ForceBase.C +++ b/FSI/ForceBase.C @@ -183,10 +183,12 @@ std::size_t preciceAdapter::FSI::ForceBase::writeToBuffer(double* buffer, void preciceAdapter::FSI::ForceBase::readFromBuffer(double* buffer) const { - /* TODO: Implement + /* TODO: Implement (issue https://github.com/precice/openfoam-adapter/issues/240) * We need two nested for-loops for each patch, * the outer for the locations and the inner for the dimensions. - * See the preCICE readBlockVectorData() implementation. + * Since the Force class already implements a read method, + * this only corresponds to stresses. + * In general, consider refactoring the ForceBase, Force, and Stress classes. */ - adapterInfo("Reading forces is not supported.", "error"); + adapterInfo("Reading stresses is not supported.", "error"); } diff --git a/changelog-entries/379.md b/changelog-entries/379.md new file mode 100644 index 00000000..a950d624 --- /dev/null +++ b/changelog-entries/379.md @@ -0,0 +1 @@ +- Fixed the wording in the error message thrown when reading stresses (was previously referring to forces) [#379](https://github.com/precice/openfoam-adapter/pull/379) \ No newline at end of file From d74aeb0c2ae846c3d5f0f51a985bc23c543959d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:28:44 +0100 Subject: [PATCH 27/70] Bump actions/upload-artifact from 4 to 5 (#381) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-custom.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 935ff479..26b168e7 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -137,7 +137,7 @@ jobs: wait $PIDfluid fi - name: Archive logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: logs path: | @@ -151,7 +151,7 @@ jobs: tutorials/partitioned-pipe/fluid1-openfoam-pimplefoam/fluid1-openfoam-pimplefoam.log tutorials/partitioned-pipe/fluid2-openfoam-pimplefoam/fluid2-openfoam-pimplefoam.log - name: Archive case files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: case-files path: | From c5e46e83371cd80bbb8bc62381cd22e27de5f7d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:24:26 +0100 Subject: [PATCH 28/70] Bump actions/checkout from 5 to 6 (#384) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-custom.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/check-format.yml | 2 +- .github/workflows/check-shell.yml | 2 +- .github/workflows/pre-commit.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 26b168e7..0adbe3be 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -75,7 +75,7 @@ jobs: echo "Run tutorial partitioned-pipe: ${{ github.event.inputs.runTutorialPartitionedPipe }}" echo "Tutorials branch: ${{ github.event.inputs.branchTutorials }}" - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.event.inputs.refAdapter }} - name: Install preCICE (no cache) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f64d6f71..1de14d19 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: container: precice/precice:nightly steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up OpenFOAM uses: gerlero/setup-openfoam@v1 with: diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index f1779568..039f1f35 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -5,7 +5,7 @@ jobs: name: Check formatting (clang-format) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Run clang-format style check for C/C++ programs. uses: jidicula/clang-format-action@main with: diff --git a/.github/workflows/check-shell.yml b/.github/workflows/check-shell.yml index e1defe17..1c220d10 100644 --- a/.github/workflows/check-shell.yml +++ b/.github/workflows/check-shell.yml @@ -5,6 +5,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Lint shell scripts (shellcheck) uses: ludeeus/action-shellcheck@master diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 53eef13b..c5a65f97 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout preCICE - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup python uses: actions/setup-python@v6 with: From 86aab03d1ece7589ab93b9631aac52794b9d909f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:22:33 +0100 Subject: [PATCH 29/70] Bump actions/upload-artifact from 5 to 6 (#385) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-custom.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 0adbe3be..80b984c1 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -137,7 +137,7 @@ jobs: wait $PIDfluid fi - name: Archive logs - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: logs path: | @@ -151,7 +151,7 @@ jobs: tutorials/partitioned-pipe/fluid1-openfoam-pimplefoam/fluid1-openfoam-pimplefoam.log tutorials/partitioned-pipe/fluid2-openfoam-pimplefoam/fluid2-openfoam-pimplefoam.log - name: Archive case files - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: case-files path: | From 52d9fa283d26b86cf07de22aebbef3aea356beea Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 31 Dec 2025 00:03:05 +0100 Subject: [PATCH 30/70] Update URLs in README.md --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 4b37e51f..17698943 100644 --- a/docs/README.md +++ b/docs/README.md @@ -60,7 +60,7 @@ Please cite this adapter using our [reference paper in the OpenFOAM Journal](htt This project is actively maintained on [precice/openfoam-adapter](https://github.com/precice/openfoam-adapter). Current maintainers: [@MakisH](https://github.com/MakisH/) and [@DavidSCN](https://github.com/DavidSCN). This adapter was developed as part of [Gerasimos Chourdakis' master's thesis](https://mediatum.ub.tum.de/1462269) [2]. -It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia Cheung ([master's thesis](https://www5.in.tum.de/pub/Cheung2016_Thesis.pdf) [3], in cooperation with [SimScale](https://www.simscale.com/)). +It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia Cheung ([master's thesis](https://www.simscale.com/) [3], in cooperation with [SimScale](https://www.simscale.com/)). The fluid-structure interaction module was developed in close collaboration between Gerasimos Chourdakis and Derek Risseeuw (TU Delft), in the context of the [master's thesis of the latter](http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123) [4]. We would also like to thank David Schneider (Univ. Siegen / TUM) and Maximilian Müller (TU Braunschweig) for sharing the code and experience of their similar previous work. @@ -79,7 +79,7 @@ The adapter is [easily extensible](https://precice.org/adapter-openfoam-extend.h URL: https://mediatum.ub.tum.de/1462269 [3] Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016.
-URL: https://www5.in.tum.de/pub/Cheung2016_Thesis.pdf +URL: https://mediatum.ub.tum.de/1461907 [4] Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019.
URL: http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123 From c6f0071f9b6e8114c194811ea776d08aa02cc8da Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Fri, 9 Jan 2026 13:28:33 +0100 Subject: [PATCH 31/70] Bump versions to OpenFOAM v2512 and preCICE v3.3.0 (#386) Also: Migrate OpenFOAM GitLab URLs to gitlab.com. --- .clang-format | 5 +++-- .github/workflows/build-custom.input | 4 ++-- .github/workflows/build-custom.yml | 7 ++++++- .github/workflows/build.yml | 4 ++-- .github/workflows/system-tests.yaml | 2 +- README.md | 2 +- changelog-entries/386.md | 1 + docs/openfoam-support.md | 12 +++++++----- 8 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 changelog-entries/386.md diff --git a/.clang-format b/.clang-format index 00adea7a..8284a618 100644 --- a/.clang-format +++ b/.clang-format @@ -3,11 +3,12 @@ Language: Cpp # BasedOnStyle: LLVM # # Proposed clang-format-11 style for OpenFOAM, trying to follow the OpenFOAM style guide: -# https://develop.openfoam.com/Development/openfoam/-/wikis/coding/style/style +# https://gitlab.com/openfoam/core/openfoam/-/wikis/coding/style/style # Configuration developed for the OpenFOAM-preCICE adapter code: # https://github.com/precice/openfoam-adapter # Contribute to the discussion at the respective OpenFOAM issue: -# https://develop.openfoam.com/Development/openfoam/-/issues/1634 +# https://develop.openfoam.com/Development/openfoam/-/issues/1634 (archived) +# https://gitlab.com/openfoam/core/openfoam/-/issues/1634 # # Keep `public:` at the first indentation level AccessModifierOffset: -4 diff --git a/.github/workflows/build-custom.input b/.github/workflows/build-custom.input index a90b3277..5d075e44 100644 --- a/.github/workflows/build-custom.input +++ b/.github/workflows/build-custom.input @@ -1,7 +1,7 @@ virtualEnvironment="ubuntu-22.04" refAdapter="develop" -versionOpenFOAM="2412" -versionpreCICE="v3.2.0" +versionOpenFOAM="2512" +versionpreCICE="v3.3.0" runTutorialHeatedPlate="false" runTutorialQuickstart="false" runTutorialPartitionedPipe="false" diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 80b984c1..2162c3e9 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -20,6 +20,8 @@ on: required: true type: choice options: + - 2512 + - 2506 - 2412 - 2406 - 2312 @@ -31,13 +33,16 @@ on: - 2012 - 2006 - 1912 + - 13 + - 12 + - 11 - 10 - 9 - 8 - 7 versionpreCICE: description: 'Version of preCICE to build with' - default: 'v3.2.0' + default: 'v3.3.0' required: true runTutorialHeatedPlate: description: Run tutorial flow-over-heated-plate diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1de14d19..347ba0b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build with OpenFOAM v2412 +name: Build with OpenFOAM v2512 on: push: branches-ignore: @@ -28,6 +28,6 @@ jobs: - name: Set up OpenFOAM uses: gerlero/setup-openfoam@v1 with: - openfoam-version: 2412 + openfoam-version: 2512 - name: Build OpenFOAM-preCICE adapter run: ./Allwmake diff --git a/.github/workflows/system-tests.yaml b/.github/workflows/system-tests.yaml index 914f1d6e..4bc6aaaf 100644 --- a/.github/workflows/system-tests.yaml +++ b/.github/workflows/system-tests.yaml @@ -40,6 +40,6 @@ jobs: uses: precice/tutorials/.github/workflows/run_testsuite_workflow.yml@develop with: suites: openfoam_adapter_pr - build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:v3.2.0,OPENFOAM_EXECUTABLE:openfoam2312,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} + build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:v3.3.0,OPENFOAM_EXECUTABLE:openfoam2512,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} system_tests_branch: develop log_level: "DEBUG" diff --git a/README.md b/README.md index 7bf5fc22..33dc03cb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Cite](https://img.shields.io/badge/cite-literature_guide-d45815)](https://precice.org/fundamentals-literature-guide.html) [![Changelog](https://img.shields.io/badge/Keep%20a%20Changelog--555.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmMTVkMzAiIHZpZXdCb3g9IjAgMCAxODcgMTg1Ij48cGF0aCBkPSJNNjIgN2MtMTUgMy0yOCAxMC0zNyAyMmExMjIgMTIyIDAgMDAtMTggOTEgNzQgNzQgMCAwMDE2IDM4YzYgOSAxNCAxNSAyNCAxOGE4OSA4OSAwIDAwMjQgNCA0NSA0NSAwIDAwNiAwbDMtMSAxMy0xYTE1OCAxNTggMCAwMDU1LTE3IDYzIDYzIDAgMDAzNS01MiAzNCAzNCAwIDAwLTEtNWMtMy0xOC05LTMzLTE5LTQ3LTEyLTE3LTI0LTI4LTM4LTM3QTg1IDg1IDAgMDA2MiA3em0zMCA4YzIwIDQgMzggMTQgNTMgMzEgMTcgMTggMjYgMzcgMjkgNTh2MTJjLTMgMTctMTMgMzAtMjggMzhhMTU1IDE1NSAwIDAxLTUzIDE2bC0xMyAyaC0xYTUxIDUxIDAgMDEtMTItMWwtMTctMmMtMTMtNC0yMy0xMi0yOS0yNy01LTEyLTgtMjQtOC0zOWExMzMgMTMzIDAgMDE4LTUwYzUtMTMgMTEtMjYgMjYtMzMgMTQtNyAyOS05IDQ1LTV6TTQwIDQ1YTk0IDk0IDAgMDAtMTcgNTQgNzUgNzUgMCAwMDYgMzJjOCAxOSAyMiAzMSA0MiAzMiAyMSAyIDQxLTIgNjAtMTRhNjAgNjAgMCAwMDIxLTE5IDUzIDUzIDAgMDA5LTI5YzAtMTYtOC0zMy0yMy01MWE0NyA0NyAwIDAwLTUtNWMtMjMtMjAtNDUtMjYtNjctMTgtMTIgNC0yMCA5LTI2IDE4em0xMDggNzZhNTAgNTAgMCAwMS0yMSAyMmMtMTcgOS0zMiAxMy00OCAxMy0xMSAwLTIxLTMtMzAtOS01LTMtOS05LTEzLTE2YTgxIDgxIDAgMDEtNi0zMiA5NCA5NCAwIDAxOC0zNSA5MCA5MCAwIDAxNi0xMmwxLTJjNS05IDEzLTEzIDIzLTE2IDE2LTUgMzItMyA1MCA5IDEzIDggMjMgMjAgMzAgMzYgNyAxNSA3IDI5IDAgNDJ6bS00My03M2MtMTctOC0zMy02LTQ2IDUtMTAgOC0xNiAyMC0xOSAzN2E1NCA1NCAwIDAwNSAzNGM3IDE1IDIwIDIzIDM3IDIyIDIyLTEgMzgtOSA0OC0yNGE0MSA0MSAwIDAwOC0yNCA0MyA0MyAwIDAwLTEtMTJjLTYtMTgtMTYtMzEtMzItMzh6bS0yMyA5MWgtMWMtNyAwLTE0LTItMjEtN2EyNyAyNyAwIDAxLTEwLTEzIDU3IDU3IDAgMDEtNC0yMCA2MyA2MyAwIDAxNi0yNWM1LTEyIDEyLTE5IDI0LTIxIDktMyAxOC0yIDI3IDIgMTQgNiAyMyAxOCAyNyAzM3MtMiAzMS0xNiA0MGMtMTEgOC0yMSAxMS0zMiAxMXptMS0zNHYxNGgtOFY2OGg4djI4bDEwLTEwaDExbC0xNCAxNSAxNyAxOEg5NnoiLz48L3N2Zz4K)](https://github.com/precice/openfoam-adapter/blob/develop/CHANGELOG.md) [![License](https://img.shields.io/github/license/precice/openfoam-adapter.svg)](https://github.com/precice/openfoam-adapter/blob/master/LICENSE) -[![Build with OpenFOAM v2404](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml) +[![Build with OpenFOAM v2512](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml) [![OpenFOAM Package Index](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fexasim-project%2Fopi%2Frefs%2Fheads%2Fmain%2Fpkg%2Fprecice-adapter%2Fmetadata.json&query=%24.%5Bname%2Ctype%2Cversion%5D&label=opi)](https://github.com/exasim-project/opi) ## Start here diff --git a/changelog-entries/386.md b/changelog-entries/386.md new file mode 100644 index 00000000..0c32bb3f --- /dev/null +++ b/changelog-entries/386.md @@ -0,0 +1 @@ +- Changed the default OpenFOAM version to v2512 and preCICE to v3.3.0 [#386](https://github.com/precice/openfoam-adapter/pull/386) \ No newline at end of file diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index db41562c..4f26e857 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -7,14 +7,14 @@ summary: Recent OpenFOAM.com versions work out-of-the-box. Recent OpenFOAM.org v ## How to get OpenFOAM -The easiest way to start is to get binary packages for your Linux distribution. For example, to [get OpenFOAM v2412 on Ubuntu](https://develop.openfoam.com/Development/openfoam/-/wikis/precompiled/debian#precompiled-packages-debianubuntu): +The easiest way to start is to get binary packages for your Linux distribution. For example, to [get OpenFOAM v2512 on Ubuntu](https://gitlab.com/openfoam/core/openfoam/-/wikis/precompiled/debian): ```bash # Add the signing key, add the repository, update: wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash -# Install OpenFOAM v2412: -sudo apt-get install openfoam2412-dev +# Install OpenFOAM v2512: +sudo apt-get install openfoam2512-dev ``` As these steps change your `.profile`, you need to log out and in again to make OpenFOAM fully discoverable. @@ -34,7 +34,7 @@ Currently, and since v1.3.0 of the adapter (first one for preCICE v3), only the We provide version-specific [release archives](https://github.com/precice/openfoam-adapter/releases/latest) and respective Git branches for: - OpenCFD / ESI (openfoam.com) - main focus: - - [OpenFOAM v1812-v2412](https://github.com/precice/openfoam-adapter) or newer + - [OpenFOAM v1812-v2512](https://github.com/precice/openfoam-adapter) or newer - [OpenFOAM v1612-v1806](https://github.com/precice/openfoam-adapter/tree/OpenFOAMv1806) (not tested) - OpenFOAM Foundation (openfoam.org) - secondary, consider experimental: - [OpenFOAM 10](https://github.com/precice/openfoam-adapter/tree/OpenFOAM10) @@ -53,7 +53,7 @@ We use the Ubuntu repositories of each OpenFOAM vendor for testing. Quick refere - OpenCFD / ESI (openfoam.com): - [PPA repository](https://dl.openfoam.com/repos/deb/) - - More details on the [OpenFOAM documentation](https://develop.openfoam.com/Development/openfoam/-/wikis/precompiled). + - More details on the [OpenFOAM documentation](https://gitlab.com/openfoam/core/openfoam/-/wikis/precompiled). - OpenFOAM Foundation (openfoam.org): - [PPA repository](https://dl.openfoam.org/ubuntu/) - More details on the [OpenFOAM documentation](https://dl.openfoam.org/). @@ -64,6 +64,8 @@ The following table gives an overview, including versions that we do not support | version | 24.04 | 22.04 | 20.04 | 18.04 | | --- | --- | --- | --- | --- | +| (com) v2512 | x | x | x | x | +| (com) v2506 | x | x | x | x | | (com) v2412 | x | x | x | x | | (com) v2406 | x | x | x | x | | (com) v2312 | x | x | x | x | From cad7e8d8d879d3631c6155007c25562de3c165ff Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Fri, 9 Jan 2026 17:31:27 +0100 Subject: [PATCH 32/70] Extend porting guide with more points for OpenFOAM 6 and 7 (#356) --- docs/openfoam-support.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index 4f26e857..1cbf8c7b 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -101,11 +101,12 @@ We take into account the following relevant differences between OpenFOAM version - **dictionary access:** In dictionaries (essentially: in the `preciceDict`), some methods are not available. These are mainly used in the configuration step in `Adapter.C`, but also in `ForceBase.C`. - Replace `preciceDict.get("...")` with `static_cast(preciceDict.lookup("...")`. - Replace `preciceDict.get("...")` with `static_cast(preciceDict.lookup("..."))`. + - Replace `preciceDict.getOrDefault` with `preciceDict.lookupOrDefault`. - Replace `preciceDict.get("...")` with `static_cast(preciceDict.lookup("..."))`. - Replace `preciceDict.findDict("...")` with `preciceDict.subDictPtr("...")`. - **Db access:** In the macro that deals with adding checkpointed fields, some methods are not available. - - Replace `mesh_.sortedNames()` with `mesh_.lookupClass().sortedToc()`. - - Replace `mesh_.thisDb().getObjectPtr(obj)` with `&(const_cast(mesh_.thisDb().lookupObject(obj)))`. + - Replace `mesh_.sortedNames()` with `mesh_.lookupClass().sortedToc()`. + - Replace `mesh_.thisDb().getObjectPtr(obj)` with `&(const_cast(mesh_.thisDb().lookupObject(obj)))`. - **Pointers:** `std::unique_ptr` is not available. - Replace `std::unique_ptr` with `Foam::autoPtr`. - Replace calls to pointer `.reset()`, such as `ForceOwning_.reset(new volVectorField(`, with `ForceOwning_ = new volVectorField(`. Adjust the number of closing parentheses. @@ -136,6 +137,9 @@ Related work in the adapter: [PR #33](https://github.com/precice/openfoam-adapte OpenFOAM 7 was [released](https://openfoam.org/release/7/) in July 2019 ([GitHub mirror](https://github.com/OpenFOAM/OpenFOAM-7), [Doxygen](https://cpp.openfoam.org/v7)). Compared to OpenFOAM 6: +- **writeEntry:** The interface for `writeEntry` was changed. + - Where in the latest adapter (`FF/BoundaryConditions`) you see `this->writeEntry("value", os)`, replace it with `writeEntry(os, "value", *this)`. + - Similarly, where `this->valueFraction().writeEntry("valueFraction", os);`, replace it with `writeEntry(os, "valueFraction", this->valueFraction())`. Similarly for `this->refValue().writeEntry("refValue", os)`. - **fileName:** `fileName::DIRECTORY` was renamed to `fileType::directory`. - Replace this in the `Adapter.C`. From cff76120c1d0491c5d2dca7fec86d9259a4b4185 Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Mon, 19 Jan 2026 15:34:52 +0100 Subject: [PATCH 33/70] Add documentation about subcycling (#388) --- docs/config.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/config.md b/docs/config.md index 05585964..7e1c2357 100644 --- a/docs/config.md +++ b/docs/config.md @@ -426,6 +426,18 @@ It is important to notice that the target data location is again the face center This is implemented for all CHT-related fields mapped with a `consistent` constraint, but it is not implemented for the `FSI` and `FF` modules. {% endnote %} +### Notes on subcycling + +If it is necessary to subcycle, i.e., do multiple solver time steps per coupling time window, on either the OpenFOAM side or another solver, please consider additional nuances. A good way to start is reading up on subcycling and [waveform time interpolation](https://precice.org/couple-your-code-waveform). Subcycling is *allowed* by preCICE and the OpenFOAM-adapter, but you may obtain different results with subcycling enabled or not depending on your problem and coupling configuration. This has to do with how the subcycling data is modelled in the time-integration of your problem, where the two independent solvers (coupling participants) with non-matching timesteps march forward in time. This depends on the time stepping scheme and the type of physical quantity that you are coupling. + +Currently, when doing subcycling in OpenFOAM on an FSI problem, the OpenFOAM-adapter will give the following warning: + +`The adapter does not fully support subcycling for FSI and instabilities may occur.` + +There is an active discussion on a [GitHub issue](https://github.com/precice/openfoam-adapter/issues/368) about why you will get different results with subcycling on the perpendicular-flap case. Until this is resolved, it is recommended to not subcycle the fluid participant in FSI simulations. + +In short, preCICE will sample only the instantaneous state of the coupled data at end of the coupling time window. If your coupled quantity is an instantaneous state of the system, such as Temperature, Velocity or Displacement, then your time-dependent problem evolution with subcycling will be modelled correctly. However, if your coupled data is a rate-quantity or something which should be conserved over time, such as Heat-Flux, Momentum-Flux or Force, then you would have to consider the modelling more carefully. For example, to exchange the time-average of Heat-Flux samples over the coupling time window. + ### Additional properties for some solvers Some solvers may not read all the material properties that are required for a coupled simulation. These parameters need to be added in the `preciceDict`. @@ -596,7 +608,7 @@ FSI ``` {% important %} -The option here defines the way the interface mesh is initialized when restarting an FSI simulation in OpenFOAM. In order to restart a coupled simulation, your solid solver needs to be capable of restarting as well. Furthermore, the two participants need to follow the same assumption for the initialization, which for OpenFOAM you can configure with this option. You can find more information about restarting coupled simulations on [Dsicourse](https://precice.discourse.group/t/how-can-i-restart-a-coupled-simulation/675). +The option here defines the way the interface mesh is initialized when restarting an FSI simulation in OpenFOAM. In order to restart a coupled simulation, your solid solver needs to be capable of restarting as well. Furthermore, the two participants need to follow the same assumption for the initialization, which for OpenFOAM you can configure with this option. You can find more information about restarting coupled simulations on [Discourse](https://precice.discourse.group/t/how-can-i-restart-a-coupled-simulation/675). {% endimportant %} #### Debugging From 67c78fdc191ac11edeafb6120738897ddce4f55e Mon Sep 17 00:00:00 2001 From: David Schneider Date: Tue, 20 Jan 2026 10:21:16 +0100 Subject: [PATCH 34/70] Add coupling fields used for CFD-DEM (#380) * Add drag force as FF volume force * Example for adding DragForces to the OpenFOAM adapter * Create drag force object if it doesn't exist * Add fields for semi-implicit momentum coupling * Skip first read * Skip first read * Add Readme for dataset * Extend pressure gradient to write in volume coupling mode * Also add check for timewindow completeness on read skip * Elide skip read option * Remove README hints on changes and building * ... missed from previous commit * Indentation using clang-format * Rename pressure gradient to full pressure gradient * Add Pressure normal gradient again * Fix the configuration * Use suffix Full instead of prefix * Changelog entry --------- Co-authored-by: Robin Walloner --- Adapter.C | 1 - FF/DragForce.C | 203 ++++++++++++++++++++++++++++++++++++++ FF/DragForce.H | 51 ++++++++++ FF/ExplicitMomentum.C | 203 ++++++++++++++++++++++++++++++++++++++ FF/ExplicitMomentum.H | 51 ++++++++++ FF/FF.C | 40 ++++++++ FF/FF.H | 13 +++ FF/ImplicitMomentum.C | 139 ++++++++++++++++++++++++++ FF/ImplicitMomentum.H | 51 ++++++++++ FF/ModuleFF.C | 4 + FF/PressureGradientFull.C | 132 +++++++++++++++++++++++++ FF/PressureGradientFull.H | 53 ++++++++++ changelog-entries/380.md | 1 + 13 files changed, 941 insertions(+), 1 deletion(-) create mode 100644 FF/DragForce.C create mode 100644 FF/DragForce.H create mode 100644 FF/ExplicitMomentum.C create mode 100644 FF/ExplicitMomentum.H create mode 100644 FF/ImplicitMomentum.C create mode 100644 FF/ImplicitMomentum.H create mode 100644 FF/PressureGradientFull.C create mode 100644 FF/PressureGradientFull.H create mode 100644 changelog-entries/380.md diff --git a/Adapter.C b/Adapter.C index aaa983ef..883e0e64 100644 --- a/Adapter.C +++ b/Adapter.C @@ -680,7 +680,6 @@ void preciceAdapter::Adapter::adjustSolverTimeStepAndReadData() // Read the received coupling data from the buffer // Fits to an implicit Euler readCouplingData(runTime_.deltaT().value()); - return; } diff --git a/FF/DragForce.C b/FF/DragForce.C new file mode 100644 index 00000000..8e9f5ffa --- /dev/null +++ b/FF/DragForce.C @@ -0,0 +1,203 @@ +#include "DragForce.H" +#include "coupledVelocityFvPatchField.H" + +using namespace Foam; + +preciceAdapter::FF::DragForce::DragForce( + const Foam::fvMesh& mesh, + const std::string nameFd) +{ + if (mesh.foundObject(nameFd)) + { + adapterInfo("Loaded existing particle force object " + nameFd, "debug"); + F_d_ = const_cast( + &mesh.lookupObject(nameFd)); + } + else + { + adapterInfo("Creating a new drag force object " + nameFd, "debug"); + F_d_ = new volVectorField( + IOobject( + nameFd, + mesh.time().timeName(), + mesh, + IOobject::MUST_READ, + IOobject::AUTO_WRITE), + mesh); + } + dataType_ = vector; +} + +std::size_t preciceAdapter::FF::DragForce::write(double* buffer, bool meshConnectivity, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (const auto& cell : F_d_->internalField()) + { + // x-dimension + buffer[bufferIndex++] = cell.x(); + + // y-dimension + buffer[bufferIndex++] = cell.y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = cell.z(); + } + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(F_d_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // x-dimension + buffer[bufferIndex++] = F_d_->internalField()[currentCell].x(); + + // y-dimension + buffer[bufferIndex++] = F_d_->internalField()[currentCell].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = F_d_->internalField()[currentCell].z(); + } + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + vectorField UPatch = F_d_->boundaryField()[patchID]; + + // For every cell of the patch + forAll(F_d_->boundaryFieldRef()[patchID], i) + { + // Copy the velocity into the buffer + // x-dimension + buffer[bufferIndex++] = + UPatch[i].x(); + + // y-dimension + buffer[bufferIndex++] = + UPatch[i].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = + UPatch[i].z(); + } + } + } + return bufferIndex; +} + +void preciceAdapter::FF::DragForce::read(double* buffer, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (auto& cell : F_d_->ref()) + { + // x-dimension + cell.x() = buffer[bufferIndex++]; + + // y-dimension + cell.y() = buffer[bufferIndex++]; + + if (dim == 3) + { + // z-dimension + cell.z() = buffer[bufferIndex++]; + } + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(F_d_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // x-dimension + F_d_->ref()[currentCell].x() = buffer[bufferIndex++]; + + // y-dimension + F_d_->ref()[currentCell].y() = buffer[bufferIndex++]; + + if (dim == 3) + { + // z-dimension + F_d_->ref()[currentCell].z() = buffer[bufferIndex++]; + } + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + // Get the velocity value boundary patch + vectorField* valuePatchPtr = &F_d_->boundaryFieldRef()[patchID]; + vectorField& valuePatch = *valuePatchPtr; + + // For every cell of the patch + forAll(F_d_->boundaryFieldRef()[patchID], i) + { + // Set the velocity as the buffer value + // x-dimension + valuePatch[i].x() = + buffer[bufferIndex++]; + + // y-dimension + valuePatch[i].y() = + buffer[bufferIndex++]; + + if (dim == 3) + { + // z-dimension + valuePatch[i].z() = + buffer[bufferIndex++]; + } + } + } +} + +bool preciceAdapter::FF::DragForce::isLocationTypeSupported(const bool meshConnectivity) const +{ + if (meshConnectivity) + { + return (this->locationType_ == LocationType::faceCenters); + } + else + { + return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::volumeCenters); + } +} + +std::string preciceAdapter::FF::DragForce::getDataName() const +{ + return "DragForce"; +} diff --git a/FF/DragForce.H b/FF/DragForce.H new file mode 100644 index 00000000..84b6a0a2 --- /dev/null +++ b/FF/DragForce.H @@ -0,0 +1,51 @@ +#ifndef FF_DRAG_FORCE_H +#define FF_DRAG_FORCE_H + +#include "CouplingDataUser.H" + +#include "fvCFD.H" +#include "cellSet.H" + +namespace preciceAdapter +{ +namespace FF +{ + +//- Class that writes and reads velocity +class DragForce : public CouplingDataUser +{ + +private: + //- DragForce field + Foam::volVectorField* F_d_; + +public: + //- Constructor + DragForce( + const Foam::fvMesh& mesh, + const std::string nameFd); + + //- Write the velocity values into the buffer + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; + + //- Read the velocity values from the buffer + void read(double* buffer, const unsigned int dim) final; + + bool isLocationTypeSupported(const bool meshConnectivity) const final; + + //- Get the name of the current data field + std::string getDataName() const final; +}; + +} +} + +// NOTE: In order to couple another variable, you need to add another +// subclass of the CouplingDataUser. Take this file and DragForce.C +// as an example and look for more notes in the Adapter.C and Adapter.H. +// Essentially, the new class needs to implement a constructor (which also +// sets the dataType_) and implement the write() and read() methods. +// Then, you need to create objects of this class in the Adapter.C, +// whenever requested from the configuration file (see notes). + +#endif diff --git a/FF/ExplicitMomentum.C b/FF/ExplicitMomentum.C new file mode 100644 index 00000000..2f7d0798 --- /dev/null +++ b/FF/ExplicitMomentum.C @@ -0,0 +1,203 @@ +#include "ExplicitMomentum.H" +#include "coupledVelocityFvPatchField.H" + +using namespace Foam; + +preciceAdapter::FF::ExplicitMomentum::ExplicitMomentum( + const Foam::fvMesh& mesh, + const std::string nameExplicitMomentum) +{ + if (mesh.foundObject(nameExplicitMomentum)) + { + adapterInfo("Loaded existing explicit momentum object " + nameExplicitMomentum, "debug"); + ExplicitMomentum_ = const_cast( + &mesh.lookupObject(nameExplicitMomentum)); + } + else + { + adapterInfo("Creating a new explicit momentum object " + nameExplicitMomentum, "debug"); + ExplicitMomentum_ = new volVectorField( + IOobject( + nameExplicitMomentum, + mesh.time().timeName(), + mesh, + IOobject::MUST_READ, + IOobject::AUTO_WRITE), + mesh); + } + dataType_ = vector; +} + +std::size_t preciceAdapter::FF::ExplicitMomentum::write(double* buffer, bool meshConnectivity, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (const auto& cell : ExplicitMomentum_->internalField()) + { + // x-dimension + buffer[bufferIndex++] = cell.x(); + + // y-dimension + buffer[bufferIndex++] = cell.y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = cell.z(); + } + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(ExplicitMomentum_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // x-dimension + buffer[bufferIndex++] = ExplicitMomentum_->internalField()[currentCell].x(); + + // y-dimension + buffer[bufferIndex++] = ExplicitMomentum_->internalField()[currentCell].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = ExplicitMomentum_->internalField()[currentCell].z(); + } + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + vectorField ExplicitMomentumPatch = ExplicitMomentum_->boundaryField()[patchID]; + + // For every cell of the patch + forAll(ExplicitMomentum_->boundaryFieldRef()[patchID], i) + { + // Copy the velocity into the buffer + // x-dimension + buffer[bufferIndex++] = + ExplicitMomentumPatch[i].x(); + + // y-dimension + buffer[bufferIndex++] = + ExplicitMomentumPatch[i].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = + ExplicitMomentumPatch[i].z(); + } + } + } + return bufferIndex; +} + +void preciceAdapter::FF::ExplicitMomentum::read(double* buffer, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (auto& cell : ExplicitMomentum_->ref()) + { + // x-dimension + cell.x() = buffer[bufferIndex++]; + + // y-dimension + cell.y() = buffer[bufferIndex++]; + + if (dim == 3) + { + // z-dimension + cell.z() = buffer[bufferIndex++]; + } + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(ExplicitMomentum_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // x-dimension + ExplicitMomentum_->ref()[currentCell].x() = buffer[bufferIndex++]; + + // y-dimension + ExplicitMomentum_->ref()[currentCell].y() = buffer[bufferIndex++]; + + if (dim == 3) + { + // z-dimension + ExplicitMomentum_->ref()[currentCell].z() = buffer[bufferIndex++]; + } + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + // Get the velocity value boundary patch + vectorField* valuePatchPtr = &ExplicitMomentum_->boundaryFieldRef()[patchID]; + vectorField& valuePatch = *valuePatchPtr; + + // For every cell of the patch + forAll(ExplicitMomentum_->boundaryFieldRef()[patchID], i) + { + // Set the velocity as the buffer value + // x-dimension + valuePatch[i].x() = + buffer[bufferIndex++]; + + // y-dimension + valuePatch[i].y() = + buffer[bufferIndex++]; + + if (dim == 3) + { + // z-dimension + valuePatch[i].z() = + buffer[bufferIndex++]; + } + } + } +} + +bool preciceAdapter::FF::ExplicitMomentum::isLocationTypeSupported(const bool meshConnectivity) const +{ + if (meshConnectivity) + { + return (this->locationType_ == LocationType::faceCenters); + } + else + { + return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::volumeCenters); + } +} + +std::string preciceAdapter::FF::ExplicitMomentum::getDataName() const +{ + return "ExplicitMomentum"; +} diff --git a/FF/ExplicitMomentum.H b/FF/ExplicitMomentum.H new file mode 100644 index 00000000..a2524bed --- /dev/null +++ b/FF/ExplicitMomentum.H @@ -0,0 +1,51 @@ +#ifndef FF_EXPLICIT_MOMENTUM_H +#define FF_EXPLICIT_MOMENTUM_H + +#include "CouplingDataUser.H" + +#include "fvCFD.H" +#include "cellSet.H" + +namespace preciceAdapter +{ +namespace FF +{ + +//- Class that writes and reads velocity +class ExplicitMomentum : public CouplingDataUser +{ + +private: + //- ExplicitMomentum field + Foam::volVectorField* ExplicitMomentum_; + +public: + //- Constructor + ExplicitMomentum( + const Foam::fvMesh& mesh, + const std::string nameExplicitMomentum); + + //- Write the velocity values into the buffer + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; + + //- Read the velocity values from the buffer + void read(double* buffer, const unsigned int dim) final; + + bool isLocationTypeSupported(const bool meshConnectivity) const final; + + //- Get the name of the current data field + std::string getDataName() const final; +}; + +} +} + +// NOTE: In order to couple another variable, you need to add another +// subclass of the CouplingDataUser. Take this file and ExplicitMomentum.C +// as an example and look for more notes in the Adapter.C and Adapter.H. +// Essentially, the new class needs to implement a constructor (which also +// sets the dataType_) and implement the write() and read() methods. +// Then, you need to create objects of this class in the Adapter.C, +// whenever requested from the configuration file (see notes). + +#endif diff --git a/FF/FF.C b/FF/FF.C index 2c2144ee..713e1c56 100644 --- a/FF/FF.C +++ b/FF/FF.C @@ -71,6 +71,18 @@ bool preciceAdapter::FF::FluidFluid::readConfig(const IOdictionary& adapterConfi namePhi_ = FFdict.lookupOrDefault("namePhi", "phi"); DEBUG(adapterInfo(" face flux field name : " + namePhi_)); + // Read the name of the face flux field (if different) + nameDragForce_ = FFdict.lookupOrDefault("nameDragForce", "Fd"); + adapterInfo(" drag force name : " + nameDragForce_); + + // Read the name of the explicit momentum source term (if different) + nameExplicitMomentum_ = FFdict.lookupOrDefault("nameExplicitMomentum", "ExplicitMomentum"); + adapterInfo(" explicit momentum name : " + nameExplicitMomentum_); + + // Read the name of the implicit momentum source term (if different) + nameImplicitMomentum_ = FFdict.lookupOrDefault("nameImplicitMomentum", "ImplicitMomentum"); + adapterInfo(" implicit momentum name : " + nameImplicitMomentum_); + // Check whether to enable flux correction for velocity fluxCorrection_ = FFdict.lookupOrDefault("fluxCorrection", false); DEBUG(adapterInfo(" flux correction of velocity is set to : " + std::to_string(fluxCorrection_))); @@ -131,6 +143,13 @@ bool preciceAdapter::FF::FluidFluid::addWriters(std::string dataName, Interface* new Velocity(mesh_, nameU_, namePhi_, fluxCorrection_)); DEBUG(adapterInfo("Added writer: Velocity.")); } + else if (dataName.find("PressureGradientFull") == 0) + { + interface->addCouplingDataWriter( + dataName, + new PressureGradientFull(mesh_, nameP_)); + DEBUG(adapterInfo("Added writer: Full Pressure Gradient.")); + } else if (dataName.find("PressureGradient") == 0) { interface->addCouplingDataWriter( @@ -261,6 +280,27 @@ bool preciceAdapter::FF::FluidFluid::addReaders(std::string dataName, Interface* new Phi(mesh_, namePhi_)); DEBUG(adapterInfo("Added reader: Phi.")); } + else if (dataName.find("DragForce") == 0) + { + interface->addCouplingDataReader( + dataName, + new DragForce(mesh_, nameDragForce_)); + DEBUG(adapterInfo("Added reader: DragForce.")); + } + else if (dataName.find("ExplicitMomentum") == 0) + { + interface->addCouplingDataReader( + dataName, + new ExplicitMomentum(mesh_, nameExplicitMomentum_)); + DEBUG(adapterInfo("Added reader: ExplicitMomentum.")); + } + else if (dataName.find("ImplicitMomentum") == 0) + { + interface->addCouplingDataReader( + dataName, + new ImplicitMomentum(mesh_, nameImplicitMomentum_)); + DEBUG(adapterInfo("Added reader: ImplicitMomentum.")); + } else { found = false; diff --git a/FF/FF.H b/FF/FF.H index 77629442..a430000b 100644 --- a/FF/FF.H +++ b/FF/FF.H @@ -4,10 +4,14 @@ #include "Interface.H" #include "FF/Velocity.H" +#include "FF/DragForce.H" +#include "FF/ExplicitMomentum.H" +#include "FF/ImplicitMomentum.H" #include "FF/Pressure.H" #include "FF/Temperature.H" #include "FF/Alpha.H" #include "FF/PressureGradient.H" +#include "FF/PressureGradientFull.H" #include "FF/VelocityGradient.H" #include "FF/TemperatureGradient.H" #include "FF/AlphaGradient.H" @@ -47,6 +51,15 @@ protected: //- Name of the face flux field std::string namePhi_ = "phi"; + //- Name of the particle drag force + std::string nameDragForce_ = "DragForce"; + + //- Name of the explicit momentum source term + std::string nameExplicitMomentum_ = "ExplicitMomentum"; + + //- Name of the implicit momentum source term + std::string nameImplicitMomentum_ = "ImplicitMomentum"; + //- Flux correction of velocity bool fluxCorrection_ = false; diff --git a/FF/ImplicitMomentum.C b/FF/ImplicitMomentum.C new file mode 100644 index 00000000..3c066066 --- /dev/null +++ b/FF/ImplicitMomentum.C @@ -0,0 +1,139 @@ +#include "ImplicitMomentum.H" +#include "coupledVelocityFvPatchField.H" + +using namespace Foam; + +preciceAdapter::FF::ImplicitMomentum::ImplicitMomentum( + const Foam::fvMesh& mesh, + const std::string nameImplicitMomentum) +{ + if (mesh.foundObject(nameImplicitMomentum)) + { + adapterInfo("Loaded existing implicit momentum object " + nameImplicitMomentum, "debug"); + ImplicitMomentum_ = const_cast( + &mesh.lookupObject(nameImplicitMomentum)); + } + else + { + adapterInfo("Creating a new implicit momentum object " + nameImplicitMomentum, "debug"); + ImplicitMomentum_ = new volScalarField( + IOobject( + nameImplicitMomentum, + mesh.time().timeName(), + mesh, + IOobject::MUST_READ, + IOobject::AUTO_WRITE), + mesh); + } + dataType_ = scalar; +} + +std::size_t preciceAdapter::FF::ImplicitMomentum::write(double* buffer, bool meshConnectivity, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (const auto& cell : ImplicitMomentum_->internalField()) + { + buffer[bufferIndex++] = cell; + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(ImplicitMomentum_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + buffer[bufferIndex++] = ImplicitMomentum_->internalField()[currentCell]; + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + scalarField ImplicitMomentumPatch = ImplicitMomentum_->boundaryField()[patchID]; + + // For every cell of the patch + forAll(ImplicitMomentum_->boundaryFieldRef()[patchID], i) + { + // Copy the velocity into the buffer + buffer[bufferIndex++] = + ImplicitMomentumPatch[i]; + } + } + return bufferIndex; +} + +void preciceAdapter::FF::ImplicitMomentum::read(double* buffer, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (auto& cell : ImplicitMomentum_->ref()) + { + cell = buffer[bufferIndex++]; + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(ImplicitMomentum_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + ImplicitMomentum_->ref()[currentCell] = buffer[bufferIndex++]; + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + // Get the velocity value boundary patch + scalarField* valuePatchPtr = &ImplicitMomentum_->boundaryFieldRef()[patchID]; + scalarField& valuePatch = *valuePatchPtr; + + // For every cell of the patch + forAll(ImplicitMomentum_->boundaryFieldRef()[patchID], i) + { + // Set the velocity as the buffer value + valuePatch[i] = + buffer[bufferIndex++]; + } + } +} + +bool preciceAdapter::FF::ImplicitMomentum::isLocationTypeSupported(const bool meshConnectivity) const +{ + if (meshConnectivity) + { + return (this->locationType_ == LocationType::faceCenters); + } + else + { + return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::volumeCenters); + } +} + +std::string preciceAdapter::FF::ImplicitMomentum::getDataName() const +{ + return "ImplicitMomentum"; +} diff --git a/FF/ImplicitMomentum.H b/FF/ImplicitMomentum.H new file mode 100644 index 00000000..42cf143a --- /dev/null +++ b/FF/ImplicitMomentum.H @@ -0,0 +1,51 @@ +#ifndef FF_IMPLICIT_MOMENTUM_H +#define FF_IMPLICIT_MOMENTUM_H + +#include "CouplingDataUser.H" + +#include "fvCFD.H" +#include "cellSet.H" + +namespace preciceAdapter +{ +namespace FF +{ + +//- Class that writes and reads velocity +class ImplicitMomentum : public CouplingDataUser +{ + +private: + //- ImplicitMomentum field + Foam::volScalarField* ImplicitMomentum_; + +public: + //- Constructor + ImplicitMomentum( + const Foam::fvMesh& mesh, + const std::string nameImplicitMomentum); + + //- Write the velocity values into the buffer + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) final; + + //- Read the velocity values from the buffer + void read(double* buffer, const unsigned int dim) final; + + bool isLocationTypeSupported(const bool meshConnectivity) const final; + + //- Get the name of the current data field + std::string getDataName() const final; +}; + +} +} + +// NOTE: In order to couple another variable, you need to add another +// subclass of the CouplingDataUser. Take this file and ExplDragCoeff.C +// as an example and look for more notes in the Adapter.C and Adapter.H. +// Essentially, the new class needs to implement a constructor (which also +// sets the dataType_) and implement the write() and read() methods. +// Then, you need to create objects of this class in the Adapter.C, +// whenever requested from the configuration file (see notes). + +#endif diff --git a/FF/ModuleFF.C b/FF/ModuleFF.C index 21d191ea..5adb6ad8 100644 --- a/FF/ModuleFF.C +++ b/FF/ModuleFF.C @@ -1,9 +1,13 @@ // Central inlude file to reduce the number of build targets and build time #include "FF.C" #include "Velocity.C" +#include "DragForce.C" +#include "ExplicitMomentum.C" +#include "ImplicitMomentum.C" #include "VelocityGradient.C" #include "Pressure.C" #include "PressureGradient.C" +#include "PressureGradientFull.C" #include "Temperature.C" #include "TemperatureGradient.C" #include "Alpha.C" diff --git a/FF/PressureGradientFull.C b/FF/PressureGradientFull.C new file mode 100644 index 00000000..2588fbb8 --- /dev/null +++ b/FF/PressureGradientFull.C @@ -0,0 +1,132 @@ +#include "PressureGradientFull.H" + +using namespace Foam; + +preciceAdapter::FF::PressureGradientFull::PressureGradientFull( + const Foam::fvMesh& mesh, + const std::string nameP) +: p_( + const_cast( + &mesh.lookupObject(nameP))), + gradP_(IOobject( + "gradP", + mesh.time().timeName(), + mesh, + IOobject::NO_READ, + IOobject::NO_WRITE), + fvc::grad(*p_)) +{ + dataType_ = vector; +} + +std::size_t preciceAdapter::FF::PressureGradientFull::write(double* buffer, bool meshConnectivity, const unsigned int dim) +{ + int bufferIndex = 0; + gradP_ = fvc::grad(*p_); + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (const auto& cell : gradP_.internalField()) + { + // x-dimension + buffer[bufferIndex++] = cell.x(); + + // y-dimension + buffer[bufferIndex++] = cell.y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = cell.z(); + } + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(p_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // x-dimension + buffer[bufferIndex++] = gradP_.internalField()[currentCell].x(); + + // y-dimension + buffer[bufferIndex++] = gradP_.internalField()[currentCell].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = gradP_.internalField()[currentCell].z(); + } + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + // For every cell of the patch + forAll(gradP_.boundaryFieldRef()[patchID], i) + { + // Copy the velocity into the buffer + // x-dimension + buffer[bufferIndex++] = + gradP_.boundaryFieldRef()[patchID][i].x(); + + // y-dimension + buffer[bufferIndex++] = + gradP_.boundaryFieldRef()[patchID][i].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = + gradP_.boundaryFieldRef()[patchID][i].z(); + } + } + } + return bufferIndex; +} + +void preciceAdapter::FF::PressureGradientFull::read(double* buffer, const unsigned int dim) +{ + int bufferIndex = 0; + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + // Get the pressure gradient boundary patch + scalarField& gradientPatch = + refCast( + p_->boundaryFieldRef()[patchID]) + .gradient(); + + // For every cell of the patch + forAll(gradientPatch, i) + { + // Set the pressure gradient as the buffer value + gradientPatch[i] = + buffer[bufferIndex++]; + } + } +} + +bool preciceAdapter::FF::PressureGradientFull::isLocationTypeSupported(const bool meshConnectivity) const +{ + return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::volumeCenters); +} + +std::string preciceAdapter::FF::PressureGradientFull::getDataName() const +{ + return "PressureGradientFull"; +} diff --git a/FF/PressureGradientFull.H b/FF/PressureGradientFull.H new file mode 100644 index 00000000..38279a1e --- /dev/null +++ b/FF/PressureGradientFull.H @@ -0,0 +1,53 @@ +#ifndef FF_FULL_PRESSURE_GRADIENT_H +#define FF_FULL_PRESSURE_GRADIENT_H + +#include "CouplingDataUser.H" + +#include "fvCFD.H" + +namespace preciceAdapter +{ +namespace FF +{ + +//- Class that writes and reads pressure gradient +class PressureGradientFull : public CouplingDataUser +{ + +private: + //- Pressure field + Foam::volScalarField* p_; + + // Gradient field + Foam::volVectorField gradP_; + +public: + //- Constructor + PressureGradientFull( + const Foam::fvMesh& mesh, + const std::string nameP); + + //- Write the pressure gradient values into the buffer + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim) override; + + //- Read the pressure gradient values from the buffer + void read(double* buffer, const unsigned int dim) override; + + bool isLocationTypeSupported(const bool meshConnectivity) const final; + + //- Get the name of the current data field + std::string getDataName() const final; +}; + +} +} + +// NOTE: In order to couple another variable, you need to add another +// subclass of the CouplingDataUser. Take this file and PressureGradientFull.C +// as an example and look for more notes in the Adapter.C and Adapter.H. +// Essentially, the new class needs to implement a constructor (which also +// sets the dataType_) and implement the write() and read() methods. +// Then, you need to create objects of this class in the Adapter.C, +// whenever requested from the configuration file (see notes). + +#endif diff --git a/changelog-entries/380.md b/changelog-entries/380.md new file mode 100644 index 00000000..0510ee02 --- /dev/null +++ b/changelog-entries/380.md @@ -0,0 +1 @@ +- Added the drag force, implicit momentum coefficient, explicit momentum, and the full pressure gradient as coupling variables for CFD-DEM. From ea3acb1fdb4870cc432daeb36d479744ae6c9571 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 20 Jan 2026 17:30:12 +0100 Subject: [PATCH 35/70] Update references style in README.md (#389) --- docs/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/README.md b/docs/README.md index 17698943..3e62d9ad 100644 --- a/docs/README.md +++ b/docs/README.md @@ -53,44 +53,44 @@ our [training session from the 15th OpenFOAM Workshop](https://mediatum.ub.tum.d ## Cite -Please cite this adapter using our [reference paper in the OpenFOAM Journal](https://doi.org/10.51560/ofj.v3.88) [1]. See the [preCICE literature guide](https://precice.org/fundamentals-literature-guide.html) for more details. +Please cite this adapter using our [reference paper in the OpenFOAM Journal](https://doi.org/10.51560/ofj.v3.88)[^1]. See the [preCICE literature guide](https://precice.org/fundamentals-literature-guide.html) for more details. ## History This project is actively maintained on [precice/openfoam-adapter](https://github.com/precice/openfoam-adapter). Current maintainers: [@MakisH](https://github.com/MakisH/) and [@DavidSCN](https://github.com/DavidSCN). -This adapter was developed as part of [Gerasimos Chourdakis' master's thesis](https://mediatum.ub.tum.de/1462269) [2]. -It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia Cheung ([master's thesis](https://www.simscale.com/) [3], in cooperation with [SimScale](https://www.simscale.com/)). +This adapter was developed as part of [Gerasimos Chourdakis' master's thesis](https://mediatum.ub.tum.de/1462269)[^2]. +It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia Cheung ([master's thesis](https://www.simscale.com/)[^3], in cooperation with [SimScale](https://www.simscale.com/)). -The fluid-structure interaction module was developed in close collaboration between Gerasimos Chourdakis and Derek Risseeuw (TU Delft), in the context of the [master's thesis of the latter](http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123) [4]. We would also like to thank David Schneider (Univ. Siegen / TUM) and Maximilian Müller (TU Braunschweig) for sharing the code and experience of their similar previous work. +The fluid-structure interaction module was developed in close collaboration between Gerasimos Chourdakis and Derek Risseeuw (TU Delft), in the context of the [master's thesis of the latter](http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123)[^4]. We would also like to thank David Schneider (Univ. Siegen / TUM) and Maximilian Müller (TU Braunschweig) for sharing the code and experience of their similar previous work. -The fluid-fluid coupling module was added by Gerasimos Chourdakis, in the context of his dissertation. [#67](https://github.com/precice/openfoam-adapter/pull/67). Further contributions in this direction by Markus Mühlhäußer ([master's thesis](https://mediatum.ub.tum.de/node?id=1696254&change_language=en) [5], [related publication](https://mediatum.ub.tum.de/node?id=1732401&change_language=en) [6]). +The fluid-fluid coupling module was added by Gerasimos Chourdakis, in the context of his dissertation. [#67](https://github.com/precice/openfoam-adapter/pull/67). Further contributions in this direction by Markus Mühlhäußer ([master's thesis](https://mediatum.ub.tum.de/node?id=1696254&change_language=en)[^5], [related publication](https://mediatum.ub.tum.de/node?id=1732401&change_language=en)[^6]). -The volume coupling functionality was contributed by Tina Vladimirova, in the context of her [interdisciplinary project](https://mediatum.ub.tum.de/1734883) [7], based on previous work by various community contributors. +The volume coupling functionality was contributed by Tina Vladimirova, in the context of her [interdisciplinary project](https://mediatum.ub.tum.de/1734883)[^7], based on previous work by various community contributors. The adapter is [easily extensible](https://precice.org/adapter-openfoam-extend.html). ### Related literature -[1] Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25.
+[^1]: Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25.
[DOI: 10.51560/ofj.v3.88](https://doi.org/10.51560/ofj.v3.88) -[2] Gerasimos Chourdakis. A general OpenFOAM adapter for the coupling library preCICE. Master's thesis, Department of Informatics, Technical University of Munich, 2017.
+[^2]: Gerasimos Chourdakis. A general OpenFOAM adapter for the coupling library preCICE. Master's thesis, Department of Informatics, Technical University of Munich, 2017.
URL: https://mediatum.ub.tum.de/1462269 -[3] Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016.
+[^3]: Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016.
URL: https://mediatum.ub.tum.de/1461907 -[4] Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019.
+[^4]: Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019.
URL: http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123 -[5] Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022.
+[^5]: Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022.
URL: https://mediatum.ub.tum.de/node?id=1696254&change_language=en -[6] Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023.
+[^6]: Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023.
[DOI: 10.23967/c.coupled.2023.014](https://doi.org/10.23967/c.coupled.2023.014) -[7] Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023.
+[^7]: Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023.
URL: https://mediatum.ub.tum.de/1734883 {% disclaimer %} From d660d9211929a223ceed17abd854f522164a0221 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Fri, 30 Jan 2026 15:27:21 +0100 Subject: [PATCH 36/70] Update PRECICE_REF in system-tests workflow Following the update of reference results in https://github.com/precice/tutorials/pull/698 --- .github/workflows/system-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/system-tests.yaml b/.github/workflows/system-tests.yaml index 4bc6aaaf..4d673e2f 100644 --- a/.github/workflows/system-tests.yaml +++ b/.github/workflows/system-tests.yaml @@ -40,6 +40,6 @@ jobs: uses: precice/tutorials/.github/workflows/run_testsuite_workflow.yml@develop with: suites: openfoam_adapter_pr - build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:v3.3.0,OPENFOAM_EXECUTABLE:openfoam2512,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} + build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:d199bb3,OPENFOAM_EXECUTABLE:openfoam2512,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} system_tests_branch: develop log_level: "DEBUG" From c16613254cb558841e68a6a056f298320bb26f6c Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:20:02 +0100 Subject: [PATCH 37/70] Mesh checkpointing refactor (#387) * Delete redundant/unused code. Remove all methods related to volume checkpointing. Remove unused mesh fields (meshSurfaceVectorFields_, meshVolVectorFields_, volScalarInternalFields_). * Mesh points checkpointing refactor. Make meshPoints a pointer instead of a reference, consistent with the rest of the checkpoints. * Add documentation about checkpointed fields to extend.md --------- Co-authored-by: Gerasimos Chourdakis --- Adapter.C | 286 ++---------------- Adapter.H | 66 +--- changelog-entries/387.md | 1 + docs/extend.md | 70 +++++ ...docs-adapter-openfoam-implicit-loop-src.md | 55 ++++ .../docs-adapter-openfoam-implicit-loop.png | Bin 0 -> 185854 bytes 6 files changed, 169 insertions(+), 309 deletions(-) create mode 100644 changelog-entries/387.md create mode 100644 docs/images/docs-adapter-openfoam-implicit-loop-src.md create mode 100644 docs/images/docs-adapter-openfoam-implicit-loop.png diff --git a/Adapter.C b/Adapter.C index 883e0e64..451189c9 100644 --- a/Adapter.C +++ b/Adapter.C @@ -636,13 +636,11 @@ void preciceAdapter::Adapter::adjustSolverTimeStepAndReadData() double tolerance = 1e-14; if (precice_->getMaxTimeStepSize() - timestepSolverDetermined > tolerance) { - // Add a bool 'subCycling = true' which is checked in the storeMeshPoints() function. adapterInfo( "The solver's timestep is smaller than the " "coupling timestep. Subcycling...", "info"); timestepSolver_ = timestepSolverDetermined; - // TODO subcycling is enabled. For FSI the oldVolumes must be written, which is normally not done. if (FSIenabled_) { adapterInfo( @@ -735,33 +733,23 @@ void preciceAdapter::Adapter::reloadCheckpointTime() void preciceAdapter::Adapter::storeMeshPoints() { - DEBUG(adapterInfo("Storing mesh points...")); - // TODO: In foam-extend, we would need "allPoints()". Check if this gives the same data. - meshPoints_ = mesh_.points(); - oldMeshPoints_ = mesh_.oldPoints(); - - /* - // TODO This is only required for subcycling. It should not be called when not subcycling!! - // Add a bool 'subcycling' which can be evaluated every timestep. - if ( !oldVolsStored && mesh_.foundObject("V00") ) // For Ddt schemes which use one previous timestep + if (!meshPoints_) { - setupMeshVolCheckpointing(); - oldVolsStored = true; + DEBUG(adapterInfo("Storing mesh points...")); + // Add points and oldPoints + meshPoints_ = new Foam::pointField(mesh_.points()); + meshOldPoints_ = new Foam::pointField(mesh_.oldPoints()); } - // Update any volume fields from the buffer to the checkpointed values (if already exists.) - */ - DEBUG(adapterInfo("Stored mesh points.")); if (mesh_.moving()) { - if (!meshCheckPointed) + if (!meshCheckPointed_) { // Set up the checkpoint for the mesh flux: meshPhi setupMeshCheckpointing(); - meshCheckPointed = true; + meshCheckPointed_ = true; } writeMeshCheckpoint(); - writeVolCheckpoint(); // Does not write anything unless subcycling. } } @@ -773,31 +761,16 @@ void preciceAdapter::Adapter::reloadMeshPoints() return; } - // In Foam::polyMesh::movePoints. - // TODO: The function movePoints overwrites the pointer to the old mesh. - // Therefore, if you revert the mesh, the oldpointer will be set to the points, which are the new values. - DEBUG(adapterInfo("Moving mesh points to their previous locations...")); + // Reload mesh points + const_cast(mesh_).movePoints(*meshPoints_); - // TODO - // Switch oldpoints on for pure physics. (is this required?). Switch off for better mesh deformation capabilities? - // const_cast(mesh_.points()) = oldMeshPoints_; - const_cast(mesh_).movePoints(meshPoints_); + // polyMesh.movePoints will only update oldPoints + // if (curMotionTimeIndex_ != time().timeIndex()) + const_cast(mesh_.oldPoints()) = *meshOldPoints_; - DEBUG(adapterInfo("Moved mesh points to their previous locations.")); - - // TODO The if statement can be removed in this case, but it is still included for clarity - if (meshCheckPointed) - { - readMeshCheckpoint(); - } + readMeshCheckpoint(); - /* // TODO This part should only be used when sybcycling. See the description in 'storeMeshPoints()' - // The if statement can be removed in this case, but it is still included for clarity - if ( oldVolsStored ) - { - readVolCheckpoint(); - } - */ + DEBUG(adapterInfo("Moved mesh points to their previous locations.")); } void preciceAdapter::Adapter::setupMeshCheckpointing() @@ -810,63 +783,10 @@ void preciceAdapter::Adapter::setupMeshCheckpointing() // delta // are updated by the function fvMesh::movePoints. Only the meshPhi needs checkpointing. DEBUG(adapterInfo("Creating a list of the mesh checkpointed fields...")); + // Add meshPhi (Face motion flux) + addMeshCheckpointField(const_cast(mesh_.phi())); - // Add meshPhi to the checkpointed fields - addMeshCheckpointField( - const_cast( - mesh_.phi())); -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Added " + mesh_.phi().name() + " in the list of checkpointed fields."); -#endif -} - -void preciceAdapter::Adapter::setupMeshVolCheckpointing() -{ - DEBUG(adapterInfo("Creating a list of the mesh volume checkpointed fields...")); - // Add the V0 and the V00 to the list of checkpointed fields. - // For V0 - addVolCheckpointField( - const_cast( - mesh_.V0())); -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Added " + mesh_.V0().name() + " in the list of checkpointed fields."); -#endif - // For V00 - addVolCheckpointField( - const_cast( - mesh_.V00())); -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Added " + mesh_.V00().name() + " in the list of checkpointed fields."); -#endif - - // Also add the buffer fields. - // TODO For V0 - /* addVolCheckpointFieldBuffer - ( - const_cast - ( - mesh_.V0() - ) - ); */ -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Added " + mesh_.V0().name() + " in the list of buffer checkpointed fields."); -#endif - // TODO For V00 - /* addVolCheckpointFieldBuffer - ( - const_cast - ( - mesh_.V00() - ) - );*/ -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Added " + mesh_.V00().name() + " in the list of buffer checkpointed fields."); -#endif + DEBUG(adapterInfo("Added " + mesh_.phi().name() + " to the list of checkpointed fields.")); } @@ -969,38 +889,10 @@ void preciceAdapter::Adapter::pruneCheckpointedFields() void preciceAdapter::Adapter::addMeshCheckpointField(surfaceScalarField& field) { - { - meshSurfaceScalarFields_.push_back(&field); - meshSurfaceScalarFieldCopies_.push_back(new surfaceScalarField(field)); - } -} - -void preciceAdapter::Adapter::addMeshCheckpointField(surfaceVectorField& field) -{ - { - meshSurfaceVectorFields_.push_back(&field); - meshSurfaceVectorFieldCopies_.push_back(new surfaceVectorField(field)); - } + meshSurfaceScalarFields_.push_back(&field); + meshSurfaceScalarFieldCopies_.push_back(new surfaceScalarField(field)); } -void preciceAdapter::Adapter::addMeshCheckpointField(volVectorField& field) -{ - { - meshVolVectorFields_.push_back(&field); - meshVolVectorFieldCopies_.push_back(new volVectorField(field)); - } -} - -// TODO Internal field for the V0 (volume old) and V00 (volume old-old) fields -void preciceAdapter::Adapter::addVolCheckpointField(volScalarField::Internal& field) -{ - { - volScalarInternalFields_.push_back(&field); - volScalarInternalFieldCopies_.push_back(new volScalarField::Internal(field)); - } -} - - void preciceAdapter::Adapter::addCheckpointField(volScalarField* field) { if (field) @@ -1286,10 +1178,7 @@ void preciceAdapter::Adapter::readCheckpoint() // NOTE: Add here other field types to read, if needed. -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Checkpoint was read. Time = " + std::to_string(runTime_.value())); -#endif + DEBUG(adapterInfo("Checkpoint was read. Time = " + std::to_string(runTime_.value()))); ACCUMULATE_TIMER(timeInCheckpointingRead_); @@ -1373,10 +1262,7 @@ void preciceAdapter::Adapter::writeCheckpoint() } // NOTE: Add here other types to write, if needed. -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Checkpoint for time t = " + std::to_string(runTime_.value()) + " was stored."); -#endif + DEBUG(adapterInfo("Checkpoint for time t = " + std::to_string(runTime_.value()) + " was stored.")); ACCUMULATE_TIMER(timeInCheckpointingWrite_); @@ -1387,11 +1273,9 @@ void preciceAdapter::Adapter::readMeshCheckpoint() { DEBUG(adapterInfo("Reading a mesh checkpoint...")); - // TODO only the meshPhi field is here, which is a surfaceScalarField. The other fields can be removed. - // Reload all the fields of type mesh surfaceScalarField + // Only the meshPhi field is here, which is a surfaceScalarField. for (uint i = 0; i < meshSurfaceScalarFields_.size(); i++) { - // Load the volume field *(meshSurfaceScalarFields_.at(i)) == *(meshSurfaceScalarFieldCopies_.at(i)); int nOldTimes(meshSurfaceScalarFields_.at(i)->nOldTimes()); @@ -1405,44 +1289,7 @@ void preciceAdapter::Adapter::readMeshCheckpoint() } } - // Reload all the fields of type mesh surfaceVectorField - for (uint i = 0; i < meshSurfaceVectorFields_.size(); i++) - { - // Load the volume field - *(meshSurfaceVectorFields_.at(i)) == *(meshSurfaceVectorFieldCopies_.at(i)); - - int nOldTimes(meshSurfaceVectorFields_.at(i)->nOldTimes()); - if (nOldTimes >= 1) - { - meshSurfaceVectorFields_.at(i)->oldTime() == meshSurfaceVectorFieldCopies_.at(i)->oldTime(); - } - if (nOldTimes == 2) - { - meshSurfaceVectorFields_.at(i)->oldTime().oldTime() == meshSurfaceVectorFieldCopies_.at(i)->oldTime().oldTime(); - } - } - - // Reload all the fields of type mesh volVectorField - for (uint i = 0; i < meshVolVectorFields_.size(); i++) - { - // Load the volume field - *(meshVolVectorFields_.at(i)) == *(meshVolVectorFieldCopies_.at(i)); - - int nOldTimes(meshVolVectorFields_.at(i)->nOldTimes()); - if (nOldTimes >= 1) - { - meshVolVectorFields_.at(i)->oldTime() == meshVolVectorFieldCopies_.at(i)->oldTime(); - } - if (nOldTimes == 2) - { - meshVolVectorFields_.at(i)->oldTime().oldTime() == meshVolVectorFieldCopies_.at(i)->oldTime().oldTime(); - } - } - -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Mesh checkpoint was read. Time = " + std::to_string(runTime_.value())); -#endif + DEBUG(adapterInfo("Mesh checkpoint was read. Time = " + std::to_string(runTime_.value()))); return; } @@ -1451,72 +1298,24 @@ void preciceAdapter::Adapter::writeMeshCheckpoint() { DEBUG(adapterInfo("Writing a mesh checkpoint...")); - // Store all the fields of type mesh surfaceScalar + // Store all the fields of type mesh surfaceScalar (phi) for (uint i = 0; i < meshSurfaceScalarFields_.size(); i++) { *(meshSurfaceScalarFieldCopies_.at(i)) == *(meshSurfaceScalarFields_.at(i)); } - // Store all the fields of type mesh surfaceVector - for (uint i = 0; i < meshSurfaceVectorFields_.size(); i++) - { - *(meshSurfaceVectorFieldCopies_.at(i)) == *(meshSurfaceVectorFields_.at(i)); - } - - // Store all the fields of type mesh volVector - for (uint i = 0; i < meshVolVectorFields_.size(); i++) - { - *(meshVolVectorFieldCopies_.at(i)) == *(meshVolVectorFields_.at(i)); - } - -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Mesh checkpoint for time t = " + std::to_string(runTime_.value()) + " was stored."); -#endif - - return; -} - -// TODO for the volumes of the mesh, check this part for subcycling. -void preciceAdapter::Adapter::readVolCheckpoint() -{ - DEBUG(adapterInfo("Reading the mesh volumes checkpoint...")); - - // Reload all the fields of type mesh volVectorField::Internal - for (uint i = 0; i < volScalarInternalFields_.size(); i++) - { - // Load the volume field - *(volScalarInternalFields_.at(i)) = *(volScalarInternalFieldCopies_.at(i)); - // There are no old times for the internal fields. - } - -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Mesh volumes were read. Time = " + std::to_string(runTime_.value())); -#endif - - return; -} + DEBUG(adapterInfo("Storing mesh points...")); -void preciceAdapter::Adapter::writeVolCheckpoint() -{ - DEBUG(adapterInfo("Writing a mesh volumes checkpoint...")); + // Store mesh points + // swap pointers + *(meshOldPoints_) = *(meshPoints_); + *(meshPoints_) = mesh_.points(); - // Store all the fields of type mesh volScalarField::Internal - for (uint i = 0; i < volScalarInternalFields_.size(); i++) - { - *(volScalarInternalFieldCopies_.at(i)) = *(volScalarInternalFields_.at(i)); - } - -#ifdef ADAPTER_DEBUG_MODE - adapterInfo( - "Mesh volumes checkpoint for time t = " + std::to_string(runTime_.value()) + " was stored."); -#endif + DEBUG(adapterInfo("Mesh checkpoint for time t = " + std::to_string(runTime_.value()) + " was stored.")); return; } - void preciceAdapter::Adapter::end() try { @@ -1606,28 +1405,6 @@ void preciceAdapter::Adapter::teardown() } meshSurfaceScalarFieldCopies_.clear(); - // meshSurfaceVector - for (uint i = 0; i < meshSurfaceVectorFieldCopies_.size(); i++) - { - delete meshSurfaceVectorFieldCopies_.at(i); - } - meshSurfaceVectorFieldCopies_.clear(); - - // meshVolVector - for (uint i = 0; i < meshVolVectorFieldCopies_.size(); i++) - { - delete meshVolVectorFieldCopies_.at(i); - } - meshVolVectorFieldCopies_.clear(); - - // TODO for the internal volume - // volScalarInternal - for (uint i = 0; i < volScalarInternalFieldCopies_.size(); i++) - { - delete volScalarInternalFieldCopies_.at(i); - } - volScalarInternalFieldCopies_.clear(); - // volTensorField for (uint i = 0; i < volTensorFieldCopies_.size(); i++) { @@ -1659,6 +1436,9 @@ void preciceAdapter::Adapter::teardown() // NOTE: Add here delete for other types, if needed checkpointing_ = false; + + delete meshPoints_; + delete meshOldPoints_; } // Delete the CHT module diff --git a/Adapter.H b/Adapter.H index fcd6f0e8..e718bc61 100644 --- a/Adapter.H +++ b/Adapter.H @@ -88,6 +88,9 @@ private: //- Switch for checkpointing bool checkpointing_ = false; + //- Moving mesh checkpointing + bool meshCheckPointed_ = false; + //- Type of timestep (fixed, adjustable) bool adjustableTimestep_; @@ -141,45 +144,18 @@ private: //- Checkpointed time (index) Foam::label couplingIterationTimeIndex_; - //- Checkpointed mesh points - Foam::pointField meshPoints_; - Foam::pointField oldMeshPoints_; - bool meshCheckPointed = false; - - // TODO: Currently unused, see storeMeshPoints(). - //- Checkpointed mesh volume - // bool oldVolsStored = false; - // Foam::volScalarField::Internal * oldVols_; - // Foam::volScalarField::Internal * oldOldVols_; - // int curTimeIndex_ = 0; - // Vectors of pointers to the checkpointed mesh fields and their copies - //- Checkpointed surfaceScalarField mesh fields + //- Checkpointed mesh points, oldPoints + Foam::pointField* meshPoints_; + Foam::pointField* meshOldPoints_; + + //- Checkpointed surfaceScalarField mesh fields (phi) std::vector meshSurfaceScalarFields_; //- Checkpointed surfaceScalarField mesh fields (copies) std::vector meshSurfaceScalarFieldCopies_; - //- Checkpointed surfaceVectorField mesh fields - std::vector meshSurfaceVectorFields_; - - //- Checkpointed surfaceVectorField mesh fields (copies) - std::vector meshSurfaceVectorFieldCopies_; - - //- Checkpointed volVectorField mesh fields - std::vector meshVolVectorFields_; - - //- Checkpointed volVectorField mesh fields (copies) - std::vector meshVolVectorFieldCopies_; - - // TODO checkpoint for the V0 (Old volume) and V00 (Old-Old volume) fields. - //- Checkpointed volScalarField mesh fields - std::vector volScalarInternalFields_; - - //- Checkpointed volScalarField mesh fields (copies) - std::vector volScalarInternalFieldCopies_; - // Vectors of pointers to the checkpointed fields and their copies //- Checkpointed volScalarField fields @@ -291,9 +267,6 @@ private: //- Configure the mesh checkpointing void setupMeshCheckpointing(); - //- Configure the mesh checkpointing - void setupMeshVolCheckpointing(); - //- Configure the checkpointing void setupCheckpointing(); @@ -314,21 +287,9 @@ private: void reloadMeshPoints(); // Add mesh checkpoint fields, depending on the type - //- Add a surfaceScalarField mesh field - void addMeshCheckpointField(surfaceScalarField& field); - - //- Add a surfaceVectorField mesh field - void addMeshCheckpointField(surfaceVectorField& field); - //- Add a volVectorField mesh field - void addMeshCheckpointField(volVectorField& field); - - // TODO V0 and V00 checkpointed field. - //- Add the V0 and V00 checkpoint fields - void addVolCheckpointField(volScalarField::Internal& field); - // void addVolCheckpointFieldBuffer(volScalarField::Internal & field); - - // Add checkpoint fields, depending on the type + //- Add a surfaceScalarField mesh field (phi) + void addMeshCheckpointField(Foam::surfaceScalarField& field); //- Add a volScalarField to checkpoint void addCheckpointField(volScalarField* field); @@ -375,13 +336,6 @@ private: //- Write the checkpoint - store the fields and time void writeCheckpoint(); - // TODO Probably these can be included to the mesh checkpoints. - //- Read the volume checkpoint - restore the mesh volume fields - void readVolCheckpoint(); - - //- Write the volume checkpoint to a buffer - restore the mesh volume fields - void writeVolCheckpoint(); - //- Destroy the preCICE interface and delete the allocated // memory in a proper way. Called by the destructor. void teardown(); diff --git a/changelog-entries/387.md b/changelog-entries/387.md new file mode 100644 index 00000000..6c12fb23 --- /dev/null +++ b/changelog-entries/387.md @@ -0,0 +1 @@ +- Mesh points checkpointing refactor. Removed unused code related to mesh field and mesh volume checkpointing [#387](https://github.com/precice/openfoam-adapter/pull/387) \ No newline at end of file diff --git a/docs/extend.md b/docs/extend.md index 6d7e66b7..5a00fc6f 100644 --- a/docs/extend.md +++ b/docs/extend.md @@ -28,3 +28,73 @@ section of the `Make/options`. Since the adapter is a shared library, another missing library will trigger an "undefined symbol" runtime error. See also the notes and discussion in [issue #7: Create a module for fluid-structure interaction](https://github.com/precice/openfoam-adapter/issues/7). + +## Checkpointing + +In implicit coupling, the adapter manages the coupling loop, which entails checkpointing the simulation state (fields and mesh). +The checkpoints are stored and reloaded to repeat implicit time steps. +As the coupling data is exchanged, the adapter repeats the time step until convergence. +See this diagram for an overview: + +![Implicit checkpointing diagram](images/docs-adapter-openfoam-implicit-loop.png) + +While the adapter already takes care of checkpointing all of the usual field and mesh data, your solver or use specific scenario might need to checkpoint more objects. +Start by looking at the `setupCheckpointing()` function in the [Adapter.C](https://github.com/precice/openfoam-adapter/blob/master/Adapter.C). + +### Checkpointed fields + +For reference, here is a list of known checkpointed and non-checkpointed fields in the OpenFOAM adapter. The adapter automatically finds and checkpoints all objects registered in the OpenFOAM object registry (`mesh_.thisDb()`) that match the following types: + +#### Volume fields + +- `volScalarField`: e.g., `p`, `T`, `k`, `omega`, `nut`, `nu`, `rho` +- `volVectorField`: e.g., `U`, `Force`, `cellDisplacement` +- `volTensorField`: e.g., `grad(U)` (if exists, relevant closed issue [#158](https://github.com/precice/openfoam-adapter/issues/158)) +- `volSymmTensorField`: e.g., `R` (Reynolds stress) + +#### Surface fields + +- `surfaceScalarField`: e.g., `phi` (flux), `faceDiffusivity` +- `surfaceVectorField`: e.g., `Uf` +- `surfaceTensorField` + +#### Point fields + +- `pointScalarField` +- `pointVectorField`: e.g., `pointDisplacement` +- `pointTensorField` + +The adapter will only create the checkpoint list once during initialization: After the first solver step, it is assumed that no new fields have been created that need to be checkpointed. + +#### Mesh data + +To handle mesh motion (ALE), the adapter explicitly checkpoints: + +- Mesh Points `mesh_.points()` and `mesh_.oldPoints()` +- Mesh Motion Flux `mesh_.phi()` + +### Non-checkpointed fields + +Certain fields are purposely not checkpointed, see issue [#324](https://github.com/precice/openfoam-adapter/issues/324) and explanation in [PR #387](https://github.com/precice/openfoam-adapter/pull/387). This is either because they are re-calculated when needed "on-demand" or because they are essentially static (dictionaries assumed unchanging). + +#### Derived mesh fields + +The following fields are recalculated by OpenFOAM when `fvMesh::movePoints` is called, so the adapter does not store them: + +- `C` (Cell Centers) +- `Cf` (Face Centers) +- `Sf` (Face Area Vectors) +- `magSf` (Face Area Magnitudes) +- `delta` (Face-Center to Cell-Center distances) +- `V`, `V0`, `V00` (Cell Volumes): These are already properly checkpointed by OpenFOAM itself, except when subcycling (needs further handling when subcycling - [related discussion](https://github.com/precice/openfoam-adapter/pull/369#issuecomment-3748773060)). + +#### Dictionaries + +The adapter does not checkpoint `IOdictionary` objects, assuming they are unchanged over the time windows or handled by OpenFOAM otherwise. Examples: + +- `controlDict` +- `transportProperties` +- `turbulenceProperties` +- `fvSchemes`, `fvSolution` +- `dynamicMeshDict` +- `preciceDict` diff --git a/docs/images/docs-adapter-openfoam-implicit-loop-src.md b/docs/images/docs-adapter-openfoam-implicit-loop-src.md new file mode 100644 index 00000000..6ff0fdc7 --- /dev/null +++ b/docs/images/docs-adapter-openfoam-implicit-loop-src.md @@ -0,0 +1,55 @@ +# Implicit coupling diagram + +```mermaid +flowchart TD + subgraph loop ["Implicit Iteration Loop for Window t"] + direction TB + B{"OpenFOAM Solve"} + C["Adapter::execute()"] + D["Adapter:writeCouplingData()"] + E["preCICE::advance()"] + F{"preCICE - Rollback Needed?"} + G["Adapter::readCheckpoint() (Rollback)"] + I["Adapter - Read NEW Coupled Solver Data"] + + J{"preCICE - Save State Needed?"} + K["Adapter::writeCheckpoint()"] + L["Adapter - Read FINAL/Next Window Data"] + + end + + B --> C; + C --> D; + D --> E; + E --> F; + + F -- Yes --> G; + G --> I; + I --> B; + + F -- No --> J; %% Converged or explicit + K --> L; + J -- Yes --> K; + J -- No --> L; + + L --> M["OpenFOAM - Advance to Next Window t+dt"]; + + %% Styles + style B fill:#f9f,stroke:#333 %% Solver Solve + style M fill:#f9f,stroke:#333 %% Solver Advance Window + + style C fill:#eef,stroke:#333 %% Adapter::execute() entry + + style D fill:#ffdacc,stroke:#333 %% Write Coupling Data + style K fill:#ffdacc,stroke:#333 %% Write Checkpoint + + style E fill:#eee,stroke:#333 %% preCICE advance + style F fill:#eee,stroke:#333,stroke-dasharray: 5 5 %% preCICE Rollback Check + style J fill:#eee,stroke:#333,stroke-dasharray: 5 5 %% preCICE Save Check + + style G fill:#cfc,stroke:#333 %% Read Checkpoint + style I fill:#cfc,stroke:#333 %% Read NEW Data + style L fill:#cfc,stroke:#333 %% Read FINAL/Next Data + + style loop stroke:#aaa,stroke-width:1px,stroke-dasharray: 3 +``` diff --git a/docs/images/docs-adapter-openfoam-implicit-loop.png b/docs/images/docs-adapter-openfoam-implicit-loop.png new file mode 100644 index 0000000000000000000000000000000000000000..cce3bd023e45dbdfbc0fa3cba53bf5cdcaee075f GIT binary patch literal 185854 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

  • Q_rGV#PktFS2ClOxWAwlherSK$h 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 JZcD=?b?GxN)ht=`yxU?1dI96 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 CW*sX4 literal 0 HcmV?d00001 From f7e2d6cbaab154ed180d2bb0646ea2dc56763e54 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 3 Feb 2026 16:11:35 +0100 Subject: [PATCH 38/70] Allow for case-insensitive exchanged field names (#391) closes #390 --- CHT/CHT.C | 16 +++++++-------- FF/FF.C | 44 ++++++++++++++++++++-------------------- FSI/FSI.C | 16 +++++++-------- Utilities.C | 8 ++++++++ Utilities.H | 3 +++ changelog-entries/390.md | 1 + docs/config.md | 5 +++++ 7 files changed, 55 insertions(+), 38 deletions(-) create mode 100644 changelog-entries/390.md diff --git a/CHT/CHT.C b/CHT/CHT.C index e51342dd..cef3052d 100644 --- a/CHT/CHT.C +++ b/CHT/CHT.C @@ -122,21 +122,21 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addWriters(std::string dataName { bool found = true; // Set to false later, if needed. - if (dataName.find("Sink-Temperature") == 0) + if (matchingStrings(dataName, "Sink-Temperature")) { interface->addCouplingDataWriter( dataName, new SinkTemperature(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Sink Temperature.")); } - else if (dataName.find("Temperature") == 0) + else if (matchingStrings(dataName, "Temperature")) { interface->addCouplingDataWriter( dataName, new Temperature(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Temperature.")); } - else if (dataName.find("Heat-Flux") == 0) + else if (matchingStrings(dataName, "Heat-Flux")) { if (solverType_.compare("compressible") == 0) { @@ -165,7 +165,7 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addWriters(std::string dataName "error"); } } - else if (dataName.find("Heat-Transfer-Coefficient") == 0) + else if (matchingStrings(dataName, "Heat-Transfer-Coefficient")) { if (solverType_.compare("compressible") == 0) { @@ -212,21 +212,21 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addReaders(std::string dataName { bool found = true; // Set to false later, if needed. - if (dataName.find("Sink-Temperature") == 0) + if (matchingStrings(dataName, "Sink-Temperature")) { interface->addCouplingDataReader( dataName, new SinkTemperature(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Sink Temperature.")); } - else if (dataName.find("Temperature") == 0) + else if (matchingStrings(dataName, "Temperature")) { interface->addCouplingDataReader( dataName, new Temperature(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Temperature.")); } - else if (dataName.find("Heat-Flux") == 0) + else if (matchingStrings(dataName, "Heat-Flux")) { if (solverType_.compare("compressible") == 0) { @@ -255,7 +255,7 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addReaders(std::string dataName "error"); } } - else if (dataName.find("Heat-Transfer-Coefficient") == 0) + else if (matchingStrings(dataName, "Heat-Transfer-Coefficient")) { if (solverType_.compare("compressible") == 0) { diff --git a/FF/FF.C b/FF/FF.C index 713e1c56..49a2bc3d 100644 --- a/FF/FF.C +++ b/FF/FF.C @@ -129,70 +129,70 @@ bool preciceAdapter::FF::FluidFluid::addWriters(std::string dataName, Interface* { bool found = true; // Set to false later, if needed. - if (dataName.find("VelocityGradient") == 0) + if (matchingStrings(dataName, "VelocityGradient")) { interface->addCouplingDataWriter( dataName, new VelocityGradient(mesh_, nameU_)); DEBUG(adapterInfo("Added writer: Velocity Gradient.")); } - else if (dataName.find("Velocity") == 0) + else if (matchingStrings(dataName, "Velocity")) { interface->addCouplingDataWriter( dataName, new Velocity(mesh_, nameU_, namePhi_, fluxCorrection_)); DEBUG(adapterInfo("Added writer: Velocity.")); } - else if (dataName.find("PressureGradientFull") == 0) + else if (matchingStrings(dataName, "PressureGradientFull")) { interface->addCouplingDataWriter( dataName, new PressureGradientFull(mesh_, nameP_)); DEBUG(adapterInfo("Added writer: Full Pressure Gradient.")); } - else if (dataName.find("PressureGradient") == 0) + else if (matchingStrings(dataName, "PressureGradient")) { interface->addCouplingDataWriter( dataName, new PressureGradient(mesh_, nameP_)); DEBUG(adapterInfo("Added writer: Pressure Gradient.")); } - else if (dataName.find("Pressure") == 0) + else if (matchingStrings(dataName, "Pressure")) { interface->addCouplingDataWriter( dataName, new Pressure(mesh_, nameP_)); DEBUG(adapterInfo("Added writer: Pressure.")); } - else if (dataName.find("FlowTemperatureGradient") == 0) + else if (matchingStrings(dataName, "FlowTemperatureGradient")) { interface->addCouplingDataWriter( dataName, new TemperatureGradient(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Flow Temperature Gradient.")); } - else if (dataName.find("FlowTemperature") == 0) + else if (matchingStrings(dataName, "FlowTemperature")) { interface->addCouplingDataWriter( dataName, new Temperature(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Flow Temperature.")); } - else if (dataName.find("AlphaGradient") == 0) + else if (matchingStrings(dataName, "AlphaGradient")) { interface->addCouplingDataWriter( dataName, new AlphaGradient(mesh_, nameAlpha_)); DEBUG(adapterInfo("Added writer: Alpha Gradient.")); } - else if (dataName.find("Alpha") == 0) + else if (matchingStrings(dataName, "Alpha")) { interface->addCouplingDataWriter( dataName, new Alpha(mesh_, nameAlpha_)); DEBUG(adapterInfo("Added writer: Alpha.")); } - else if (dataName.find("Phi") == 0) + else if (matchingStrings(dataName, "Phi")) { interface->addCouplingDataWriter( dataName, @@ -217,84 +217,84 @@ bool preciceAdapter::FF::FluidFluid::addReaders(std::string dataName, Interface* { bool found = true; // Set to false later, if needed. - if (dataName.find("VelocityGradient") == 0) + if (matchingStrings(dataName, "VelocityGradient")) { interface->addCouplingDataReader( dataName, new VelocityGradient(mesh_, nameU_)); DEBUG(adapterInfo("Added reader: VelocityGradient.")); } - else if (dataName.find("Velocity") == 0) + else if (matchingStrings(dataName, "Velocity")) { interface->addCouplingDataReader( dataName, new Velocity(mesh_, nameU_, namePhi_)); DEBUG(adapterInfo("Added reader: Velocity.")); } - else if (dataName.find("PressureGradient") == 0) + else if (matchingStrings(dataName, "PressureGradient")) { interface->addCouplingDataReader( dataName, new PressureGradient(mesh_, nameP_)); DEBUG(adapterInfo("Added reader: Pressure Gradient.")); } - else if (dataName.find("Pressure") == 0) + else if (matchingStrings(dataName, "Pressure")) { interface->addCouplingDataReader( dataName, new Pressure(mesh_, nameP_)); DEBUG(adapterInfo("Added reader: Pressure.")); } - else if (dataName.find("FlowTemperatureGradient") == 0) + else if (matchingStrings(dataName, "FlowTemperatureGradient")) { interface->addCouplingDataReader( dataName, new TemperatureGradient(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Flow Temperature Gradient.")); } - else if (dataName.find("FlowTemperature") == 0) + else if (matchingStrings(dataName, "FlowTemperature")) { interface->addCouplingDataReader( dataName, new Temperature(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Flow Temperature.")); } - else if (dataName.find("AlphaGradient") == 0) + else if (matchingStrings(dataName, "AlphaGradient")) { interface->addCouplingDataReader( dataName, new AlphaGradient(mesh_, nameAlpha_)); DEBUG(adapterInfo("Added reader: Alpha Gradient.")); } - else if (dataName.find("Alpha") == 0) + else if (matchingStrings(dataName, "Alpha")) { interface->addCouplingDataReader( dataName, new Alpha(mesh_, nameAlpha_)); DEBUG(adapterInfo("Added reader: Alpha.")); } - else if (dataName.find("Phi") == 0) + else if (matchingStrings(dataName, "Phi")) { interface->addCouplingDataReader( dataName, new Phi(mesh_, namePhi_)); DEBUG(adapterInfo("Added reader: Phi.")); } - else if (dataName.find("DragForce") == 0) + else if (matchingStrings(dataName, "DragForce")) { interface->addCouplingDataReader( dataName, new DragForce(mesh_, nameDragForce_)); DEBUG(adapterInfo("Added reader: DragForce.")); } - else if (dataName.find("ExplicitMomentum") == 0) + else if (matchingStrings(dataName, "ExplicitMomentum")) { interface->addCouplingDataReader( dataName, new ExplicitMomentum(mesh_, nameExplicitMomentum_)); DEBUG(adapterInfo("Added reader: ExplicitMomentum.")); } - else if (dataName.find("ImplicitMomentum") == 0) + else if (matchingStrings(dataName, "ImplicitMomentum")) { interface->addCouplingDataReader( dataName, diff --git a/FSI/FSI.C b/FSI/FSI.C index 0681896f..16aa88c3 100644 --- a/FSI/FSI.C +++ b/FSI/FSI.C @@ -134,7 +134,7 @@ bool preciceAdapter::FSI::FluidStructureInteraction::addWriters(std::string data { bool found = true; // Set to false later, if needed. - if (dataName.find("Force") == 0) + if (matchingStrings(dataName, "Force")) { interface->addCouplingDataWriter( dataName, @@ -142,21 +142,21 @@ bool preciceAdapter::FSI::FluidStructureInteraction::addWriters(std::string data ); DEBUG(adapterInfo("Added writer: Force.")); } - else if (dataName.find("DisplacementDelta") == 0) + else if (matchingStrings(dataName, "DisplacementDelta")) { interface->addCouplingDataWriter( dataName, new DisplacementDelta(mesh_, namePointDisplacement_, nameCellDisplacement_)); DEBUG(adapterInfo("Added writer: DisplacementDelta.")); } - else if (dataName.find("Displacement") == 0) + else if (matchingStrings(dataName, "Displacement")) { interface->addCouplingDataWriter( dataName, new Displacement(mesh_, namePointDisplacement_, nameCellDisplacement_)); DEBUG(adapterInfo("Added writer: Displacement.")); } - else if (dataName.find("Stress") == 0) + else if (matchingStrings(dataName, "Stress")) { interface->addCouplingDataWriter( dataName, @@ -182,7 +182,7 @@ bool preciceAdapter::FSI::FluidStructureInteraction::addReaders(std::string data { bool found = true; // Set to false later, if needed. - if (dataName.find("Force") == 0) + if (matchingStrings(dataName, "Force")) { interface->addCouplingDataReader( dataName, @@ -190,21 +190,21 @@ bool preciceAdapter::FSI::FluidStructureInteraction::addReaders(std::string data ); DEBUG(adapterInfo("Added reader: Force.")); } - else if (dataName.find("DisplacementDelta") == 0) + else if (matchingStrings(dataName, "DisplacementDelta")) { interface->addCouplingDataReader( dataName, new DisplacementDelta(mesh_, namePointDisplacement_, nameCellDisplacement_)); DEBUG(adapterInfo("Added reader: DisplacementDelta.")); } - else if (dataName.find("Displacement") == 0) + else if (matchingStrings(dataName, "Displacement")) { interface->addCouplingDataReader( dataName, new Displacement(mesh_, namePointDisplacement_, nameCellDisplacement_)); DEBUG(adapterInfo("Added reader: Displacement.")); } - else if (dataName.find("Stress") == 0) + else if (matchingStrings(dataName, "Stress")) { interface->addCouplingDataReader( dataName, diff --git a/Utilities.C b/Utilities.C index b1533d6d..603b3f99 100644 --- a/Utilities.C +++ b/Utilities.C @@ -1,4 +1,5 @@ #include "Utilities.H" +#include using namespace Foam; @@ -64,3 +65,10 @@ void adapterInfo(const std::string message, const std::string level) return; } + +bool matchingStrings(std::string s, std::string match) +{ + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); + std::transform(match.begin(), match.end(), match.begin(), [](unsigned char c) { return std::tolower(c); }); + return s.find(match) == 0; +} \ No newline at end of file diff --git a/Utilities.H b/Utilities.H index 8099d33d..8b852923 100644 --- a/Utilities.H +++ b/Utilities.H @@ -41,4 +41,7 @@ void adapterInfo(const std::string message, const std::string level = "debug"); +// Returns true if the match is found in the beginning of the string s, ignoring case +bool matchingStrings(std::string s, std::string match); + #endif diff --git a/changelog-entries/390.md b/changelog-entries/390.md new file mode 100644 index 00000000..33d4548c --- /dev/null +++ b/changelog-entries/390.md @@ -0,0 +1 @@ +- Add support for case-insensitive field names (e.g., both `DISPLACEMENTS` and `Displacements`) [#390](https://github.com/precice/openfoam-adapter/pull/390). diff --git a/docs/config.md b/docs/config.md index 7e1c2357..cdddf954 100644 --- a/docs/config.md +++ b/docs/config.md @@ -137,6 +137,11 @@ For fluid-structure interaction, coupled quantities can be: You will run into problems when you use `Displacement(Delta)` as write data set and execute RBF mappings in parallel. This would affect users who use OpenFOAM and the adapter as the Solid participant in order to compute solid mechanics with OpenFOAM (currently not officially supported at all). Have a look [at this issue on GitHub](https://github.com/precice/openfoam-adapter/issues/153) for details. {% endwarning %} +{% tip %} +The `writeData` and `readData` names are case-insensitive since v1.4.0. This means that both `TEMPERATURE` and `Temperature` are valid, for example. +Additionally, since earlier versions, only the beginning of the name needs to match: `Temperatures0` is valid and matched to the temperature reader/writer, for example. +{% endtip %} + ## Configuration of the OpenFOAM case A few changes are required in the configuration of an OpenFOAM case, in order to specify the interfaces and load the adapter. For some solvers, additional parameters may be needed (see "advanced configuration"). From 892c715c5f625d6b73aa7b22da4768e96dc6a385 Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Mon, 9 Feb 2026 15:42:51 +0100 Subject: [PATCH 39/70] Parse both legacy and schema-based preciceDict configuration files (#354) --- Adapter.C | 86 ++++++++++++++++++++++++++++++++++------ Adapter.H | 16 +++++++- changelog-entries/354.md | 1 + docs/config.md | 21 ++++++++++ 4 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 changelog-entries/354.md diff --git a/Adapter.C b/Adapter.C index 451189c9..3e072e19 100644 --- a/Adapter.C +++ b/Adapter.C @@ -16,6 +16,69 @@ preciceAdapter::Adapter::Adapter(const Time& runTime, const fvMesh& mesh) return; } +void preciceAdapter::Adapter::readFieldConfigs(const std::string& listName, Foam::ITstream& stream, std::vector& configs) +{ + // Perform check on whether read/writeData is a list + if (stream.peek() == token::BEGIN_LIST) + { + token _t; + stream >> _t; // First token is '(', which we throw away + + // Read stream until end of list + while (stream.peek() != token::END_LIST && !stream.eof()) + { + // Next token is always a word (the data name) + word dataName; + stream >> dataName; + + struct FieldConfig FieldConfig; + + // If next token is '{', we have a dictionary (new schema). + // We create a dictionary from the stream `dictionary dict(stream);` + // The dictionary must contain the 'name'. + // If 'solver_name' is not specified, it defaults to the same as 'name'. + // 'operation' defaults to 'value'. + // Note: Currently, the modules FF, CHT, and FSI do not use solver_name/operation. + if (stream.peek() == token::BEGIN_BLOCK) + { + dictionary dict(stream); + FieldConfig.name = dict.get("name"); // The 'name' entry is mandatory. + FieldConfig.solver_name = dict.lookupOrDefault("solver_name", FieldConfig.name); + FieldConfig.operation = dict.lookupOrDefault("operation", "value"); + try + { + FieldConfig.flip_normal = dict.lookupOrDefault("flip-normal", false); + } + catch (const Foam::IOerror& e) + { + adapterInfo("Error parsing 'flip-normal' for field " + dataName + "\n" + e.message(), "error"); + } + } + // Else, we have a simple word entry (legacy schema/backwards compatibility). + else + { + FieldConfig.name = dataName; + FieldConfig.solver_name = "Undefined (legacy mode)"; + FieldConfig.operation = "Undefined (legacy mode)"; + FieldConfig.flip_normal = false; + } + + configs.push_back(FieldConfig); + + DEBUG(adapterInfo(" - " + dataName)); + DEBUG(adapterInfo(" name: " + FieldConfig.name)); + DEBUG(adapterInfo(" solver_name: " + FieldConfig.solver_name)); + DEBUG(adapterInfo(" operation : " + FieldConfig.operation)); + DEBUG(adapterInfo(" flip-normal: " + std::string(FieldConfig.flip_normal ? "true" : "false"))); + } + stream >> _t; // Last token ')' + } + else + { + adapterInfo(listName + " must be a list", "error"); + } +} + void preciceAdapter::Adapter::configFileRead() { @@ -133,21 +196,20 @@ void preciceAdapter::Adapter::configFileRead() return; } - DEBUG(adapterInfo(" writeData : ")); - auto writeData = interfaceDict.lookupOrDefault("writeData", wordList()); - for (auto writeDatum : writeData) + if (interfaceDict.found("writeData")) { - interfaceConfig.writeData.push_back(writeDatum); - DEBUG(adapterInfo(" - " + writeDatum)); + DEBUG(adapterInfo(" writeData : ")); + ITstream writeDataStream = interfaceDict.lookup("writeData"); + readFieldConfigs("writeData", writeDataStream, interfaceConfig.writeData); } - DEBUG(adapterInfo(" readData : ")); - auto readData = interfaceDict.lookupOrDefault("readData", wordList()); - for (auto readDatum : readData) + if (interfaceDict.found("readData")) { - interfaceConfig.readData.push_back(readDatum); - DEBUG(adapterInfo(" - " + readDatum)); + DEBUG(adapterInfo(" readData : ")); + ITstream readDataStream = interfaceDict.lookup("readData"); + readFieldConfigs("readData", readDataStream, interfaceConfig.readData); } + interfacesConfig_.push_back(interfaceConfig); } } @@ -267,7 +329,7 @@ try DEBUG(adapterInfo("Adding coupling data writers...")); for (uint j = 0; j < interfacesConfig_.at(i).writeData.size(); j++) { - std::string dataName = interfacesConfig_.at(i).writeData.at(j); + std::string dataName = interfacesConfig_.at(i).writeData.at(j).name; unsigned int inModules = 0; @@ -308,7 +370,7 @@ try DEBUG(adapterInfo("Adding coupling data readers...")); for (uint j = 0; j < interfacesConfig_.at(i).readData.size(); j++) { - std::string dataName = interfacesConfig_.at(i).readData.at(j); + std::string dataName = interfacesConfig_.at(i).readData.at(j).name; unsigned int inModules = 0; diff --git a/Adapter.H b/Adapter.H index e718bc61..b480318c 100644 --- a/Adapter.H +++ b/Adapter.H @@ -37,6 +37,15 @@ class Adapter { private: + //- field options + struct FieldConfig + { + std::string name; + std::string solver_name; + std::string operation; + bool flip_normal; + }; + //- Structure of the configuration of each coupling interface. // Every interface needs to know the coupling mesh, the OpenFOAM // patches that consist the coupling surface and the kinds @@ -48,8 +57,8 @@ private: bool meshConnectivity; std::vector patchNames; std::vector cellSetNames; - std::vector writeData; - std::vector readData; + std::vector writeData; + std::vector readData; }; //- Configuration interfaces @@ -226,6 +235,9 @@ private: //- Read the adapter's configuration file void configFileRead(); + //- Helper to read a list of field configurations, which can contain either words, dictionaries, or both + void readFieldConfigs(const std::string& listName, Foam::ITstream& stream, std::vector& configs); + //- Check the adapter's configuration file bool configFileCheck(const std::string adapterConfigFileName); diff --git a/changelog-entries/354.md b/changelog-entries/354.md new file mode 100644 index 00000000..ae995013 --- /dev/null +++ b/changelog-entries/354.md @@ -0,0 +1 @@ +- Updated `preciceDict` parsing for upcoming schema changes. `readData` and `writeData` are now mixed lists of words and/or dictionaries. Dictionaries allow specifying additional options like `solver_name`, `operation`, and `flip-normal` for each field. Backwards compatibility with old schema `wordList` parsing. [#354](https://github.com/precice/openfoam-adapter/pull/354) diff --git a/docs/config.md b/docs/config.md index cdddf954..6f95ea0d 100644 --- a/docs/config.md +++ b/docs/config.md @@ -631,3 +631,24 @@ with the adapter configuration file usually named as `precice-adapter-config.yml We moved to a OpenFOAM dictionary format in [#105](https://github.com/precice/openfoam-adapter/pull/105), to reduce the dependencies. You may also find the [tutorials #69](https://github.com/precice/tutorials/pull/69) to be a useful reference (file changes). + +## Upcoming changes to the configuration format + +We are currently working on porting the adapter configuration file to the new [adapter configuration schema](https://github.com/precice/preeco-orga/tree/main/adapter-config-schema). Since v1.4.0, `readData` and `writeData` support parsing both the new format. Additional options (`solver_name`, `operation` and `flip-normal`) can be specified in the dictionaries . For example, the data `name` as known by preCICE can be different than the `solver_name` known by OpenFOAM. However, the new options are not yet functionally supported by the current modules FF, CHT and FSI. The legacy word list format is still supported, and both formats can even be mixed: + +```cpp +readData +( + // New dictionary entry + Velocity1 + { + name Velocity; // Data name as defined in preCICE config + solver_name U; // Optional: Field name in OpenFOAM (defaults to same as 'name') + operation value; // Optional: Operation to perform on data (defaults to 'value') + flip-normal false; // Optional: Flip the normal direction (defaults to 'false') + } + + // Legacy word entry + Temperature +); +``` From ee554ebffb6bf3975f5d6ddeda6e8a448e5df2c2 Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:26:10 +0100 Subject: [PATCH 40/70] Generic Read Write module (#346) --- Adapter.C | 65 +++++- Adapter.H | 17 +- Interface.H | 9 + Make/files | 1 + changelog-entries/346.md | 1 + docs/config.md | 49 +++- module-generic/Generic.C | 118 ++++++++++ module-generic/Generic.H | 55 +++++ module-generic/ModuleGeneric.C | 3 + module-generic/ReadWrite.C | 404 +++++++++++++++++++++++++++++++++ module-generic/ReadWrite.H | 56 +++++ 11 files changed, 762 insertions(+), 16 deletions(-) create mode 100644 changelog-entries/346.md create mode 100644 module-generic/Generic.C create mode 100644 module-generic/Generic.H create mode 100644 module-generic/ModuleGeneric.C create mode 100644 module-generic/ReadWrite.C create mode 100644 module-generic/ReadWrite.H diff --git a/Adapter.C b/Adapter.C index 3e072e19..45a696a8 100644 --- a/Adapter.C +++ b/Adapter.C @@ -126,6 +126,11 @@ void preciceAdapter::Adapter::configFileRead() { FFenabled_ = true; } + + if (module == "generic") + { + genericModuleEnabled_ = true; + } } // Every interface is a subdictionary of "interfaces", @@ -217,6 +222,15 @@ void preciceAdapter::Adapter::configFileRead() // NOTE: set the switch for your new module here + if (genericModuleEnabled_) + { + Generic_ = new Generic::GenericInterface(mesh_); + if (!Generic_->configure(preciceDict)) + { + return; + } + } + // If the CHT module is enabled, create it, read the // CHT-specific options and configure it. if (CHTenabled_) @@ -270,7 +284,7 @@ void preciceAdapter::Adapter::configFileRead() // NOTE: Create your module and read any options specific to it here - if (!CHTenabled_ && !FSIenabled_ && !FFenabled_) // NOTE: Add your new switch here + if (!CHTenabled_ && !FSIenabled_ && !FFenabled_ && !genericModuleEnabled_) // NOTE: Add your new switch here { adapterInfo("No module is enabled.", "error"); return; @@ -329,7 +343,8 @@ try DEBUG(adapterInfo("Adding coupling data writers...")); for (uint j = 0; j < interfacesConfig_.at(i).writeData.size(); j++) { - std::string dataName = interfacesConfig_.at(i).writeData.at(j).name; + FieldConfig FieldConfig = interfacesConfig_.at(i).writeData.at(j); + std::string dataName = FieldConfig.name; unsigned int inModules = 0; @@ -351,6 +366,16 @@ try inModules++; } + // Add generic module coupling data writers + // Only add Generic interface if not found in other modules + if (inModules == 0) + { + if (genericModuleEnabled_ && Generic_->addWriters(FieldConfig, interface)) + { + inModules++; + }; + } + if (inModules == 0) { adapterInfo("I don't know how to write \"" + dataName @@ -370,18 +395,38 @@ try DEBUG(adapterInfo("Adding coupling data readers...")); for (uint j = 0; j < interfacesConfig_.at(i).readData.size(); j++) { - std::string dataName = interfacesConfig_.at(i).readData.at(j).name; + FieldConfig FieldConfig = interfacesConfig_.at(i).readData.at(j); + std::string dataName = FieldConfig.name; unsigned int inModules = 0; // Add CHT-related coupling data readers - if (CHTenabled_ && CHT_->addReaders(dataName, interface)) inModules++; + if (CHTenabled_ && CHT_->addReaders(dataName, interface)) + { + inModules++; + } // Add FSI-related coupling data readers - if (FSIenabled_ && FSI_->addReaders(dataName, interface)) inModules++; + if (FSIenabled_ && FSI_->addReaders(dataName, interface)) + { + inModules++; + } // Add FF-related coupling data readers - if (FFenabled_ && FF_->addReaders(dataName, interface)) inModules++; + if (FFenabled_ && FF_->addReaders(dataName, interface)) + { + inModules++; + } + + // Add generic module coupling data readers + // Only add Generic interface if not found in other modules + if (inModules == 0) + { + if (genericModuleEnabled_ && Generic_->addReaders(FieldConfig, interface)) + { + inModules++; + } + } if (inModules == 0) { @@ -1527,6 +1572,14 @@ void preciceAdapter::Adapter::teardown() FF_ = NULL; } + // Delete the Generic module + if (NULL != Generic_) + { + DEBUG(adapterInfo("Destroying the Generic module...")); + delete Generic_; + Generic_ = NULL; + } + // NOTE: Delete your new module here return; diff --git a/Adapter.H b/Adapter.H index b480318c..2e3f96b6 100644 --- a/Adapter.H +++ b/Adapter.H @@ -12,6 +12,8 @@ // Fluid-Fluid module #include "FF/FF.H" +#include "module-generic/Generic.H" + // NOTE: If you want to couple a new variable, include your module's header here. // You also need to include it in the Make/files file. // In case you use additional OpenFOAM symbols, you may also need to specify @@ -37,15 +39,6 @@ class Adapter { private: - //- field options - struct FieldConfig - { - std::string name; - std::string solver_name; - std::string operation; - bool flip_normal; - }; - //- Structure of the configuration of each coupling interface. // Every interface needs to know the coupling mesh, the OpenFOAM // patches that consist the coupling surface and the kinds @@ -117,6 +110,9 @@ private: // NOTE: Add a switch for your new module here + //- Switch to enable General module + bool genericModuleEnabled_ = false; + //- Interfaces std::vector interfaces_; @@ -135,6 +131,9 @@ private: //- Fluid-Fluid module object FF::FluidFluid* FF_ = NULL; + //- Generic module object + Generic::GenericInterface* Generic_ = NULL; + // NOTE: Add here a pointer for your new module object // Timesteps diff --git a/Interface.H b/Interface.H index 885ec9b0..a4d99c11 100644 --- a/Interface.H +++ b/Interface.H @@ -12,6 +12,15 @@ namespace preciceAdapter { +//- field options +struct FieldConfig +{ + std::string name; + std::string solver_name; + std::string operation; + bool flip_normal; +}; + class Interface { protected: diff --git a/Make/files b/Make/files index 6fae86ae..37a8051c 100644 --- a/Make/files +++ b/Make/files @@ -7,6 +7,7 @@ CouplingDataUser.C CHT/ModuleCHT.C FSI/ModuleFSI.C FF/ModuleFF.C +module-generic/ModuleGeneric.C Adapter.C diff --git a/changelog-entries/346.md b/changelog-entries/346.md new file mode 100644 index 00000000..19763f6a --- /dev/null +++ b/changelog-entries/346.md @@ -0,0 +1 @@ +- Added support for arbitrary field volume and surface coupling of scalar and vector field types in the experimental `Generic` module [#346](https://github.com/precice/openfoam-adapter/pull/346). \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index 6f95ea0d..7d78adda 100644 --- a/docs/config.md +++ b/docs/config.md @@ -257,6 +257,53 @@ The FF module is still experimental and the boundary conditions presented here h When coupling face flux `Phi`, usually no specific boundary condition needs to be set. The coupled boundary values are therefore not persistent and may change within a timestep. +#### Generic module + +The Generic module is an experimental addition that allows coupling any field by its name. It supports volume and surface coupling of scalar and vector fields (e.g., `Velocity`, `Pressure`, `Temperature`). If multiple modules are enabled, the Generic module will handle only those fields which have not been defined in the other modules. For example, if FF is also enabled, then fields whose data name starts with `Velocity` will be handled by the FF module instead. + +The module supports the dictionary-based configuration format, which allows setting additional options for each field: + +```cpp +// File system/preciceDict + +modules ( generic ); + +interfaces +{ + Interface1 + { + mesh Fluid-Mesh; + patches (interface); + // For volume coupling specify volumeCenters + locations faceCenters; + + writeData + ( + Velocity1 + { + name Velocity; // Data name as defined in preCICE config + solver_name U; // Optional: Field name in OpenFOAM (defaults to same as 'name') + operation value; // Optional: Operation to perform on data (defaults to 'value') + flip-normal false; // Optional: Flip the normal direction (defaults to 'false') + } + + TemperatureGradient + { + name T_Grad; + solver_name T; + operation surface-normal-gradient; // Use surface normal gradient + } + ); + }; +}; +``` + +Explicitly setting `locations volumeCenters;` in preciceDict is required for volume coupling. If omitted, it defaults to surface coupling on `faceCenters`. Volume coupling data is always assumed as values for now, i.e., cannot set volume gradient. + +When reading on the OpenFOAM side, the Generic module automatically detects the boundary condition type of the coupled patch to apply the received data. The boundary condition must be respected, therefore the data received is applied either as a `fixedValue` or `fixedGradient` boundary condition, determined by the type set in the `0/` files. + +The `operation surface-normal-gradient;` is currently only supported for scalar field surface writing. The operation `gradient` as well as the option `flip-normal` are not yet supported by the adapter. + ### Volume coupling Besides surface coupling on the domain boundaries, the OpenFOAM adapter also supports coupling overlapping domains, which can be the complete domain, or regions of it. In contrast to surface coupling, though, reading volume data (source terms) requires a few additional configuration steps compared to writing data. @@ -634,7 +681,7 @@ to be a useful reference (file changes). ## Upcoming changes to the configuration format -We are currently working on porting the adapter configuration file to the new [adapter configuration schema](https://github.com/precice/preeco-orga/tree/main/adapter-config-schema). Since v1.4.0, `readData` and `writeData` support parsing both the new format. Additional options (`solver_name`, `operation` and `flip-normal`) can be specified in the dictionaries . For example, the data `name` as known by preCICE can be different than the `solver_name` known by OpenFOAM. However, the new options are not yet functionally supported by the current modules FF, CHT and FSI. The legacy word list format is still supported, and both formats can even be mixed: +We are currently working on porting the adapter configuration file to the new [adapter configuration schema](https://github.com/precice/preeco-orga/tree/main/adapter-config-schema). Since v1.4.0, `readData` and `writeData` support parsing both the new format. Additional options (`solver_name`, `operation` and `flip-normal`) can be specified in the dictionaries. For example, the data `name` as known by preCICE can be different than the `solver_name` known by OpenFOAM. However, the new options are not yet functionally supported by the current modules FF, CHT and FSI. Support for the new options is planned in the Generic module. The legacy word list format is still supported, and both formats can even be mixed: ```cpp readData diff --git a/module-generic/Generic.C b/module-generic/Generic.C new file mode 100644 index 00000000..31affa34 --- /dev/null +++ b/module-generic/Generic.C @@ -0,0 +1,118 @@ +#include "Generic.H" + +#include "Utilities.H" + +using namespace Foam; + +preciceAdapter::Generic::GenericInterface::GenericInterface( + const Foam::fvMesh& mesh) +: mesh_(mesh) {} + +bool preciceAdapter::Generic::GenericInterface::configure(const IOdictionary& adapterConfig) +{ + DEBUG(adapterInfo("Configuring the Generic module...")); + + // Scan OpenFOAM object registry once for all + // available volScalarFields and volVectorFields + for (const auto& solver_name : mesh_.sortedNames()) + { + availableVolScalarFields += solver_name + " "; + } + DEBUG(adapterInfo(" Available volScalarFields: " + availableVolScalarFields)); + + for (const auto& solver_name : mesh_.sortedNames()) + { + availableVolVectorFields += solver_name + " "; + } + DEBUG(adapterInfo(" Available volVectorFields: " + availableVolVectorFields)); + + // Read the Generic-module specific options from the adapter's configuration file + if (!readConfig(adapterConfig)) + { + return false; + } + + return true; +} + +bool preciceAdapter::Generic::GenericInterface::readConfig(const IOdictionary& adapterConfig) +{ + // Empty for now. No other specific configuration options for the Generic module. + return true; +} + +bool preciceAdapter::Generic::GenericInterface::addWriters(const preciceAdapter::FieldConfig& FieldConfig, Interface* interface) +{ + bool found = false; + + // Force to use the new schema with the Generic module + if (FieldConfig.solver_name != "Undefined (legacy mode)") + { + // Determine type of field + if (mesh_.foundObject(FieldConfig.solver_name)) + { + found = true; + interface->addCouplingDataWriter( + FieldConfig.name, + new ScalarFieldCoupler(mesh_, FieldConfig)); + } + else if (mesh_.foundObject(FieldConfig.solver_name)) + { + found = true; + interface->addCouplingDataWriter( + FieldConfig.name, + new VectorFieldCoupler(mesh_, FieldConfig)); + } + else + { + found = false; + std::string msg = "Generic module: Data \"" + FieldConfig.name + "\", solver name: \"" + FieldConfig.solver_name + "\" not found!\n"; + msg += "Available fields: " + availableVolScalarFields + availableVolVectorFields; + adapterInfo(msg, "warning"); + } + } + + if (found) + { + DEBUG(adapterInfo("Added writer: " + FieldConfig.name)); + } + return found; +} + +bool preciceAdapter::Generic::GenericInterface::addReaders(const preciceAdapter::FieldConfig& FieldConfig, Interface* interface) +{ + bool found = false; + + // Force to use the new schema with the Generic modul + if (FieldConfig.solver_name != "Undefined (legacy mode)") + { + // Determine type of field + if (mesh_.foundObject(FieldConfig.solver_name)) + { + found = true; + interface->addCouplingDataReader( + FieldConfig.name, + new ScalarFieldCoupler(mesh_, FieldConfig)); + } + else if (mesh_.foundObject(FieldConfig.solver_name)) + { + found = true; + interface->addCouplingDataReader( + FieldConfig.name, + new VectorFieldCoupler(mesh_, FieldConfig)); + } + else + { + found = false; + std::string msg = "Generic module: Data \"" + FieldConfig.name + "\" (solver name: \"" + FieldConfig.solver_name + "\") not found!\n"; + msg += "Available fields: " + availableVolScalarFields + availableVolVectorFields; + adapterInfo(msg, "warning"); + } + } + + if (found) + { + DEBUG(adapterInfo("Added reader: " + FieldConfig.name)); + } + return found; +} diff --git a/module-generic/Generic.H b/module-generic/Generic.H new file mode 100644 index 00000000..28d41db7 --- /dev/null +++ b/module-generic/Generic.H @@ -0,0 +1,55 @@ +#ifndef GENERICMODULE_H +#define GENERICMODULE_H + +#include "Interface.H" + +#include "ReadWrite.H" + +#include "fvCFD.H" + +namespace preciceAdapter +{ +namespace Generic +{ + +class GenericInterface +{ +protected: + //- OpenFOAM fvMesh object + const Foam::fvMesh& mesh_; + + //- Read the Generic related options from the adapter's configuration file + bool readConfig(const IOdictionary& adapterConfig); + + //- Struct to store interfaces configuration (modified from Adapter.C) + struct InterfaceConfig + { + std::vector writeData; + std::vector readData; + }; + + //- Configuration interfaces + std::vector interfacesConfig_; + + //- List of available fields in the OpenFOAM object registry + std::string availableVolScalarFields; + std::string availableVolVectorFields; + +public: + //- Constructor + GenericInterface(const Foam::fvMesh& mesh); + + //- Configure + bool configure(const IOdictionary& adapterConfig); + + //- Add coupling data writers + bool addWriters(const preciceAdapter::FieldConfig& FieldConfig, Interface* interface); + + //- Add coupling data readers + bool addReaders(const preciceAdapter::FieldConfig& FieldConfig, Interface* interface); +}; + +} +} + +#endif diff --git a/module-generic/ModuleGeneric.C b/module-generic/ModuleGeneric.C new file mode 100644 index 00000000..ff567d14 --- /dev/null +++ b/module-generic/ModuleGeneric.C @@ -0,0 +1,3 @@ +// Central inlude file to reduce the number of build targets and build time +#include "ReadWrite.C" +#include "Generic.C" diff --git a/module-generic/ReadWrite.C b/module-generic/ReadWrite.C new file mode 100644 index 00000000..c90f8eba --- /dev/null +++ b/module-generic/ReadWrite.C @@ -0,0 +1,404 @@ +#include "ReadWrite.H" +#include "primitivePatchInterpolation.H" +#include "fvCFD.H" + +#include "fixedValueFvPatchFields.H" +#include "fixedGradientFvPatchFields.H" + +using namespace Foam; + +//----- preciceAdapter::Generic::scalarFieldCoupler ----------------------------------------- + +preciceAdapter::Generic::ScalarFieldCoupler::ScalarFieldCoupler( + const Foam::fvMesh& mesh, + const preciceAdapter::FieldConfig& FieldConfig) +: scalarField_( + const_cast( + &mesh.lookupObject(FieldConfig.solver_name))), + mesh_(mesh), + FieldConfig_(FieldConfig) +{ + dataType_ = scalar; +} + +void preciceAdapter::Generic::ScalarFieldCoupler::initialize() +{ + if (FieldConfig_.operation == "surface-normal-gradient") + { + if (this->locationType_ != LocationType::faceCenters) + { + adapterInfo("Generic module: The surface-normal-gradient operation is only supported for faceCenters location type.", "error"); + } + } + + if (FieldConfig_.operation == "gradient") + { + adapterInfo("Generic module: The gradient operation is not yet supported for scalar fields. Maybe you meant surface-normal-gradient?", "error"); + } +} + + +std::size_t preciceAdapter::Generic::ScalarFieldCoupler::write(double* buffer, bool meshConnectivity, const unsigned int dim) +{ + int bufferIndex = 0; + + if (FieldConfig_.operation == "surface-normal-gradient") + { + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + // Get the surface normal gradient boundary patch + scalarField gradientPatch(scalarField_->boundaryField()[patchID].snGrad()); + + // For every cell of the patch + forAll(gradientPatch, i) + { + buffer[bufferIndex++] = gradientPatch[i]; + } + } + return bufferIndex; + } + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (const auto& cell : scalarField_->internalField()) + { + buffer[bufferIndex++] = cell; + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(scalarField_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // Copy the scalar value into the buffer + buffer[bufferIndex++] = scalarField_->internalField()[currentCell]; + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + const auto& boundaryPatch(scalarField_->boundaryField()[patchID]); + + //If we use the mesh connectivity, we interpolate from the centres to the nodes + if (meshConnectivity) + { + //Create an Interpolation object at the boundary Field + primitivePatchInterpolation patchInterpolator(mesh_.boundaryMesh()[patchID]); + + //Interpolate from centers to nodes + scalarField pointValues( + patchInterpolator.faceToPointInterpolate(boundaryPatch)); + + forAll(pointValues, i) + { + // Copy the scalar value into the buffer + buffer[bufferIndex++] = pointValues[i]; + } + } + else + { + // For every cell of the patch + forAll(boundaryPatch, i) + { + buffer[bufferIndex++] = boundaryPatch[i]; + } + } + } + return bufferIndex; +} + +void preciceAdapter::Generic::ScalarFieldCoupler::read(double* buffer, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (auto& cell : scalarField_->ref()) + { + cell = buffer[bufferIndex++]; + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(scalarField_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // Copy the scalar value from the buffer + scalarField_->ref()[currentCell] = buffer[bufferIndex++]; + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + auto& bc = scalarField_->boundaryFieldRef()[patchID]; + + if (isA(bc)) + { + auto& boundaryPatch = refCast(bc); + forAll(boundaryPatch, i) + { + boundaryPatch[i] = buffer[bufferIndex++]; + } + } + else if (isA(bc)) + { + auto& boundaryPatch = refCast(bc); + forAll(boundaryPatch, i) + { + boundaryPatch.gradient()[i] = buffer[bufferIndex++]; + } + } + else + { + adapterInfo("Generic module: Unsupported boundary condition type " + bc.type(), "error"); + } + + // // evaluate the boundary condition, i.e., do some calculation to obtain the actual value provided refValue + // boundaryPatch.updateCoeffs(); + // boundaryPatch.evaluate(); + } +} + +bool preciceAdapter::Generic::ScalarFieldCoupler::isLocationTypeSupported(const bool meshConnectivity) const +{ + if (meshConnectivity) + { + return (this->locationType_ == LocationType::faceNodes); + } + else + { + return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::volumeCenters); + } +} + +std::string preciceAdapter::Generic::ScalarFieldCoupler::getDataName() const +{ + return FieldConfig_.name; +} + +//----- preciceAdapter::Generic::VectorFieldCoupler ----------------------------------------- + +preciceAdapter::Generic::VectorFieldCoupler::VectorFieldCoupler( + const Foam::fvMesh& mesh, + const preciceAdapter::FieldConfig& FieldConfig) +: vectorField_( + const_cast( + &mesh.lookupObject(FieldConfig.solver_name))), + mesh_(mesh), + FieldConfig_(FieldConfig) +{ + dataType_ = vector; +} + +void preciceAdapter::Generic::VectorFieldCoupler::initialize() +{ + // In the Generic module, for vector fields only the value operation is supported for now, i.e., cannot write gradients. + if (FieldConfig_.operation == "gradient" || FieldConfig_.operation == "surface-normal-gradient") + { + adapterInfo("Generic module: The gradient operation is not yet supported for vector fields.", "error"); + } +} + +std::size_t preciceAdapter::Generic::VectorFieldCoupler::write(double* buffer, bool meshConnectivity, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (const auto& cell : vectorField_->internalField()) + { + // x-dimension + buffer[bufferIndex++] = cell.x(); + + // y-dimension + buffer[bufferIndex++] = cell.y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = cell.z(); + } + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(vectorField_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // x-dimension + buffer[bufferIndex++] = vectorField_->internalField()[currentCell].x(); + + // y-dimension + buffer[bufferIndex++] = vectorField_->internalField()[currentCell].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = vectorField_->internalField()[currentCell].z(); + } + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + // Get the vector field boundary patch + auto& boundaryPatch = vectorField_->boundaryField()[patchID]; + + // For every cell of the patch + forAll(boundaryPatch, i) + { + buffer[bufferIndex++] = boundaryPatch[i].x(); + + buffer[bufferIndex++] = boundaryPatch[i].y(); + + if (dim == 3) + { + buffer[bufferIndex++] = boundaryPatch[i].z(); + } + } + } + return bufferIndex; +} + +void preciceAdapter::Generic::VectorFieldCoupler::read(double* buffer, const unsigned int dim) +{ + int bufferIndex = 0; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (auto& cell : vectorField_->ref()) + { + // x-dimension + cell.x() = buffer[bufferIndex++]; + + // y-dimension + cell.y() = buffer[bufferIndex++]; + + if (dim == 3) + { + // z-dimension + cell.z() = buffer[bufferIndex++]; + } + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(vectorField_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // x-dimension + vectorField_->ref()[currentCell].x() = buffer[bufferIndex++]; + + // y-dimension + vectorField_->ref()[currentCell].y() = buffer[bufferIndex++]; + + if (dim == 3) + { + // z-dimension + vectorField_->ref()[currentCell].z() = buffer[bufferIndex++]; + } + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + auto& bc = vectorField_->boundaryFieldRef()[patchID]; + + if (isA(bc)) + { + auto& boundaryPatch = refCast(bc); + forAll(boundaryPatch, i) + { + boundaryPatch[i].x() = buffer[bufferIndex++]; + boundaryPatch[i].y() = buffer[bufferIndex++]; + + if (dim == 3) + { + boundaryPatch[i].z() = buffer[bufferIndex++]; + } + } + } + else if (isA(bc)) + { + auto& boundaryPatch = refCast(bc); + forAll(boundaryPatch, i) + { + boundaryPatch.gradient()[i].x() = buffer[bufferIndex++]; + boundaryPatch.gradient()[i].y() = buffer[bufferIndex++]; + + if (dim == 3) + { + boundaryPatch.gradient()[i].z() = buffer[bufferIndex++]; + } + } + } + else + { + adapterInfo("Generic module: Unsupported boundary condition type " + bc.type(), "error"); + } + } +} + +bool preciceAdapter::Generic::VectorFieldCoupler::isLocationTypeSupported(const bool meshConnectivity) const +{ + if (meshConnectivity) + { + return false; + } + else + { + return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::volumeCenters); + } +} + +std::string preciceAdapter::Generic::VectorFieldCoupler::getDataName() const +{ + return FieldConfig_.name; +} diff --git a/module-generic/ReadWrite.H b/module-generic/ReadWrite.H new file mode 100644 index 00000000..fbe85e5b --- /dev/null +++ b/module-generic/ReadWrite.H @@ -0,0 +1,56 @@ +#ifndef MODULE_ReadWrite_H +#define MODULE_ReadWrite_H + +#include "Interface.H" +#include "CouplingDataUser.H" + +#include "fvCFD.H" +#include "cellSet.H" + +namespace preciceAdapter +{ +namespace Generic +{ + +class ScalarFieldCoupler : public CouplingDataUser // One CouplingDataUser for each field +{ +protected: + Foam::volScalarField* scalarField_; + const Foam::fvMesh& mesh_; + const preciceAdapter::FieldConfig& FieldConfig_; + +public: + ScalarFieldCoupler(const Foam::fvMesh& mesh, const preciceAdapter::FieldConfig& FieldConfig); + + void initialize(); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + void read(double* buffer, const unsigned int dim); + bool isLocationTypeSupported(const bool meshConnectivity) const; + std::string getDataName() const; + + virtual ~ScalarFieldCoupler() {}; +}; + +class VectorFieldCoupler : public CouplingDataUser +{ +protected: + Foam::volVectorField* vectorField_; + const Foam::fvMesh& mesh_; + const preciceAdapter::FieldConfig& FieldConfig_; + +public: + VectorFieldCoupler(const Foam::fvMesh& mesh, const preciceAdapter::FieldConfig& FieldConfig); + + void initialize(); + std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); + void read(double* buffer, const unsigned int dim); + bool isLocationTypeSupported(const bool meshConnectivity) const; + std::string getDataName() const; + + virtual ~VectorFieldCoupler() {}; +}; + +} +} + +#endif From f81cb205a9c4d56316b9108899623f45e017a64e Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:42:49 +0100 Subject: [PATCH 41/70] Initialize meshPoints_ to null pointers (#394) Co-authored-by: Gerasimos Chourdakis --- Adapter.H | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adapter.H b/Adapter.H index 2e3f96b6..1e6d41f5 100644 --- a/Adapter.H +++ b/Adapter.H @@ -155,8 +155,8 @@ private: // Vectors of pointers to the checkpointed mesh fields and their copies //- Checkpointed mesh points, oldPoints - Foam::pointField* meshPoints_; - Foam::pointField* meshOldPoints_; + Foam::pointField* meshPoints_ = nullptr; + Foam::pointField* meshOldPoints_ = nullptr; //- Checkpointed surfaceScalarField mesh fields (phi) std::vector meshSurfaceScalarFields_; From ff0b4893ca6b81539610687c7b143c71f30bdd0f Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:07:58 +0100 Subject: [PATCH 42/70] Update meshConnectivity claims (#393) --- Adapter.C | 14 -------------- CHT/HeatFlux.C | 7 +------ CHT/HeatTransferCoefficient.C | 7 +------ CHT/SinkTemperature.C | 7 +------ CHT/Temperature.C | 7 +------ CouplingDataUser.C | 18 +++++++++++++----- FF/Alpha.C | 2 +- FF/DragForce.C | 2 +- FF/ExplicitMomentum.C | 2 +- FF/ImplicitMomentum.C | 2 +- FF/Pressure.C | 2 +- FF/Velocity.C | 2 +- FSI/Displacement.C | 10 +++++++++- FSI/DisplacementDelta.C | 10 +++++++++- FSI/Force.C | 9 ++++++++- FSI/Stress.C | 9 ++++++++- changelog-entries/394.md | 1 + 17 files changed, 58 insertions(+), 53 deletions(-) create mode 100644 changelog-entries/394.md diff --git a/Adapter.C b/Adapter.C index 45a696a8..c1aeefff 100644 --- a/Adapter.C +++ b/Adapter.C @@ -248,20 +248,6 @@ void preciceAdapter::Adapter::configFileRead() // FSI-specific options and configure it. if (FSIenabled_) { - // Check for unsupported FSI with meshConnectivity - for (uint i = 0; i < interfacesConfig_.size(); i++) - { - if (interfacesConfig_.at(i).meshConnectivity == true) - { - adapterInfo( - "You have requested mesh connectivity (most probably for nearest-projection mapping) " - "and you have enabled the FSI module. " - "Mapping with connectivity information is not implemented for FSI, only for CHT-related fields. " - "error"); - return; - } - } - FSI_ = new FSI::FluidStructureInteraction(mesh_, runTime_); if (!FSI_->configure(preciceDict)) { diff --git a/CHT/HeatFlux.C b/CHT/HeatFlux.C index 7c63e290..cd7adced 100644 --- a/CHT/HeatFlux.C +++ b/CHT/HeatFlux.C @@ -105,14 +105,9 @@ void preciceAdapter::CHT::HeatFlux::read(double* buffer, const unsigned int dim) bool preciceAdapter::CHT::HeatFlux::isLocationTypeSupported(const bool meshConnectivity) const { - // For cases with mesh connectivity, we support: - // - face nodes, only for writing - // - face centers, only for reading - // However, since we do not distinguish between reading and writing in the code, we - // always return true and offload the handling to the user. if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::faceNodes); // we currently do not support meshConnectivity for volumeCenters + return (this->locationType_ == LocationType::faceNodes); } else { diff --git a/CHT/HeatTransferCoefficient.C b/CHT/HeatTransferCoefficient.C index 68778280..4797ae67 100644 --- a/CHT/HeatTransferCoefficient.C +++ b/CHT/HeatTransferCoefficient.C @@ -118,14 +118,9 @@ void preciceAdapter::CHT::HeatTransferCoefficient::read(double* buffer, const un bool preciceAdapter::CHT::HeatTransferCoefficient::isLocationTypeSupported(const bool meshConnectivity) const { - // For cases with mesh connectivity, we support: - // - face nodes, only for writing - // - face centers, only for reading - // However, since we do not distinguish between reading and writing in the code, we - // always return true and offload the handling to the user. if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::faceNodes); // we currently do not support meshConnectivity for volumeCenters + return (this->locationType_ == LocationType::faceNodes); } else { diff --git a/CHT/SinkTemperature.C b/CHT/SinkTemperature.C index 9ed18244..2b006dbe 100644 --- a/CHT/SinkTemperature.C +++ b/CHT/SinkTemperature.C @@ -95,14 +95,9 @@ void preciceAdapter::CHT::SinkTemperature::read(double* buffer, const unsigned i bool preciceAdapter::CHT::SinkTemperature::isLocationTypeSupported(const bool meshConnectivity) const { - // For cases with mesh connectivity, we support: - // - face nodes, only for writing - // - face centers, only for reading - // However, since we do not distinguish between reading and writing in the code, we - // always return true and offload the handling to the user. if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::faceNodes); // we currently do not support meshConnectivity for volumeCenters + return (this->locationType_ == LocationType::faceNodes); } else { diff --git a/CHT/Temperature.C b/CHT/Temperature.C index 3459e840..9b63ec7a 100644 --- a/CHT/Temperature.C +++ b/CHT/Temperature.C @@ -128,14 +128,9 @@ void preciceAdapter::CHT::Temperature::read(double* buffer, const unsigned int d bool preciceAdapter::CHT::Temperature::isLocationTypeSupported(const bool meshConnectivity) const { - // For cases with mesh connectivity, we support: - // - face nodes, only for writing - // - face centers, only for reading - // However, since we do not distinguish between reading and writing in the code, we - // always return true and offload the handling to the user. if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::faceNodes); // we currently do not support meshConnectivity for volumeCenters + return (this->locationType_ == LocationType::faceNodes); } else { diff --git a/CouplingDataUser.C b/CouplingDataUser.C index f90ef9a0..0e38c480 100644 --- a/CouplingDataUser.C +++ b/CouplingDataUser.C @@ -51,11 +51,19 @@ void preciceAdapter::CouplingDataUser::checkDataLocation(const bool meshConnecti else if (locationType_ == LocationType::volumeCenters) location = "volumeCenters"; - adapterInfo("\"locations = " + location + "\" is not supported for the data \"" - + getDataName() + "\". Please select a different " - "location type, a different data set or provide " - "additional connectivity information.", - "error"); + if (meshConnectivity) + { + adapterInfo("The data \"" + getDataName() + "\"" + + " does not currently support mesh connectivity (e.g., nearest-projection mapping) for location type " + + "\"" + location + "\".", + "error"); + } + else + { + adapterInfo("The data \"" + getDataName() + "\" does not support location type \"" + + location + "\". Please select a different location type.", + "error"); + } } } diff --git a/FF/Alpha.C b/FF/Alpha.C index f11d9c2e..8f85bcf9 100644 --- a/FF/Alpha.C +++ b/FF/Alpha.C @@ -102,7 +102,7 @@ bool preciceAdapter::FF::Alpha::isLocationTypeSupported(const bool meshConnectiv { if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters); + return false; } else { diff --git a/FF/DragForce.C b/FF/DragForce.C index 8e9f5ffa..f59d3334 100644 --- a/FF/DragForce.C +++ b/FF/DragForce.C @@ -189,7 +189,7 @@ bool preciceAdapter::FF::DragForce::isLocationTypeSupported(const bool meshConne { if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters); + return false; } else { diff --git a/FF/ExplicitMomentum.C b/FF/ExplicitMomentum.C index 2f7d0798..34fd4f01 100644 --- a/FF/ExplicitMomentum.C +++ b/FF/ExplicitMomentum.C @@ -189,7 +189,7 @@ bool preciceAdapter::FF::ExplicitMomentum::isLocationTypeSupported(const bool me { if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters); + return false; } else { diff --git a/FF/ImplicitMomentum.C b/FF/ImplicitMomentum.C index 3c066066..59a4366b 100644 --- a/FF/ImplicitMomentum.C +++ b/FF/ImplicitMomentum.C @@ -125,7 +125,7 @@ bool preciceAdapter::FF::ImplicitMomentum::isLocationTypeSupported(const bool me { if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters); + return false; } else { diff --git a/FF/Pressure.C b/FF/Pressure.C index 5e8b94db..3a8e7bc8 100644 --- a/FF/Pressure.C +++ b/FF/Pressure.C @@ -116,7 +116,7 @@ bool preciceAdapter::FF::Pressure::isLocationTypeSupported(const bool meshConnec { if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters); + return false; } else { diff --git a/FF/Velocity.C b/FF/Velocity.C index 18470d73..cebaed72 100644 --- a/FF/Velocity.C +++ b/FF/Velocity.C @@ -209,7 +209,7 @@ bool preciceAdapter::FF::Velocity::isLocationTypeSupported(const bool meshConnec { if (meshConnectivity) { - return (this->locationType_ == LocationType::faceCenters); + return false; } else { diff --git a/FSI/Displacement.C b/FSI/Displacement.C index 4a76e26f..fd13d605 100644 --- a/FSI/Displacement.C +++ b/FSI/Displacement.C @@ -142,7 +142,15 @@ void preciceAdapter::FSI::Displacement::read(double* buffer, const unsigned int bool preciceAdapter::FSI::Displacement::isLocationTypeSupported(const bool meshConnectivity) const { - return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::faceNodes); + // Solid solver can allow connectivity for writing displacement + if (meshConnectivity) + { + return (this->locationType_ == LocationType::faceNodes); + } + else + { + return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::faceNodes); + } } std::string preciceAdapter::FSI::Displacement::getDataName() const diff --git a/FSI/DisplacementDelta.C b/FSI/DisplacementDelta.C index b1629de0..99c6ca58 100644 --- a/FSI/DisplacementDelta.C +++ b/FSI/DisplacementDelta.C @@ -96,7 +96,15 @@ void preciceAdapter::FSI::DisplacementDelta::read(double* buffer, const unsigned bool preciceAdapter::FSI::DisplacementDelta::isLocationTypeSupported(const bool meshConnectivity) const { - return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::faceNodes); + // Solid solver *could* allow connectivity for writing displacement + if (meshConnectivity) + { + return (this->locationType_ == LocationType::faceNodes); + } + else + { + return (this->locationType_ == LocationType::faceCenters || this->locationType_ == LocationType::faceNodes); + } } std::string preciceAdapter::FSI::DisplacementDelta::getDataName() const diff --git a/FSI/Force.C b/FSI/Force.C index d8cbad39..9bcb56d6 100644 --- a/FSI/Force.C +++ b/FSI/Force.C @@ -79,7 +79,14 @@ void preciceAdapter::FSI::Force::read(double* buffer, const unsigned int dim) bool preciceAdapter::FSI::Force::isLocationTypeSupported(const bool meshConnectivity) const { - return (this->locationType_ == LocationType::faceCenters); + if (meshConnectivity) + { + return false; + } + else + { + return (this->locationType_ == LocationType::faceCenters); + } } std::string preciceAdapter::FSI::Force::getDataName() const diff --git a/FSI/Stress.C b/FSI/Stress.C index 1194a249..ada422bb 100644 --- a/FSI/Stress.C +++ b/FSI/Stress.C @@ -33,7 +33,14 @@ void preciceAdapter::FSI::Stress::read(double* buffer, const unsigned int dim) bool preciceAdapter::FSI::Stress::isLocationTypeSupported(const bool meshConnectivity) const { - return (this->locationType_ == LocationType::faceCenters); + if (meshConnectivity) + { + return false; + } + else + { + return (this->locationType_ == LocationType::faceCenters); + } } std::string preciceAdapter::FSI::Stress::getDataName() const diff --git a/changelog-entries/394.md b/changelog-entries/394.md new file mode 100644 index 00000000..f9e42b1f --- /dev/null +++ b/changelog-entries/394.md @@ -0,0 +1 @@ +Updated `isLocationTypeSupported` across all modules to remove unreachable conditions and to better reflect the implementation. FSI now allows mesh connectivity when writing Displacement. \ No newline at end of file From ee4e25c9da0ecb8de0c8ca95dc414ca1e9dc706d Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 25 Feb 2026 13:26:58 +0100 Subject: [PATCH 43/70] Change NULL pointer values to C++11 nullptr (#396) --- Adapter.C | 26 +++++++++++++------------- Adapter.H | 8 ++++---- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Adapter.C b/Adapter.C index c1aeefff..9ad31155 100644 --- a/Adapter.C +++ b/Adapter.C @@ -635,7 +635,7 @@ void preciceAdapter::Adapter::initialize() void preciceAdapter::Adapter::finalize() { - if (NULL != precice_ && preciceInitialized_ && !isCouplingOngoing()) + if (nullptr != precice_ && preciceInitialized_ && !isCouplingOngoing()) { DEBUG(adapterInfo("Finalizing the preCICE solver interface...")); @@ -782,7 +782,7 @@ bool preciceAdapter::Adapter::isCouplingOngoing() // the solver would try to access this method again, // giving a segmentation fault if precice_ // was not available. - if (NULL != precice_) + if (nullptr != precice_) { isCouplingOngoing = precice_->isCouplingOngoing(); } @@ -1413,7 +1413,7 @@ void preciceAdapter::Adapter::end() try { // Throw a warning if the simulation exited before the coupling was complete - if (NULL != precice_ && isCouplingOngoing()) + if (nullptr != precice_ && isCouplingOngoing()) { adapterInfo("The solver exited before the coupling was complete.", "warning"); } @@ -1429,11 +1429,11 @@ void preciceAdapter::Adapter::teardown() { // If the solver interface was not deleted before, delete it now. // Normally it should be deleted when isCouplingOngoing() becomes false. - if (NULL != precice_) + if (nullptr != precice_) { DEBUG(adapterInfo("Destroying the preCICE solver interface...")); delete precice_; - precice_ = NULL; + precice_ = nullptr; } // Delete the preCICE solver interfaces @@ -1535,35 +1535,35 @@ void preciceAdapter::Adapter::teardown() } // Delete the CHT module - if (NULL != CHT_) + if (nullptr != CHT_) { DEBUG(adapterInfo("Destroying the CHT module...")); delete CHT_; - CHT_ = NULL; + CHT_ = nullptr; } // Delete the FSI module - if (NULL != FSI_) + if (nullptr != FSI_) { DEBUG(adapterInfo("Destroying the FSI module...")); delete FSI_; - FSI_ = NULL; + FSI_ = nullptr; } // Delete the FF module - if (NULL != FF_) + if (nullptr != FF_) { DEBUG(adapterInfo("Destroying the FF module...")); delete FF_; - FF_ = NULL; + FF_ = nullptr; } // Delete the Generic module - if (NULL != Generic_) + if (nullptr != Generic_) { DEBUG(adapterInfo("Destroying the Generic module...")); delete Generic_; - Generic_ = NULL; + Generic_ = nullptr; } // NOTE: Delete your new module here diff --git a/Adapter.H b/Adapter.H index 1e6d41f5..dff9b6a8 100644 --- a/Adapter.H +++ b/Adapter.H @@ -123,16 +123,16 @@ private: bool preciceInitialized_ = false; //- Conjugate Heat Transfer module object - CHT::ConjugateHeatTransfer* CHT_ = NULL; + CHT::ConjugateHeatTransfer* CHT_ = nullptr; //- Fluid-Structure Interaction module object - FSI::FluidStructureInteraction* FSI_ = NULL; + FSI::FluidStructureInteraction* FSI_ = nullptr; //- Fluid-Fluid module object - FF::FluidFluid* FF_ = NULL; + FF::FluidFluid* FF_ = nullptr; //- Generic module object - Generic::GenericInterface* Generic_ = NULL; + Generic::GenericInterface* Generic_ = nullptr; // NOTE: Add here a pointer for your new module object From 618967fc6fc2a2e81e8b814f1d0fd5a3e3b54de3 Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:10:44 +0100 Subject: [PATCH 44/70] Implement `flip-normal` operation adapter-wide inside `CouplingDataUser` (#395) * Change the signatures of all addWriters and addReaders from std::string dataName to const preciceAdapter::FieldConfig& fieldConfig * Implement flip normal operation in CouplingDataUser * Remove outdated comments * Convert dataBuffer pointers to dataSpan. Use precice::span instead of std::span for compatibility --- Adapter.C | 52 +++++++++++++++++++------------------- CHT/CHT.C | 40 ++++++++++++++++------------- CHT/CHT.H | 4 +-- CouplingDataUser.C | 16 ++++++++++++ CouplingDataUser.H | 10 ++++++++ FF/FF.C | 52 ++++++++++++++++++++------------------ FF/FF.H | 4 +-- FSI/FSI.C | 24 ++++++++++-------- FSI/FSI.H | 4 +-- Interface.C | 36 +++++++++++++++----------- Interface.H | 4 +-- module-generic/Generic.C | 40 ++++++++++++++--------------- module-generic/Generic.H | 4 +-- module-generic/ReadWrite.C | 24 +++++++++--------- module-generic/ReadWrite.H | 8 +++--- 15 files changed, 183 insertions(+), 139 deletions(-) diff --git a/Adapter.C b/Adapter.C index 9ad31155..e03de9ba 100644 --- a/Adapter.C +++ b/Adapter.C @@ -31,7 +31,7 @@ void preciceAdapter::Adapter::readFieldConfigs(const std::string& listName, Foam word dataName; stream >> dataName; - struct FieldConfig FieldConfig; + struct FieldConfig fieldConfig; // If next token is '{', we have a dictionary (new schema). // We create a dictionary from the stream `dictionary dict(stream);` @@ -42,12 +42,12 @@ void preciceAdapter::Adapter::readFieldConfigs(const std::string& listName, Foam if (stream.peek() == token::BEGIN_BLOCK) { dictionary dict(stream); - FieldConfig.name = dict.get("name"); // The 'name' entry is mandatory. - FieldConfig.solver_name = dict.lookupOrDefault("solver_name", FieldConfig.name); - FieldConfig.operation = dict.lookupOrDefault("operation", "value"); + fieldConfig.name = dict.get("name"); // The 'name' entry is mandatory. + fieldConfig.solver_name = dict.lookupOrDefault("solver_name", fieldConfig.name); + fieldConfig.operation = dict.lookupOrDefault("operation", "value"); try { - FieldConfig.flip_normal = dict.lookupOrDefault("flip-normal", false); + fieldConfig.flip_normal = dict.lookupOrDefault("flip-normal", false); } catch (const Foam::IOerror& e) { @@ -57,19 +57,19 @@ void preciceAdapter::Adapter::readFieldConfigs(const std::string& listName, Foam // Else, we have a simple word entry (legacy schema/backwards compatibility). else { - FieldConfig.name = dataName; - FieldConfig.solver_name = "Undefined (legacy mode)"; - FieldConfig.operation = "Undefined (legacy mode)"; - FieldConfig.flip_normal = false; + fieldConfig.name = dataName; + fieldConfig.solver_name = "Undefined (legacy mode)"; + fieldConfig.operation = "Undefined (legacy mode)"; + fieldConfig.flip_normal = false; } - configs.push_back(FieldConfig); + configs.push_back(fieldConfig); DEBUG(adapterInfo(" - " + dataName)); - DEBUG(adapterInfo(" name: " + FieldConfig.name)); - DEBUG(adapterInfo(" solver_name: " + FieldConfig.solver_name)); - DEBUG(adapterInfo(" operation : " + FieldConfig.operation)); - DEBUG(adapterInfo(" flip-normal: " + std::string(FieldConfig.flip_normal ? "true" : "false"))); + DEBUG(adapterInfo(" name: " + fieldConfig.name)); + DEBUG(adapterInfo(" solver_name: " + fieldConfig.solver_name)); + DEBUG(adapterInfo(" operation : " + fieldConfig.operation)); + DEBUG(adapterInfo(" flip-normal: " + std::string(fieldConfig.flip_normal ? "true" : "false"))); } stream >> _t; // Last token ')' } @@ -329,25 +329,25 @@ try DEBUG(adapterInfo("Adding coupling data writers...")); for (uint j = 0; j < interfacesConfig_.at(i).writeData.size(); j++) { - FieldConfig FieldConfig = interfacesConfig_.at(i).writeData.at(j); - std::string dataName = FieldConfig.name; + FieldConfig fieldConfig = interfacesConfig_.at(i).writeData.at(j); + std::string dataName = fieldConfig.name; unsigned int inModules = 0; // Add CHT-related coupling data writers - if (CHTenabled_ && CHT_->addWriters(dataName, interface)) + if (CHTenabled_ && CHT_->addWriters(fieldConfig, interface)) { inModules++; } // Add FSI-related coupling data writers - if (FSIenabled_ && FSI_->addWriters(dataName, interface)) + if (FSIenabled_ && FSI_->addWriters(fieldConfig, interface)) { inModules++; } // Add FF-related coupling data writers - if (FFenabled_ && FF_->addWriters(dataName, interface)) + if (FFenabled_ && FF_->addWriters(fieldConfig, interface)) { inModules++; } @@ -356,7 +356,7 @@ try // Only add Generic interface if not found in other modules if (inModules == 0) { - if (genericModuleEnabled_ && Generic_->addWriters(FieldConfig, interface)) + if (genericModuleEnabled_ && Generic_->addWriters(fieldConfig, interface)) { inModules++; }; @@ -381,25 +381,25 @@ try DEBUG(adapterInfo("Adding coupling data readers...")); for (uint j = 0; j < interfacesConfig_.at(i).readData.size(); j++) { - FieldConfig FieldConfig = interfacesConfig_.at(i).readData.at(j); - std::string dataName = FieldConfig.name; + FieldConfig fieldConfig = interfacesConfig_.at(i).readData.at(j); + std::string dataName = fieldConfig.name; unsigned int inModules = 0; // Add CHT-related coupling data readers - if (CHTenabled_ && CHT_->addReaders(dataName, interface)) + if (CHTenabled_ && CHT_->addReaders(fieldConfig, interface)) { inModules++; } // Add FSI-related coupling data readers - if (FSIenabled_ && FSI_->addReaders(dataName, interface)) + if (FSIenabled_ && FSI_->addReaders(fieldConfig, interface)) { inModules++; } // Add FF-related coupling data readers - if (FFenabled_ && FF_->addReaders(dataName, interface)) + if (FFenabled_ && FF_->addReaders(fieldConfig, interface)) { inModules++; } @@ -408,7 +408,7 @@ try // Only add Generic interface if not found in other modules if (inModules == 0) { - if (genericModuleEnabled_ && Generic_->addReaders(FieldConfig, interface)) + if (genericModuleEnabled_ && Generic_->addReaders(fieldConfig, interface)) { inModules++; } diff --git a/CHT/CHT.C b/CHT/CHT.C index cef3052d..ee189ac3 100644 --- a/CHT/CHT.C +++ b/CHT/CHT.C @@ -118,21 +118,23 @@ std::string preciceAdapter::CHT::ConjugateHeatTransfer::determineSolverType() return solverType; } -bool preciceAdapter::CHT::ConjugateHeatTransfer::addWriters(std::string dataName, Interface* interface) +bool preciceAdapter::CHT::ConjugateHeatTransfer::addWriters(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface) { + std::string dataName = fieldConfig.name; + bool found = true; // Set to false later, if needed. if (matchingStrings(dataName, "Sink-Temperature")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new SinkTemperature(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Sink Temperature.")); } else if (matchingStrings(dataName, "Temperature")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Temperature(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Temperature.")); } @@ -141,21 +143,21 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addWriters(std::string dataName if (solverType_.compare("compressible") == 0) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new HeatFlux_Compressible(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Heat Flux for compressible solvers.")); } else if (solverType_.compare("incompressible") == 0) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new HeatFlux_Incompressible(mesh_, nameT_, nameRho_, nameCp_, namePr_, nameAlphat_)); DEBUG(adapterInfo("Added writer: Heat Flux for incompressible solvers. ")); } else if (solverType_.compare("basic") == 0) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new HeatFlux_Basic(mesh_, nameT_, nameKappa_)); DEBUG(adapterInfo("Added writer: Heat Flux for basic solvers. ")); } @@ -170,21 +172,21 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addWriters(std::string dataName if (solverType_.compare("compressible") == 0) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new HeatTransferCoefficient_Compressible(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Heat Transfer Coefficient for compressible solvers.")); } else if (solverType_.compare("incompressible") == 0) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new HeatTransferCoefficient_Incompressible(mesh_, nameT_, nameRho_, nameCp_, namePr_, nameAlphat_)); DEBUG(adapterInfo("Added writer: Heat Transfer Coefficient for incompressible solvers. ")); } else if (solverType_.compare("basic") == 0) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new HeatTransferCoefficient_Basic(mesh_, nameT_, nameKappa_)); DEBUG(adapterInfo("Added writer: Heat Transfer Coefficient for basic solvers. ")); } @@ -208,21 +210,23 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addWriters(std::string dataName return found; } -bool preciceAdapter::CHT::ConjugateHeatTransfer::addReaders(std::string dataName, Interface* interface) +bool preciceAdapter::CHT::ConjugateHeatTransfer::addReaders(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface) { + std::string dataName = fieldConfig.name; + bool found = true; // Set to false later, if needed. if (matchingStrings(dataName, "Sink-Temperature")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new SinkTemperature(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Sink Temperature.")); } else if (matchingStrings(dataName, "Temperature")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Temperature(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Temperature.")); } @@ -231,21 +235,21 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addReaders(std::string dataName if (solverType_.compare("compressible") == 0) { interface->addCouplingDataReader( - dataName, + fieldConfig, new HeatFlux_Compressible(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Heat Flux for compressible solvers.")); } else if (solverType_.compare("incompressible") == 0) { interface->addCouplingDataReader( - dataName, + fieldConfig, new HeatFlux_Incompressible(mesh_, nameT_, nameRho_, nameCp_, namePr_, nameAlphat_)); DEBUG(adapterInfo("Added reader: Heat Flux for incompressible solvers. ")); } else if (solverType_.compare("basic") == 0) { interface->addCouplingDataReader( - dataName, + fieldConfig, new HeatFlux_Basic(mesh_, nameT_, nameKappa_)); DEBUG(adapterInfo("Added reader: Heat Flux for basic solvers. ")); } @@ -260,21 +264,21 @@ bool preciceAdapter::CHT::ConjugateHeatTransfer::addReaders(std::string dataName if (solverType_.compare("compressible") == 0) { interface->addCouplingDataReader( - dataName, + fieldConfig, new HeatTransferCoefficient_Compressible(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Heat Transfer Coefficient for compressible solvers.")); } else if (solverType_.compare("incompressible") == 0) { interface->addCouplingDataReader( - dataName, + fieldConfig, new HeatTransferCoefficient_Incompressible(mesh_, nameT_, nameRho_, nameCp_, namePr_, nameAlphat_)); DEBUG(adapterInfo("Added reader: Heat Transfer Coefficient for incompressible solvers. ")); } else if (solverType_.compare("basic") == 0) { interface->addCouplingDataReader( - dataName, + fieldConfig, new HeatTransferCoefficient_Basic(mesh_, nameT_, nameKappa_)); DEBUG(adapterInfo("Added reader: Heat Transfer Coefficient for basic solvers. ")); } diff --git a/CHT/CHT.H b/CHT/CHT.H index bbda2fca..d56faaa3 100644 --- a/CHT/CHT.H +++ b/CHT/CHT.H @@ -60,10 +60,10 @@ public: bool configure(const IOdictionary& adapterConfig); //- Add coupling data writers - bool addWriters(std::string dataName, Interface* interface); + bool addWriters(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface); //- Add coupling data readers - bool addReaders(std::string dataName, Interface* interface); + bool addReaders(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface); }; } diff --git a/CouplingDataUser.C b/CouplingDataUser.C index 0e38c480..257c57b0 100644 --- a/CouplingDataUser.C +++ b/CouplingDataUser.C @@ -24,6 +24,22 @@ const std::string& preciceAdapter::CouplingDataUser::dataName() return dataName_; } +void preciceAdapter::CouplingDataUser::setFlipNormal(bool flipNormal) +{ + flipNormal_ = flipNormal; +} + +void preciceAdapter::CouplingDataUser::applyFlipNormal(precice::span dataBuffer) +{ + if (flipNormal_) + { + for (double& val : dataBuffer) + { + val *= -1.0; + } + } +} + void preciceAdapter::CouplingDataUser::setPatchIDs(std::vector patchIDs) { patchIDs_ = patchIDs; diff --git a/CouplingDataUser.H b/CouplingDataUser.H index 8faab32a..ed9c9261 100644 --- a/CouplingDataUser.H +++ b/CouplingDataUser.H @@ -2,6 +2,7 @@ #define COUPLINGDATAUSER_H #include "Utilities.H" +#include #include #include @@ -42,6 +43,9 @@ protected: //- location type of the interface LocationType locationType_ = LocationType::none; + //- flip normal option + bool flipNormal_ = false; + public: //- Constructor CouplingDataUser(); @@ -58,6 +62,12 @@ public: //- Get the data name const std::string& dataName(); + //- Set flip normal option + void setFlipNormal(bool flipNormal); + + //- Apply flip normal to the data buffer (multiply by -1) + void applyFlipNormal(precice::span dataBuffer); + //- Set the patch IDs that form the interface void setPatchIDs(std::vector patchIDs); diff --git a/FF/FF.C b/FF/FF.C index 49a2bc3d..cea8613b 100644 --- a/FF/FF.C +++ b/FF/FF.C @@ -125,77 +125,79 @@ std::string preciceAdapter::FF::FluidFluid::determineSolverType() return solverType; } -bool preciceAdapter::FF::FluidFluid::addWriters(std::string dataName, Interface* interface) +bool preciceAdapter::FF::FluidFluid::addWriters(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface) { + std::string dataName = fieldConfig.name; + bool found = true; // Set to false later, if needed. if (matchingStrings(dataName, "VelocityGradient")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new VelocityGradient(mesh_, nameU_)); DEBUG(adapterInfo("Added writer: Velocity Gradient.")); } else if (matchingStrings(dataName, "Velocity")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Velocity(mesh_, nameU_, namePhi_, fluxCorrection_)); DEBUG(adapterInfo("Added writer: Velocity.")); } else if (matchingStrings(dataName, "PressureGradientFull")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new PressureGradientFull(mesh_, nameP_)); DEBUG(adapterInfo("Added writer: Full Pressure Gradient.")); } else if (matchingStrings(dataName, "PressureGradient")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new PressureGradient(mesh_, nameP_)); DEBUG(adapterInfo("Added writer: Pressure Gradient.")); } else if (matchingStrings(dataName, "Pressure")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Pressure(mesh_, nameP_)); DEBUG(adapterInfo("Added writer: Pressure.")); } else if (matchingStrings(dataName, "FlowTemperatureGradient")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new TemperatureGradient(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Flow Temperature Gradient.")); } else if (matchingStrings(dataName, "FlowTemperature")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Temperature(mesh_, nameT_)); DEBUG(adapterInfo("Added writer: Flow Temperature.")); } else if (matchingStrings(dataName, "AlphaGradient")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new AlphaGradient(mesh_, nameAlpha_)); DEBUG(adapterInfo("Added writer: Alpha Gradient.")); } else if (matchingStrings(dataName, "Alpha")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Alpha(mesh_, nameAlpha_)); DEBUG(adapterInfo("Added writer: Alpha.")); } else if (matchingStrings(dataName, "Phi")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Phi(mesh_, namePhi_)); DEBUG(adapterInfo("Added writer: Phi.")); } @@ -213,91 +215,93 @@ bool preciceAdapter::FF::FluidFluid::addWriters(std::string dataName, Interface* return found; } -bool preciceAdapter::FF::FluidFluid::addReaders(std::string dataName, Interface* interface) +bool preciceAdapter::FF::FluidFluid::addReaders(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface) { + std::string dataName = fieldConfig.name; + bool found = true; // Set to false later, if needed. if (matchingStrings(dataName, "VelocityGradient")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new VelocityGradient(mesh_, nameU_)); DEBUG(adapterInfo("Added reader: VelocityGradient.")); } else if (matchingStrings(dataName, "Velocity")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Velocity(mesh_, nameU_, namePhi_)); DEBUG(adapterInfo("Added reader: Velocity.")); } else if (matchingStrings(dataName, "PressureGradient")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new PressureGradient(mesh_, nameP_)); DEBUG(adapterInfo("Added reader: Pressure Gradient.")); } else if (matchingStrings(dataName, "Pressure")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Pressure(mesh_, nameP_)); DEBUG(adapterInfo("Added reader: Pressure.")); } else if (matchingStrings(dataName, "FlowTemperatureGradient")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new TemperatureGradient(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Flow Temperature Gradient.")); } else if (matchingStrings(dataName, "FlowTemperature")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Temperature(mesh_, nameT_)); DEBUG(adapterInfo("Added reader: Flow Temperature.")); } else if (matchingStrings(dataName, "AlphaGradient")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new AlphaGradient(mesh_, nameAlpha_)); DEBUG(adapterInfo("Added reader: Alpha Gradient.")); } else if (matchingStrings(dataName, "Alpha")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Alpha(mesh_, nameAlpha_)); DEBUG(adapterInfo("Added reader: Alpha.")); } else if (matchingStrings(dataName, "Phi")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Phi(mesh_, namePhi_)); DEBUG(adapterInfo("Added reader: Phi.")); } else if (matchingStrings(dataName, "DragForce")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new DragForce(mesh_, nameDragForce_)); DEBUG(adapterInfo("Added reader: DragForce.")); } else if (matchingStrings(dataName, "ExplicitMomentum")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new ExplicitMomentum(mesh_, nameExplicitMomentum_)); DEBUG(adapterInfo("Added reader: ExplicitMomentum.")); } else if (matchingStrings(dataName, "ImplicitMomentum")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new ImplicitMomentum(mesh_, nameImplicitMomentum_)); DEBUG(adapterInfo("Added reader: ImplicitMomentum.")); } diff --git a/FF/FF.H b/FF/FF.H index a430000b..d57d77e0 100644 --- a/FF/FF.H +++ b/FF/FF.H @@ -77,10 +77,10 @@ public: bool configure(const IOdictionary& adapterConfig); //- Add coupling data writers - bool addWriters(std::string dataName, Interface* interface); + bool addWriters(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface); //- Add coupling data readers - bool addReaders(std::string dataName, Interface* interface); + bool addReaders(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface); }; } diff --git a/FSI/FSI.C b/FSI/FSI.C index 16aa88c3..728445a6 100644 --- a/FSI/FSI.C +++ b/FSI/FSI.C @@ -130,14 +130,16 @@ std::string preciceAdapter::FSI::FluidStructureInteraction::determineSolverType( } -bool preciceAdapter::FSI::FluidStructureInteraction::addWriters(std::string dataName, Interface* interface) +bool preciceAdapter::FSI::FluidStructureInteraction::addWriters(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface) { + std::string dataName = fieldConfig.name; + bool found = true; // Set to false later, if needed. if (matchingStrings(dataName, "Force")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Force(mesh_, solverType_, nameForce_) /* TODO: Add any other arguments here */ ); DEBUG(adapterInfo("Added writer: Force.")); @@ -145,21 +147,21 @@ bool preciceAdapter::FSI::FluidStructureInteraction::addWriters(std::string data else if (matchingStrings(dataName, "DisplacementDelta")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new DisplacementDelta(mesh_, namePointDisplacement_, nameCellDisplacement_)); DEBUG(adapterInfo("Added writer: DisplacementDelta.")); } else if (matchingStrings(dataName, "Displacement")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Displacement(mesh_, namePointDisplacement_, nameCellDisplacement_)); DEBUG(adapterInfo("Added writer: Displacement.")); } else if (matchingStrings(dataName, "Stress")) { interface->addCouplingDataWriter( - dataName, + fieldConfig, new Stress(mesh_, solverType_) /* TODO: Add any other arguments here */ ); DEBUG(adapterInfo("Added writer: Stress.")); @@ -178,14 +180,16 @@ bool preciceAdapter::FSI::FluidStructureInteraction::addWriters(std::string data return found; } -bool preciceAdapter::FSI::FluidStructureInteraction::addReaders(std::string dataName, Interface* interface) +bool preciceAdapter::FSI::FluidStructureInteraction::addReaders(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface) { + std::string dataName = fieldConfig.name; + bool found = true; // Set to false later, if needed. if (matchingStrings(dataName, "Force")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Force(mesh_, solverType_, nameForce_) /* TODO: Add any other arguments here */ ); DEBUG(adapterInfo("Added reader: Force.")); @@ -193,21 +197,21 @@ bool preciceAdapter::FSI::FluidStructureInteraction::addReaders(std::string data else if (matchingStrings(dataName, "DisplacementDelta")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new DisplacementDelta(mesh_, namePointDisplacement_, nameCellDisplacement_)); DEBUG(adapterInfo("Added reader: DisplacementDelta.")); } else if (matchingStrings(dataName, "Displacement")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Displacement(mesh_, namePointDisplacement_, nameCellDisplacement_)); DEBUG(adapterInfo("Added reader: Displacement.")); } else if (matchingStrings(dataName, "Stress")) { interface->addCouplingDataReader( - dataName, + fieldConfig, new Stress(mesh_, solverType_) /* TODO: Add any other arguments here */ ); DEBUG(adapterInfo("Added reader: Stress.")); diff --git a/FSI/FSI.H b/FSI/FSI.H index 52d948fa..8e588792 100644 --- a/FSI/FSI.H +++ b/FSI/FSI.H @@ -62,10 +62,10 @@ public: bool configure(const IOdictionary& adapterConfig); //- Add coupling data writers - bool addWriters(std::string dataName, Interface* interface); + bool addWriters(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface); //- Add coupling data readers - bool addReaders(std::string dataName, Interface* interface); + bool addReaders(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface); //- Return the nameCellDisplacement_ field name std::string getCellDisplacementFieldName(); diff --git a/Interface.C b/Interface.C index 00dfe3f0..85c5dc21 100644 --- a/Interface.C +++ b/Interface.C @@ -434,11 +434,14 @@ void preciceAdapter::Interface::configureMesh(const fvMesh& mesh, const std::str void preciceAdapter::Interface::addCouplingDataWriter( - std::string dataName, + const FieldConfig& fieldConfig, CouplingDataUser* couplingDataWriter) { // Set the data name (from preCICE) - couplingDataWriter->setDataName(dataName); + couplingDataWriter->setDataName(fieldConfig.name); + + // Set the flip normal option + couplingDataWriter->setFlipNormal(fieldConfig.flip_normal); // Set the patchIDs of the patches that form the interface couplingDataWriter->setPatchIDs(patchIDs_); @@ -461,11 +464,14 @@ void preciceAdapter::Interface::addCouplingDataWriter( void preciceAdapter::Interface::addCouplingDataReader( - std::string dataName, + const FieldConfig& fieldConfig, preciceAdapter::CouplingDataUser* couplingDataReader) { // Set the patchIDs of the patches that form the interface - couplingDataReader->setDataName(dataName); + couplingDataReader->setDataName(fieldConfig.name); + + // Set the flip normal option + couplingDataReader->setFlipNormal(fieldConfig.flip_normal); // Add the CouplingDataUser to the list of readers couplingDataReader->setPatchIDs(patchIDs_); @@ -523,10 +529,6 @@ void preciceAdapter::Interface::createBuffer() // Create the data buffer // An interface has only one data buffer, which is shared between several // CouplingDataUsers. - // TODO: Check (write tests) if this works properly when we have multiple - // scalar and vector coupling data users in an interface. With the current - // preCICE implementation, it should work as, when writing scalars, - // it should only use the first 1/3 elements of the buffer. dataBuffer_.resize(dataBufferSize); } @@ -545,12 +547,16 @@ void preciceAdapter::Interface::readCouplingData(double relativeReadTime) // We could add a sanity check here // nReadData == vertexIDs_.size() * (1 + (dim_ - 1) * static_cast(couplingDataReader->hasVectorData())); + precice::span dataSpanRead {dataBuffer_.data(), nReadData}; precice_.readData( meshName_, couplingDataReader->dataName(), vertexIDs_, relativeReadTime, - {dataBuffer_.data(), nReadData}); + dataSpanRead); + + // Apply flip normal if required + couplingDataReader->applyFlipNormal(dataSpanRead); // Read the received data from the buffer couplingDataReader->read(dataBuffer_.data(), dim_); @@ -559,10 +565,6 @@ void preciceAdapter::Interface::readCouplingData(double relativeReadTime) void preciceAdapter::Interface::writeCouplingData() { - // TODO: wrap around isWriteDataRequired - // Does the participant need to write data or is it subcycling? - // if (precice_.isWriteDataRequired(computedTimestepLength)) - // { // Make every coupling data writer write for (uint i = 0; i < couplingDataWriters_.size(); i++) { @@ -573,14 +575,18 @@ void preciceAdapter::Interface::writeCouplingData() // Write the data into the adapter's buffer auto nWrittenData = couplingDataWriter->write(dataBuffer_.data(), meshConnectivity_, dim_); + precice::span dataSpanWritten {dataBuffer_.data(), nWrittenData}; + + // Apply flip normal if required + couplingDataWriter->applyFlipNormal(dataSpanWritten); + // Make preCICE write vector or scalar data precice_.writeData( meshName_, couplingDataWriter->dataName(), vertexIDs_, - {dataBuffer_.data(), nWrittenData}); + dataSpanWritten); } - // } } preciceAdapter::Interface::~Interface() diff --git a/Interface.H b/Interface.H index a4d99c11..6ff52a69 100644 --- a/Interface.H +++ b/Interface.H @@ -91,13 +91,13 @@ public: //- Add a CouplingDataUser to read data from the interface void addCouplingDataReader( - std::string dataName, + const FieldConfig& fieldConfig, CouplingDataUser* couplingDataReader); //- Add a CouplingDataUser to write data on the interface void addCouplingDataWriter( - std::string dataName, + const FieldConfig& fieldConfig, CouplingDataUser* couplingDataWriter); //- Allocate an appropriate buffer for scalar or vector data. diff --git a/module-generic/Generic.C b/module-generic/Generic.C index 31affa34..0adadadf 100644 --- a/module-generic/Generic.C +++ b/module-generic/Generic.C @@ -41,32 +41,32 @@ bool preciceAdapter::Generic::GenericInterface::readConfig(const IOdictionary& a return true; } -bool preciceAdapter::Generic::GenericInterface::addWriters(const preciceAdapter::FieldConfig& FieldConfig, Interface* interface) +bool preciceAdapter::Generic::GenericInterface::addWriters(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface) { bool found = false; // Force to use the new schema with the Generic module - if (FieldConfig.solver_name != "Undefined (legacy mode)") + if (fieldConfig.solver_name != "Undefined (legacy mode)") { // Determine type of field - if (mesh_.foundObject(FieldConfig.solver_name)) + if (mesh_.foundObject(fieldConfig.solver_name)) { found = true; interface->addCouplingDataWriter( - FieldConfig.name, - new ScalarFieldCoupler(mesh_, FieldConfig)); + fieldConfig, + new ScalarFieldCoupler(mesh_, fieldConfig)); } - else if (mesh_.foundObject(FieldConfig.solver_name)) + else if (mesh_.foundObject(fieldConfig.solver_name)) { found = true; interface->addCouplingDataWriter( - FieldConfig.name, - new VectorFieldCoupler(mesh_, FieldConfig)); + fieldConfig, + new VectorFieldCoupler(mesh_, fieldConfig)); } else { found = false; - std::string msg = "Generic module: Data \"" + FieldConfig.name + "\", solver name: \"" + FieldConfig.solver_name + "\" not found!\n"; + std::string msg = "Generic module: Data \"" + fieldConfig.name + "\", solver name: \"" + fieldConfig.solver_name + "\" not found!\n"; msg += "Available fields: " + availableVolScalarFields + availableVolVectorFields; adapterInfo(msg, "warning"); } @@ -74,37 +74,37 @@ bool preciceAdapter::Generic::GenericInterface::addWriters(const preciceAdapter: if (found) { - DEBUG(adapterInfo("Added writer: " + FieldConfig.name)); + DEBUG(adapterInfo("Added writer: " + fieldConfig.name)); } return found; } -bool preciceAdapter::Generic::GenericInterface::addReaders(const preciceAdapter::FieldConfig& FieldConfig, Interface* interface) +bool preciceAdapter::Generic::GenericInterface::addReaders(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface) { bool found = false; // Force to use the new schema with the Generic modul - if (FieldConfig.solver_name != "Undefined (legacy mode)") + if (fieldConfig.solver_name != "Undefined (legacy mode)") { // Determine type of field - if (mesh_.foundObject(FieldConfig.solver_name)) + if (mesh_.foundObject(fieldConfig.solver_name)) { found = true; interface->addCouplingDataReader( - FieldConfig.name, - new ScalarFieldCoupler(mesh_, FieldConfig)); + fieldConfig, + new ScalarFieldCoupler(mesh_, fieldConfig)); } - else if (mesh_.foundObject(FieldConfig.solver_name)) + else if (mesh_.foundObject(fieldConfig.solver_name)) { found = true; interface->addCouplingDataReader( - FieldConfig.name, - new VectorFieldCoupler(mesh_, FieldConfig)); + fieldConfig, + new VectorFieldCoupler(mesh_, fieldConfig)); } else { found = false; - std::string msg = "Generic module: Data \"" + FieldConfig.name + "\" (solver name: \"" + FieldConfig.solver_name + "\") not found!\n"; + std::string msg = "Generic module: Data \"" + fieldConfig.name + "\" (solver name: \"" + fieldConfig.solver_name + "\") not found!\n"; msg += "Available fields: " + availableVolScalarFields + availableVolVectorFields; adapterInfo(msg, "warning"); } @@ -112,7 +112,7 @@ bool preciceAdapter::Generic::GenericInterface::addReaders(const preciceAdapter: if (found) { - DEBUG(adapterInfo("Added reader: " + FieldConfig.name)); + DEBUG(adapterInfo("Added reader: " + fieldConfig.name)); } return found; } diff --git a/module-generic/Generic.H b/module-generic/Generic.H index 28d41db7..de284cc6 100644 --- a/module-generic/Generic.H +++ b/module-generic/Generic.H @@ -43,10 +43,10 @@ public: bool configure(const IOdictionary& adapterConfig); //- Add coupling data writers - bool addWriters(const preciceAdapter::FieldConfig& FieldConfig, Interface* interface); + bool addWriters(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface); //- Add coupling data readers - bool addReaders(const preciceAdapter::FieldConfig& FieldConfig, Interface* interface); + bool addReaders(const preciceAdapter::FieldConfig& fieldConfig, Interface* interface); }; } diff --git a/module-generic/ReadWrite.C b/module-generic/ReadWrite.C index c90f8eba..873d4c11 100644 --- a/module-generic/ReadWrite.C +++ b/module-generic/ReadWrite.C @@ -11,19 +11,19 @@ using namespace Foam; preciceAdapter::Generic::ScalarFieldCoupler::ScalarFieldCoupler( const Foam::fvMesh& mesh, - const preciceAdapter::FieldConfig& FieldConfig) + const preciceAdapter::FieldConfig& fieldConfig) : scalarField_( const_cast( - &mesh.lookupObject(FieldConfig.solver_name))), + &mesh.lookupObject(fieldConfig.solver_name))), mesh_(mesh), - FieldConfig_(FieldConfig) + fieldConfig_(fieldConfig) { dataType_ = scalar; } void preciceAdapter::Generic::ScalarFieldCoupler::initialize() { - if (FieldConfig_.operation == "surface-normal-gradient") + if (fieldConfig_.operation == "surface-normal-gradient") { if (this->locationType_ != LocationType::faceCenters) { @@ -31,7 +31,7 @@ void preciceAdapter::Generic::ScalarFieldCoupler::initialize() } } - if (FieldConfig_.operation == "gradient") + if (fieldConfig_.operation == "gradient") { adapterInfo("Generic module: The gradient operation is not yet supported for scalar fields. Maybe you meant surface-normal-gradient?", "error"); } @@ -42,7 +42,7 @@ std::size_t preciceAdapter::Generic::ScalarFieldCoupler::write(double* buffer, b { int bufferIndex = 0; - if (FieldConfig_.operation == "surface-normal-gradient") + if (fieldConfig_.operation == "surface-normal-gradient") { // For every boundary patch of the interface for (uint j = 0; j < patchIDs_.size(); j++) @@ -198,19 +198,19 @@ bool preciceAdapter::Generic::ScalarFieldCoupler::isLocationTypeSupported(const std::string preciceAdapter::Generic::ScalarFieldCoupler::getDataName() const { - return FieldConfig_.name; + return fieldConfig_.name; } //----- preciceAdapter::Generic::VectorFieldCoupler ----------------------------------------- preciceAdapter::Generic::VectorFieldCoupler::VectorFieldCoupler( const Foam::fvMesh& mesh, - const preciceAdapter::FieldConfig& FieldConfig) + const preciceAdapter::FieldConfig& fieldConfig) : vectorField_( const_cast( - &mesh.lookupObject(FieldConfig.solver_name))), + &mesh.lookupObject(fieldConfig.solver_name))), mesh_(mesh), - FieldConfig_(FieldConfig) + fieldConfig_(fieldConfig) { dataType_ = vector; } @@ -218,7 +218,7 @@ preciceAdapter::Generic::VectorFieldCoupler::VectorFieldCoupler( void preciceAdapter::Generic::VectorFieldCoupler::initialize() { // In the Generic module, for vector fields only the value operation is supported for now, i.e., cannot write gradients. - if (FieldConfig_.operation == "gradient" || FieldConfig_.operation == "surface-normal-gradient") + if (fieldConfig_.operation == "gradient" || fieldConfig_.operation == "surface-normal-gradient") { adapterInfo("Generic module: The gradient operation is not yet supported for vector fields.", "error"); } @@ -400,5 +400,5 @@ bool preciceAdapter::Generic::VectorFieldCoupler::isLocationTypeSupported(const std::string preciceAdapter::Generic::VectorFieldCoupler::getDataName() const { - return FieldConfig_.name; + return fieldConfig_.name; } diff --git a/module-generic/ReadWrite.H b/module-generic/ReadWrite.H index fbe85e5b..8b204d58 100644 --- a/module-generic/ReadWrite.H +++ b/module-generic/ReadWrite.H @@ -17,10 +17,10 @@ class ScalarFieldCoupler : public CouplingDataUser // One CouplingDataUser for e protected: Foam::volScalarField* scalarField_; const Foam::fvMesh& mesh_; - const preciceAdapter::FieldConfig& FieldConfig_; + const preciceAdapter::FieldConfig& fieldConfig_; public: - ScalarFieldCoupler(const Foam::fvMesh& mesh, const preciceAdapter::FieldConfig& FieldConfig); + ScalarFieldCoupler(const Foam::fvMesh& mesh, const preciceAdapter::FieldConfig& fieldConfig); void initialize(); std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); @@ -36,10 +36,10 @@ class VectorFieldCoupler : public CouplingDataUser protected: Foam::volVectorField* vectorField_; const Foam::fvMesh& mesh_; - const preciceAdapter::FieldConfig& FieldConfig_; + const preciceAdapter::FieldConfig& fieldConfig_; public: - VectorFieldCoupler(const Foam::fvMesh& mesh, const preciceAdapter::FieldConfig& FieldConfig); + VectorFieldCoupler(const Foam::fvMesh& mesh, const preciceAdapter::FieldConfig& fieldConfig); void initialize(); std::size_t write(double* buffer, bool meshConnectivity, const unsigned int dim); From d3a5e2b0217f162981deaf8cb210a5f48ab6b904 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 14:28:27 +0100 Subject: [PATCH 45/70] Bump actions/upload-artifact from 6 to 7 (#399) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-custom.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 2162c3e9..d26c791d 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -142,7 +142,7 @@ jobs: wait $PIDfluid fi - name: Archive logs - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: logs path: | @@ -156,7 +156,7 @@ jobs: tutorials/partitioned-pipe/fluid1-openfoam-pimplefoam/fluid1-openfoam-pimplefoam.log tutorials/partitioned-pipe/fluid2-openfoam-pimplefoam/fluid2-openfoam-pimplefoam.log - name: Archive case files - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: case-files path: | From 6ef89e8e3325478b11fa9ca3846445263e9116ef Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:11:36 +0100 Subject: [PATCH 46/70] Fix dangling reference (#401) --- Adapter.C | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adapter.C b/Adapter.C index e03de9ba..6c2c8568 100644 --- a/Adapter.C +++ b/Adapter.C @@ -329,7 +329,7 @@ try DEBUG(adapterInfo("Adding coupling data writers...")); for (uint j = 0; j < interfacesConfig_.at(i).writeData.size(); j++) { - FieldConfig fieldConfig = interfacesConfig_.at(i).writeData.at(j); + const FieldConfig& fieldConfig = interfacesConfig_.at(i).writeData.at(j); std::string dataName = fieldConfig.name; unsigned int inModules = 0; @@ -381,7 +381,7 @@ try DEBUG(adapterInfo("Adding coupling data readers...")); for (uint j = 0; j < interfacesConfig_.at(i).readData.size(); j++) { - FieldConfig fieldConfig = interfacesConfig_.at(i).readData.at(j); + const FieldConfig& fieldConfig = interfacesConfig_.at(i).readData.at(j); std::string dataName = fieldConfig.name; unsigned int inModules = 0; From 4b42bb03147511a003a4455fb4242bebec9cfb2d Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Sun, 3 May 2026 22:16:41 +0200 Subject: [PATCH 47/70] Implement operation gradient in Generic module (#398) Co-authored-by: Gerasimos Chourdakis --- Interface.C | 36 +++++++++++++ changelog-entries/398.md | 1 + docs/config.md | 8 ++- module-generic/ReadWrite.C | 107 ++++++++++++++++++++++++++++++++++--- module-generic/ReadWrite.H | 3 +- 5 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 changelog-entries/398.md diff --git a/Interface.C b/Interface.C index 85c5dc21..b8b51891 100644 --- a/Interface.C +++ b/Interface.C @@ -460,6 +460,27 @@ void preciceAdapter::Interface::addCouplingDataWriter( // Add the CouplingDataUser to the list of writers couplingDataWriters_.push_back(couplingDataWriter); + + // Check whether config mismatch scalar or vector data + unsigned int preciceDataDim = precice_.getDataDimensions(meshName_, fieldConfig.name); + bool isVectorDataConfig = (dim_ == preciceDataDim); // 2d or 3d vectors + bool isVectorDataAdapter = couplingDataWriter->hasVectorData(); + + // Taking gradient of a scalar field results in a vector field + bool isScalarConfiguredGradient = (!isVectorDataConfig) && (fieldConfig.operation == "gradient"); + + if (isVectorDataConfig != isVectorDataAdapter) + { + adapterInfo("Data dimension mismatch for field \"" + fieldConfig.name + "\". " + + "The field is defined as " + (isVectorDataConfig ? "vector" : "scalar") + + " data in the preCICE configuration, but the data is " + + (isVectorDataAdapter ? "vector" : "scalar") + + " in the adapter. Please check your preCICE configuration." + + (isScalarConfiguredGradient ? " If you are trying to couple the gradient of a scalar field, \ + make sure the data is defined vector in the preCICE config." + : ""), + "error"); + } } @@ -490,6 +511,21 @@ void preciceAdapter::Interface::addCouplingDataReader( // Add the CouplingDataUser to the list of readers couplingDataReaders_.push_back(couplingDataReader); + + // Check whether config mismatch scalar or vector data + unsigned int preciceDataDim = precice_.getDataDimensions(meshName_, fieldConfig.name); + bool isVectorDataConfig = (dim_ == preciceDataDim); // 2d or 3d vectors + bool isVectorDataAdapter = couplingDataReader->hasVectorData(); + + if (isVectorDataConfig != isVectorDataAdapter) + { + adapterInfo("Data dimension mismatch for field \"" + fieldConfig.name + "\". " + + "The field is defined as " + (isVectorDataConfig ? "vector" : "scalar") + + " data in the preCICE configuration, but the data is " + + (isVectorDataAdapter ? "vector" : "scalar") + + " in the adapter. Please check your preCICE configuration.", + "error"); + } } void preciceAdapter::Interface::createBuffer() diff --git a/changelog-entries/398.md b/changelog-entries/398.md new file mode 100644 index 00000000..292fbcc3 --- /dev/null +++ b/changelog-entries/398.md @@ -0,0 +1 @@ +- Added support for `gradient` operation in the `Generic` module [#398](https://github.com/precice/openfoam-adapter/pull/398) \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index 7d78adda..275d9d5a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -283,7 +283,7 @@ interfaces { name Velocity; // Data name as defined in preCICE config solver_name U; // Optional: Field name in OpenFOAM (defaults to same as 'name') - operation value; // Optional: Operation to perform on data (defaults to 'value') + operation value; // Optional: Operation to perform on data (defaults to 'value', other options are `gradient` and `surface-normal-gradient`) flip-normal false; // Optional: Flip the normal direction (defaults to 'false') } @@ -302,7 +302,11 @@ Explicitly setting `locations volumeCenters;` in preciceDict is required for vol When reading on the OpenFOAM side, the Generic module automatically detects the boundary condition type of the coupled patch to apply the received data. The boundary condition must be respected, therefore the data received is applied either as a `fixedValue` or `fixedGradient` boundary condition, determined by the type set in the `0/` files. -The `operation surface-normal-gradient;` is currently only supported for scalar field surface writing. The operation `gradient` as well as the option `flip-normal` are not yet supported by the adapter. +Limitations: + +- The operation `gradient` is currently only supported for writing scalar fields (resulting to a vector field). +- The operation `surface-normal-gradient` is currently only supported for surface scalar field writing (resulting to a scalar field). +- The operation `flip-normal` is supported for all field types across all modules, but the most relevant use case might be for surface scalar fields, in order to change the direction of, e.g., a flux. ### Volume coupling diff --git a/module-generic/ReadWrite.C b/module-generic/ReadWrite.C index 873d4c11..f1c2b0da 100644 --- a/module-generic/ReadWrite.C +++ b/module-generic/ReadWrite.C @@ -18,7 +18,24 @@ preciceAdapter::Generic::ScalarFieldCoupler::ScalarFieldCoupler( mesh_(mesh), fieldConfig_(fieldConfig) { - dataType_ = scalar; + if (fieldConfig_.operation == "gradient") + { + dataType_ = vector; + + // Allocate memory for gradient field + gradientFieldPtr_ = new volVectorField( + IOobject( + "grad" + fieldConfig_.solver_name, + mesh_.time().timeName(), + mesh_, + IOobject::NO_READ, + IOobject::NO_WRITE), + fvc::grad(*scalarField_)); + } + else + { + dataType_ = scalar; + } } void preciceAdapter::Generic::ScalarFieldCoupler::initialize() @@ -30,11 +47,6 @@ void preciceAdapter::Generic::ScalarFieldCoupler::initialize() adapterInfo("Generic module: The surface-normal-gradient operation is only supported for faceCenters location type.", "error"); } } - - if (fieldConfig_.operation == "gradient") - { - adapterInfo("Generic module: The gradient operation is not yet supported for scalar fields. Maybe you meant surface-normal-gradient?", "error"); - } } @@ -42,6 +54,84 @@ std::size_t preciceAdapter::Generic::ScalarFieldCoupler::write(double* buffer, b { int bufferIndex = 0; + if (fieldConfig_.operation == "gradient") + { + // Calculate the full gradient (volVectorField) + *gradientFieldPtr_ = fvc::grad(*scalarField_); + volVectorField& gradScalarField = *gradientFieldPtr_; + + if (this->locationType_ == LocationType::volumeCenters) + { + if (cellSetNames_.empty()) + { + for (const auto& cell : gradScalarField.internalField()) + { + // x-dimension + buffer[bufferIndex++] = cell.x(); + + // y-dimension + buffer[bufferIndex++] = cell.y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = cell.z(); + } + } + } + else + { + for (const auto& cellSetName : cellSetNames_) + { + cellSet overlapRegion(scalarField_->mesh(), cellSetName); + const labelList& cells = overlapRegion.toc(); + + for (const auto& currentCell : cells) + { + // x-dimension + buffer[bufferIndex++] = gradScalarField.internalField()[currentCell].x(); + + // y-dimension + buffer[bufferIndex++] = gradScalarField.internalField()[currentCell].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = gradScalarField.internalField()[currentCell].z(); + } + } + } + } + } + + // For every boundary patch of the interface + for (uint j = 0; j < patchIDs_.size(); j++) + { + int patchID = patchIDs_.at(j); + + // For every cell of the patch + forAll(gradScalarField.boundaryField()[patchID], i) + { + // Copy the velocity into the buffer + // x-dimension + buffer[bufferIndex++] = + gradScalarField.boundaryField()[patchID][i].x(); + + // y-dimension + buffer[bufferIndex++] = + gradScalarField.boundaryField()[patchID][i].y(); + + if (dim == 3) + { + // z-dimension + buffer[bufferIndex++] = + gradScalarField.boundaryField()[patchID][i].z(); + } + } + } + return bufferIndex; + } + if (fieldConfig_.operation == "surface-normal-gradient") { // For every boundary patch of the interface @@ -125,6 +215,11 @@ void preciceAdapter::Generic::ScalarFieldCoupler::read(double* buffer, const uns { int bufferIndex = 0; + if (fieldConfig_.operation == "gradient" || fieldConfig_.operation == "surface-normal-gradient") + { + adapterInfo("Generic module: The gradient operation is only supported for writing.", "error"); + } + if (this->locationType_ == LocationType::volumeCenters) { if (cellSetNames_.empty()) diff --git a/module-generic/ReadWrite.H b/module-generic/ReadWrite.H index 8b204d58..de0f6bad 100644 --- a/module-generic/ReadWrite.H +++ b/module-generic/ReadWrite.H @@ -18,6 +18,7 @@ protected: Foam::volScalarField* scalarField_; const Foam::fvMesh& mesh_; const preciceAdapter::FieldConfig& fieldConfig_; + Foam::volVectorField* gradientFieldPtr_ = nullptr; public: ScalarFieldCoupler(const Foam::fvMesh& mesh, const preciceAdapter::FieldConfig& fieldConfig); @@ -28,7 +29,7 @@ public: bool isLocationTypeSupported(const bool meshConnectivity) const; std::string getDataName() const; - virtual ~ScalarFieldCoupler() {}; + virtual ~ScalarFieldCoupler() { delete gradientFieldPtr_; }; }; class VectorFieldCoupler : public CouplingDataUser From 3536713ce502b535073362b118c7bcefd0e5ae1b Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 27 May 2026 11:19:50 +0200 Subject: [PATCH 48/70] Update name of the system tests test suite (#403) Related to https://github.com/precice/tutorials/pull/799 --- .github/workflows/system-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/system-tests.yaml b/.github/workflows/system-tests.yaml index 4d673e2f..30a44dc7 100644 --- a/.github/workflows/system-tests.yaml +++ b/.github/workflows/system-tests.yaml @@ -39,7 +39,7 @@ jobs: needs: gather-refs uses: precice/tutorials/.github/workflows/run_testsuite_workflow.yml@develop with: - suites: openfoam_adapter_pr + suites: openfoam-adapter build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:d199bb3,OPENFOAM_EXECUTABLE:openfoam2512,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} system_tests_branch: develop log_level: "DEBUG" From ff0f1db58a21918c0b5abf645c3db3b7671d4498 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 27 May 2026 11:33:18 +0200 Subject: [PATCH 49/70] Bump preCICE to v3.4.1, update more versions (#404) --- .github/workflows/build-custom.input | 2 +- .github/workflows/build-custom.yml | 3 ++- .github/workflows/pre-commit.yml | 2 +- .github/workflows/system-tests.yaml | 2 +- .pre-commit-config.yaml | 4 ++-- docs/config.md | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-custom.input b/.github/workflows/build-custom.input index 5d075e44..d53d8057 100644 --- a/.github/workflows/build-custom.input +++ b/.github/workflows/build-custom.input @@ -1,7 +1,7 @@ virtualEnvironment="ubuntu-22.04" refAdapter="develop" versionOpenFOAM="2512" -versionpreCICE="v3.3.0" +versionpreCICE="v3.4.1" runTutorialHeatedPlate="false" runTutorialQuickstart="false" runTutorialPartitionedPipe="false" diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index d26c791d..d6226fc8 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -8,6 +8,7 @@ on: required: true type: choice options: + - ubuntu-26.04 - ubuntu-24.04 - ubuntu-22.04 - ubuntu-20.04 @@ -42,7 +43,7 @@ on: - 7 versionpreCICE: description: 'Version of preCICE to build with' - default: 'v3.3.0' + default: 'v3.4.1' required: true runTutorialHeatedPlate: description: Run tutorial flow-over-heated-plate diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index c5a65f97..1aba35f1 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -17,7 +17,7 @@ jobs: - name: Setup python uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: '3.x' check-latest: true - name: Install pre-commit run: pip install pre-commit diff --git a/.github/workflows/system-tests.yaml b/.github/workflows/system-tests.yaml index 30a44dc7..e0d93096 100644 --- a/.github/workflows/system-tests.yaml +++ b/.github/workflows/system-tests.yaml @@ -40,6 +40,6 @@ jobs: uses: precice/tutorials/.github/workflows/run_testsuite_workflow.yml@develop with: suites: openfoam-adapter - build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:d199bb3,OPENFOAM_EXECUTABLE:openfoam2512,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} + build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:v3.4.1,OPENFOAM_EXECUTABLE:openfoam2512,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} system_tests_branch: develop log_level: "DEBUG" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f6d7347..2f84ce04 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,14 +11,14 @@ repos: hooks: - id: clang-format - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.30.0 + rev: v0.48.0 hooks: - id: markdownlint files: "^docs/.*.md" - id: markdownlint-fix files: "^docs/.*.md" - repo: https://github.com/koalaman/shellcheck-precommit - rev: v0.10.0 + rev: v0.11.0 hooks: - id: shellcheck args: [ --external-sources, --exclude=SC1091 ] diff --git a/docs/config.md b/docs/config.md index 275d9d5a..4d99bb45 100644 --- a/docs/config.md +++ b/docs/config.md @@ -484,7 +484,7 @@ This is implemented for all CHT-related fields mapped with a `consistent` constr ### Notes on subcycling -If it is necessary to subcycle, i.e., do multiple solver time steps per coupling time window, on either the OpenFOAM side or another solver, please consider additional nuances. A good way to start is reading up on subcycling and [waveform time interpolation](https://precice.org/couple-your-code-waveform). Subcycling is *allowed* by preCICE and the OpenFOAM-adapter, but you may obtain different results with subcycling enabled or not depending on your problem and coupling configuration. This has to do with how the subcycling data is modelled in the time-integration of your problem, where the two independent solvers (coupling participants) with non-matching timesteps march forward in time. This depends on the time stepping scheme and the type of physical quantity that you are coupling. +If it is necessary to subcycle, i.e., do multiple solver time steps per coupling time window, on either the OpenFOAM side or another solver, please consider additional nuances. A good way to start is reading up on subcycling and [waveform time interpolation](https://precice.org/couple-your-code-waveform). Subcycling is allowed by preCICE and the OpenFOAM-adapter, but you may obtain different results with subcycling enabled or not depending on your problem and coupling configuration. This has to do with how the subcycling data is modelled in the time-integration of your problem, where the two independent solvers (coupling participants) with non-matching timesteps march forward in time. This depends on the time stepping scheme and the type of physical quantity that you are coupling. Currently, when doing subcycling in OpenFOAM on an FSI problem, the OpenFOAM-adapter will give the following warning: From 2c3062ce941915616ac763371805c57e15e02466 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 27 May 2026 11:44:39 +0200 Subject: [PATCH 50/70] Remove deprecated CI workflows They have already been replaced by pre-commit hooks. --- .github/workflows/check-format.yml | 13 ------------- .github/workflows/check-shell.yml | 10 ---------- 2 files changed, 23 deletions(-) delete mode 100644 .github/workflows/check-format.yml delete mode 100644 .github/workflows/check-shell.yml diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml deleted file mode 100644 index 039f1f35..00000000 --- a/.github/workflows/check-format.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Check code formatting -on: [push, pull_request] -jobs: - formatting-check: - name: Check formatting (clang-format) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action@main - with: - clang-format-version: '11' - check-path: '.' diff --git a/.github/workflows/check-shell.yml b/.github/workflows/check-shell.yml deleted file mode 100644 index 1c220d10..00000000 --- a/.github/workflows/check-shell.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Lint scripts -on: [push, pull_request] -jobs: - check_shell: - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v6 - - name: Lint shell scripts (shellcheck) - uses: ludeeus/action-shellcheck@master From c25e8c9305cf9b2a7fc793fa9cd011066b9bff1d Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:57:58 +0300 Subject: [PATCH 51/70] Mesh points & volumes checkpointing (#369) Co-authored-by: Gerasimos Chourdakis Co-authored-by: Daniels Vidulejs --- Adapter.C | 101 +++++++++++++++++++++++++++++++++++---- Adapter.H | 26 ++++++++++ changelog-entries/369.md | 1 + docs/extend.md | 2 +- 4 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 changelog-entries/369.md diff --git a/Adapter.C b/Adapter.C index 6c2c8568..e98615fe 100644 --- a/Adapter.C +++ b/Adapter.C @@ -843,6 +843,14 @@ void preciceAdapter::Adapter::storeMeshPoints() meshCheckPointed_ = true; } writeMeshCheckpoint(); + + // For Ddt schemes which use up to two previous timesteps V0, V00 + if (volumeCheckpointState_ != VolumeCheckpointState::ALL_AVAILABLE) + { + setupMeshVolCheckpointing(); + } + + writeMeshVolCheckpoint(); } } @@ -864,17 +872,12 @@ void preciceAdapter::Adapter::reloadMeshPoints() readMeshCheckpoint(); DEBUG(adapterInfo("Moved mesh points to their previous locations.")); + + readMeshVolCheckpoint(); } void preciceAdapter::Adapter::setupMeshCheckpointing() { - // The other mesh Fields: - // C - // Cf - // Sf - // magSf - // delta - // are updated by the function fvMesh::movePoints. Only the meshPhi needs checkpointing. DEBUG(adapterInfo("Creating a list of the mesh checkpointed fields...")); // Add meshPhi (Face motion flux) addMeshCheckpointField(const_cast(mesh_.phi())); @@ -882,6 +885,29 @@ void preciceAdapter::Adapter::setupMeshCheckpointing() DEBUG(adapterInfo("Added " + mesh_.phi().name() + " to the list of checkpointed fields.")); } +void preciceAdapter::Adapter::setupMeshVolCheckpointing() +{ + // Add the mesh volumes for the current and up to two previous time steps (V, V0, V00), if available + if (volumeCheckpointState_ == VolumeCheckpointState::UNINITIALIZED) + { + // When V is available, also V0 is available (see fvMesh::movePoints, storeOldVol(V())) + addMeshVolCheckpointField(const_cast(mesh_.V())); + DEBUG(adapterInfo("Checkpoint mesh V")); + + addMeshVolCheckpointField(const_cast(mesh_.V0())); + DEBUG(adapterInfo("Checkpoint mesh V0")); + + volumeCheckpointState_ = VolumeCheckpointState::V_AND_V0_AVAILABLE; + } + else if (volumeCheckpointState_ == VolumeCheckpointState::V_AND_V0_AVAILABLE) + { + addMeshVolCheckpointField(const_cast(mesh_.V00())); + DEBUG(adapterInfo("Checkpoint mesh V00")); + + volumeCheckpointState_ = VolumeCheckpointState::ALL_AVAILABLE; + } +} + void preciceAdapter::Adapter::setupCheckpointing() { @@ -986,6 +1012,12 @@ void preciceAdapter::Adapter::addMeshCheckpointField(surfaceScalarField& field) meshSurfaceScalarFieldCopies_.push_back(new surfaceScalarField(field)); } +void preciceAdapter::Adapter::addMeshVolCheckpointField(volScalarField::Internal& field) +{ + meshVolFieldCopies_.push_back(new volScalarField::Internal(field)); +} + + void preciceAdapter::Adapter::addCheckpointField(volScalarField* field) { if (field) @@ -1409,6 +1441,49 @@ void preciceAdapter::Adapter::writeMeshCheckpoint() return; } +void preciceAdapter::Adapter::readMeshVolCheckpoint() +{ + // Reload V, V0, V00 + if (volumeCheckpointState_ == VolumeCheckpointState::V_AND_V0_AVAILABLE || volumeCheckpointState_ == VolumeCheckpointState::ALL_AVAILABLE) + { + // We do not need to reload V, as it is re-calculated in every time-step + + const_cast(mesh_.V0()) = *(meshVolFieldCopies_.at(0)); + DEBUG(adapterInfo("Read mesh volume " + meshVolFieldCopies_.at(0)->name())); + + DEBUG(adapterInfo("Mesh volumes checkpoint for time t = " + std::to_string(runTime_.value()) + " were read.")); + } + if (volumeCheckpointState_ == VolumeCheckpointState::ALL_AVAILABLE) + { + const_cast(mesh_.V00()) = *(meshVolFieldCopies_.at(1)); + DEBUG(adapterInfo("Read mesh volume " + meshVolFieldCopies_.at(1)->name())); + } + + return; +} + +void preciceAdapter::Adapter::writeMeshVolCheckpoint() +{ + // Store V, V0, V00 + if (volumeCheckpointState_ == VolumeCheckpointState::V_AND_V0_AVAILABLE || volumeCheckpointState_ == VolumeCheckpointState::ALL_AVAILABLE) + { + *(meshVolFieldCopies_.at(0)) = const_cast(mesh_.V()); + DEBUG(adapterInfo("Write mesh volume " + meshVolFieldCopies_.at(0)->name())); + + *(meshVolFieldCopies_.at(1)) = const_cast(mesh_.V0()); + DEBUG(adapterInfo("Write mesh volume " + meshVolFieldCopies_.at(1)->name())); + + DEBUG(adapterInfo("Mesh volumes checkpoint for time t = " + std::to_string(runTime_.value()) + " were stored.")); + } + if (volumeCheckpointState_ == VolumeCheckpointState::ALL_AVAILABLE) + { + *(meshVolFieldCopies_.at(2)) = const_cast(mesh_.V00()); + DEBUG(adapterInfo("Write mesh volume " + meshVolFieldCopies_.at(2)->name())); + } + + return; +} + void preciceAdapter::Adapter::end() try { @@ -1498,6 +1573,15 @@ void preciceAdapter::Adapter::teardown() } meshSurfaceScalarFieldCopies_.clear(); + for (uint i = 0; i < meshVolFieldCopies_.size(); i++) + { + delete meshVolFieldCopies_.at(i); + } + meshVolFieldCopies_.clear(); + + delete meshPoints_; + delete meshOldPoints_; + // volTensorField for (uint i = 0; i < volTensorFieldCopies_.size(); i++) { @@ -1529,9 +1613,6 @@ void preciceAdapter::Adapter::teardown() // NOTE: Add here delete for other types, if needed checkpointing_ = false; - - delete meshPoints_; - delete meshOldPoints_; } // Delete the CHT module diff --git a/Adapter.H b/Adapter.H index dff9b6a8..5ab6b4e6 100644 --- a/Adapter.H +++ b/Adapter.H @@ -152,12 +152,26 @@ private: //- Checkpointed time (index) Foam::label couplingIterationTimeIndex_; + + enum class VolumeCheckpointState : uint8_t + { + UNINITIALIZED = 0, + V_AND_V0_AVAILABLE = 1, + ALL_AVAILABLE = 2 + }; + + //- Volume checkpointing state + VolumeCheckpointState volumeCheckpointState_ = VolumeCheckpointState::UNINITIALIZED; + // Vectors of pointers to the checkpointed mesh fields and their copies //- Checkpointed mesh points, oldPoints Foam::pointField* meshPoints_ = nullptr; Foam::pointField* meshOldPoints_ = nullptr; + //- Checkpointed volScalarField mesh fields (V, V0, V00) + std::vector meshVolFieldCopies_; + //- Checkpointed surfaceScalarField mesh fields (phi) std::vector meshSurfaceScalarFields_; @@ -278,6 +292,9 @@ private: //- Configure the mesh checkpointing void setupMeshCheckpointing(); + //- Configure the mesh volume checkpointing + void setupMeshVolCheckpointing(); + //- Configure the checkpointing void setupCheckpointing(); @@ -299,6 +316,9 @@ private: // Add mesh checkpoint fields, depending on the type + //- Add a volScalarField::Internal mesh field (V, V0, V00) + void addMeshVolCheckpointField(volScalarField::Internal& field); + //- Add a surfaceScalarField mesh field (phi) void addMeshCheckpointField(Foam::surfaceScalarField& field); @@ -347,6 +367,12 @@ private: //- Write the checkpoint - store the fields and time void writeCheckpoint(); + //- Read the volume checkpoint - restore the mesh volume fields + void readMeshVolCheckpoint(); + + //- Write the volume checkpoint to a buffer - restore the mesh volume fields + void writeMeshVolCheckpoint(); + //- Destroy the preCICE interface and delete the allocated // memory in a proper way. Called by the destructor. void teardown(); diff --git a/changelog-entries/369.md b/changelog-entries/369.md new file mode 100644 index 00000000..3bf3e6cc --- /dev/null +++ b/changelog-entries/369.md @@ -0,0 +1 @@ +- Explicitly checkpoint mesh cell volumes (V, V0, V00) and support higher-order time derivative schemes, fix for implicit subcycling [#369](https://github.com/precice/openfoam-adapter/pull/369) diff --git a/docs/extend.md b/docs/extend.md index 5a00fc6f..1ff04b1c 100644 --- a/docs/extend.md +++ b/docs/extend.md @@ -72,6 +72,7 @@ To handle mesh motion (ALE), the adapter explicitly checkpoints: - Mesh Points `mesh_.points()` and `mesh_.oldPoints()` - Mesh Motion Flux `mesh_.phi()` +- Cell Volumes `V`, `V0`, and `V00` ### Non-checkpointed fields @@ -86,7 +87,6 @@ The following fields are recalculated by OpenFOAM when `fvMesh::movePoints` is c - `Sf` (Face Area Vectors) - `magSf` (Face Area Magnitudes) - `delta` (Face-Center to Cell-Center distances) -- `V`, `V0`, `V00` (Cell Volumes): These are already properly checkpointed by OpenFOAM itself, except when subcycling (needs further handling when subcycling - [related discussion](https://github.com/precice/openfoam-adapter/pull/369#issuecomment-3748773060)). #### Dictionaries From b6efc0eeaebd036a5d08f5a9fc5b48339cdaa331 Mon Sep 17 00:00:00 2001 From: Daniels Vidulejs <36682359+vidulejs@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:59:26 +0300 Subject: [PATCH 52/70] Implement mixed boundary condition (Robin) in Generic module (#400) Co-authored-by: Daniels Vidulejs --- changelog-entries/400.md | 1 + docs/config.md | 22 +++++++++++++ module-generic/ReadWrite.C | 65 +++++++++++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 changelog-entries/400.md diff --git a/changelog-entries/400.md b/changelog-entries/400.md new file mode 100644 index 00000000..aa718579 --- /dev/null +++ b/changelog-entries/400.md @@ -0,0 +1 @@ +- Add support for Robin or mixed boundary condition in the Generic module. [#400](https://github.com/precice/openfoam-adapter/pull/400). \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index 4d99bb45..55fe8242 100644 --- a/docs/config.md +++ b/docs/config.md @@ -302,6 +302,28 @@ Explicitly setting `locations volumeCenters;` in preciceDict is required for vol When reading on the OpenFOAM side, the Generic module automatically detects the boundary condition type of the coupled patch to apply the received data. The boundary condition must be respected, therefore the data received is applied either as a `fixedValue` or `fixedGradient` boundary condition, determined by the type set in the `0/` files. +Additionally, the Robin or mixed boundary condition is now supported for scalar surface coupling. The OpenFOAM boundary type `mixed` or `mixedCoded` must be set for the coupled patch in the `0/` files like so: + +```cpp +// File 0/T +boundaryField +{ + interface + { + type mixed; + refValue uniform 1; + refGradient uniform 1; + valueFraction uniform 0.5; + } +} +``` + +Any of the three fields `refValue`, `refGradient`, and `valueFraction` can be read from another participant (the initial values will be overwritten by the adapter). To do so, specify the `operation` for each coupling data in `preciceDict` as `ref-value`, `ref-gradient`, or `value-fraction`. Be careful when using the `value-fraction` operation, as it is not a physical quantity and must be within the range of [0, 1]. + +These three operations are only supported for the read side. To write coupling data from a mixed boundary patch, just use the standard `value` or `surface-normal-gradient` operation. The `valueFraction` field of the mixed boundary condition is not exposed by the adapter for writing. + +Please note that if you intend to use the `mixed` boundary condition for a heat transfer problem, the `valueFraction` is not the same as the heat transfer coefficient and it's not possible to explicitly set the heat transfer coefficient as a parameter. + Limitations: - The operation `gradient` is currently only supported for writing scalar fields (resulting to a vector field). diff --git a/module-generic/ReadWrite.C b/module-generic/ReadWrite.C index f1c2b0da..08db5756 100644 --- a/module-generic/ReadWrite.C +++ b/module-generic/ReadWrite.C @@ -4,6 +4,7 @@ #include "fixedValueFvPatchFields.H" #include "fixedGradientFvPatchFields.H" +#include "mixedFvPatchFields.H" using namespace Foam; @@ -47,6 +48,14 @@ void preciceAdapter::Generic::ScalarFieldCoupler::initialize() adapterInfo("Generic module: The surface-normal-gradient operation is only supported for faceCenters location type.", "error"); } } + + if (fieldConfig_.operation == "ref-value" || fieldConfig_.operation == "ref-gradient" || fieldConfig_.operation == "value-fraction") + { + if (this->locationType_ != LocationType::faceCenters) + { + adapterInfo("Generic module: Robin boundary conditions (ref-value, ref-gradient, and value-fraction operations) are supported only for surface locations (faceCenters).", "error"); + } + } } @@ -151,6 +160,13 @@ std::size_t preciceAdapter::Generic::ScalarFieldCoupler::write(double* buffer, b return bufferIndex; } + if (fieldConfig_.operation == "ref-value" || fieldConfig_.operation == "ref-gradient" || fieldConfig_.operation == "value-fraction") + { + adapterInfo( + "Generic module: The mixed boundary condition operations (ref-value, ref-gradient, and value-fraction) are only supported for the read side. If you wish to write these fields, just use the value or surface-normal-gradient operations instead.", + "error"); + } + if (this->locationType_ == LocationType::volumeCenters) { if (cellSetNames_.empty()) @@ -254,6 +270,13 @@ void preciceAdapter::Generic::ScalarFieldCoupler::read(double* buffer, const uns if (isA(bc)) { + if (fieldConfig_.operation != "value") + { + adapterInfo( + "Generic module: Unsupported operation " + fieldConfig_.operation + " for boundary condition type " + bc.type() + ". Supported operation is value.", + "error"); + } + auto& boundaryPatch = refCast(bc); forAll(boundaryPatch, i) { @@ -262,20 +285,54 @@ void preciceAdapter::Generic::ScalarFieldCoupler::read(double* buffer, const uns } else if (isA(bc)) { + if (fieldConfig_.operation != "value") + { + adapterInfo( + "Generic module: Unsupported operation " + fieldConfig_.operation + " for boundary condition type " + bc.type() + ". Supported operation is value.", + "error"); + } + auto& boundaryPatch = refCast(bc); forAll(boundaryPatch, i) { boundaryPatch.gradient()[i] = buffer[bufferIndex++]; } } + else if (isA(bc)) + { + auto& boundaryPatch = refCast(bc); + if (fieldConfig_.operation == "ref-value") + { + forAll(boundaryPatch, i) + { + boundaryPatch.refValue()[i] = buffer[bufferIndex++]; + } + } + else if (fieldConfig_.operation == "ref-gradient") + { + forAll(boundaryPatch, i) + { + boundaryPatch.refGrad()[i] = buffer[bufferIndex++]; + } + } + else if (fieldConfig_.operation == "value-fraction") + { + forAll(boundaryPatch, i) + { + boundaryPatch.valueFraction()[i] = buffer[bufferIndex++]; + } + } + else + { + adapterInfo( + "Generic module: Unsupported operation " + fieldConfig_.operation + " for boundary condition type " + bc.type() + ". Supported operations are ref-value, ref-gradient, and value-fraction.", + "error"); + } + } else { adapterInfo("Generic module: Unsupported boundary condition type " + bc.type(), "error"); } - - // // evaluate the boundary condition, i.e., do some calculation to obtain the actual value provided refValue - // boundaryPatch.updateCoeffs(); - // boundaryPatch.evaluate(); } } From f6d7928c52df4bf109a4d5abd245531d6eeb136c Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 16 Jun 2026 13:06:07 +0200 Subject: [PATCH 53/70] Simplify system tests workflow (#406) --- .github/workflows/system-tests.yaml | 36 ++--------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/.github/workflows/system-tests.yaml b/.github/workflows/system-tests.yaml index e0d93096..bb9529ad 100644 --- a/.github/workflows/system-tests.yaml +++ b/.github/workflows/system-tests.yaml @@ -5,41 +5,9 @@ on: types: [labeled] jobs: - gather-refs: - if: ${{ github.event.label.name == 'trigger-system-tests' }} - runs-on: ubuntu-latest - outputs: - ref-tutorials: ${{ steps.ref-tutorials.outputs.shorthash }} - steps: - - id: ref-tutorials - uses: nmbgeek/github-action-get-latest-commit@main - with: - owner: precice - repo: tutorials - branch: develop - - id: report-refs - name: Report Git refs - run: | - printf 'Tutorials: ${{ steps.ref-tutorials.outputs.shorthash }}\n ${{ steps.ref-tutorials.outputs.description }}\n----------\n' - - id: summary - name: Prepare Markdown summary - run: | - echo "### OpenFOAM adapter" >> $GITHUB_STEP_SUMMARY - echo "Reference (pull request): \`${{ github.event.pull_request.head.sha }}\`" >> $GITHUB_STEP_SUMMARY - echo "Pull request number: [${{ github.event.number }}](https://github.com/precice/openfoam-adapter/pull/${{ github.event.number }})" >> $GITHUB_STEP_SUMMARY - echo "### Tutorials" >> $GITHUB_STEP_SUMMARY - echo "Reference (develop): [\`${{ steps.ref-tutorials.outputs.shorthash }}\`](https://github.com/precice/tutorials/commit/${{ steps.ref-tutorials.outputs.shorthash }})" >> $GITHUB_STEP_SUMMARY - echo "Description:" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "${{ steps.ref-tutorials.outputs.description }}" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - run-system-tests: if: ${{ github.event.label.name == 'trigger-system-tests' }} - needs: gather-refs - uses: precice/tutorials/.github/workflows/run_testsuite_workflow.yml@develop + uses: precice/tutorials/.github/workflows/system-tests.yml@develop with: suites: openfoam-adapter - build_args: TUTORIALS_REF:${{ needs.gather-refs.outputs.ref-tutorials }},PRECICE_REF:v3.4.1,OPENFOAM_EXECUTABLE:openfoam2512,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} - system_tests_branch: develop - log_level: "DEBUG" + build_args: OPENFOAM_EXECUTABLE:openfoam2512,OPENFOAM_ADAPTER_PR:${{ github.event.number }},OPENFOAM_ADAPTER_REF:${{ github.event.pull_request.head.sha }} From b09e4994faaae8b82236984421ad7390ab7db010 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:50:12 +0200 Subject: [PATCH 54/70] Bump actions/checkout from 6 to 7 (#407) Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-custom.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/pre-commit.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index d6226fc8..04d38056 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -81,7 +81,7 @@ jobs: echo "Run tutorial partitioned-pipe: ${{ github.event.inputs.runTutorialPartitionedPipe }}" echo "Tutorials branch: ${{ github.event.inputs.branchTutorials }}" - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: ${{ github.event.inputs.refAdapter }} - name: Install preCICE (no cache) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 347ba0b5..969d2cfd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: container: precice/precice:nightly steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up OpenFOAM uses: gerlero/setup-openfoam@v1 with: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 1aba35f1..876e28bc 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout preCICE - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Setup python uses: actions/setup-python@v6 with: From a1b234f099b61948cec2cd3d98d5fe24ab687790 Mon Sep 17 00:00:00 2001 From: MuhammadAashirAslam Date: Thu, 25 Jun 2026 21:58:00 +0500 Subject: [PATCH 55/70] Prepare documentation for rendering in Hugo (#409) --- docs/README.md | 3 +++ docs/config.md | 1 + docs/extend.md | 1 + docs/get.md | 1 + docs/openfoam-support.md | 1 + 5 files changed, 7 insertions(+) diff --git a/docs/README.md b/docs/README.md index 3e62d9ad..f5e6d856 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,10 @@ --- title: The OpenFOAM adapter permalink: adapter-openfoam-overview.html +url: /adapter-openfoam-overview.html redirect_from: adapter-openfoam.html +aliases: + - /adapter-openfoam.html keywords: adapter, openfoam, cite, versions summary: An OpenFOAM function object for CHT, FSI, and fluid-fluid coupled simulations using preCICE. --- diff --git a/docs/config.md b/docs/config.md index 55fe8242..d34aabb8 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,6 +1,7 @@ --- title: Configure the OpenFOAM adapter permalink: adapter-openfoam-config.html +url: /adapter-openfoam-config.html keywords: adapter, openfoam, configuration, preciceDict, controlDict summary: "Write a system/preciceDict, set compatible boundary conditions, and activate the adapter in your system/controlDict." --- diff --git a/docs/extend.md b/docs/extend.md index 1ff04b1c..de1a0538 100644 --- a/docs/extend.md +++ b/docs/extend.md @@ -1,6 +1,7 @@ --- title: Extend the OpenFOAM adapter permalink: adapter-openfoam-extend.html +url: /adapter-openfoam-extend.html keywords: adapter, openfoam, development, modules summary: "An overview of the OpenFOAM adapter's architecture and which parts to modify if you want to add functionality." --- diff --git a/docs/get.md b/docs/get.md index 33537bff..5a4cbc04 100644 --- a/docs/get.md +++ b/docs/get.md @@ -1,6 +1,7 @@ --- title: Get the OpenFOAM adapter permalink: adapter-openfoam-get.html +url: /adapter-openfoam-get.html keywords: adapter, openfoam, building summary: "Get the code from GitHub and run ./Allwmake. If this fails, look into wmake.log and ldd.log." --- diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index 1cbf8c7b..4278db90 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -1,6 +1,7 @@ --- title: OpenFOAM support permalink: adapter-openfoam-support.html +url: /adapter-openfoam-support.html keywords: adapter, openfoam, support, versions summary: Recent OpenFOAM.com versions work out-of-the-box. Recent OpenFOAM.org versions are also supported, but you will need a version-specific branch. --- From a0ceac576e24a8a22bdf7902331e0593b314df2e Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Thu, 25 Jun 2026 19:40:05 +0200 Subject: [PATCH 56/70] Update coupledPressure / coupledVelocity BC for OpenFOAM v2606 (#410) --- .../coupledPressure/coupledPressureFvPatchField.C | 10 ---------- .../coupledPressure/coupledPressureFvPatchField.H | 9 +-------- .../coupledVelocity/coupledVelocityFvPatchField.C | 10 ---------- .../coupledVelocity/coupledVelocityFvPatchField.H | 9 +-------- changelog-entries/410.md | 1 + docs/openfoam-support.md | 1 + 6 files changed, 4 insertions(+), 36 deletions(-) create mode 100644 changelog-entries/410.md diff --git a/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C b/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C index 9b3a7665..8d57b60b 100644 --- a/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C +++ b/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.C @@ -55,16 +55,6 @@ Foam::coupledPressureFvPatchField::coupledPressureFvPatchField( } -Foam::coupledPressureFvPatchField::coupledPressureFvPatchField( - const coupledPressureFvPatchField& ptf) -: fixedFluxExtrapolatedPressureFvPatchScalarField(ptf), - refValue_(ptf.refValue_), - refGrad_(ptf.refGrad_), - valueFraction_(ptf.valueFraction_) -{ -} - - Foam::coupledPressureFvPatchField::coupledPressureFvPatchField( const coupledPressureFvPatchField& ptf, const DimensionedField& iF) diff --git a/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.H b/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.H index da9f875a..a931307a 100644 --- a/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.H +++ b/FF/BoundaryConditions/coupledPressure/coupledPressureFvPatchField.H @@ -93,14 +93,7 @@ public: //- Construct as copy coupledPressureFvPatchField( - const coupledPressureFvPatchField&); - - //- Construct and return a clone - virtual tmp clone() const - { - return tmp( - new coupledPressureFvPatchField(*this)); - } + const coupledPressureFvPatchField&) = delete; //- Construct as copy setting internal field reference coupledPressureFvPatchField( diff --git a/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.C b/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.C index b340d964..ce4ebe78 100644 --- a/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.C +++ b/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.C @@ -56,16 +56,6 @@ Foam::coupledVelocityFvPatchField::coupledVelocityFvPatchField( } -Foam::coupledVelocityFvPatchField::coupledVelocityFvPatchField( - const coupledVelocityFvPatchField& ptf) -: fvPatchField(ptf), - refValue_(ptf.refValue_), - refGrad_(ptf.refGrad_), - valueFraction_(ptf.valueFraction_) -{ -} - - Foam::coupledVelocityFvPatchField::coupledVelocityFvPatchField( const coupledVelocityFvPatchField& ptf, const DimensionedField& iF) diff --git a/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.H b/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.H index dadc1f2f..5ca7da0d 100644 --- a/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.H +++ b/FF/BoundaryConditions/coupledVelocity/coupledVelocityFvPatchField.H @@ -90,14 +90,7 @@ public: //- Construct as copy coupledVelocityFvPatchField( - const coupledVelocityFvPatchField&); - - //- Construct and return a clone - virtual tmp clone() const - { - return tmp( - new coupledVelocityFvPatchField(*this)); - } + const coupledVelocityFvPatchField&) = delete; //- Construct as copy setting internal field reference coupledVelocityFvPatchField( diff --git a/changelog-entries/410.md b/changelog-entries/410.md new file mode 100644 index 00000000..009f5fa8 --- /dev/null +++ b/changelog-entries/410.md @@ -0,0 +1 @@ +- Fixed a compatibility issue with OpenFOAM v2606 affecting the coupledVelocity and coupledPressure boundary conditions [#410](https://github.com/precice/openfoam-adapter/pull/410) \ No newline at end of file diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index 4278db90..203626f0 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -37,6 +37,7 @@ We provide version-specific [release archives](https://github.com/precice/openfo - OpenCFD / ESI (openfoam.com) - main focus: - [OpenFOAM v1812-v2512](https://github.com/precice/openfoam-adapter) or newer - [OpenFOAM v1612-v1806](https://github.com/precice/openfoam-adapter/tree/OpenFOAMv1806) (not tested) + - For OpenFOAM v2606, see a [patch (already merged)](https://github.com/precice/openfoam-adapter/pull/410) - OpenFOAM Foundation (openfoam.org) - secondary, consider experimental: - [OpenFOAM 10](https://github.com/precice/openfoam-adapter/tree/OpenFOAM10) - [OpenFOAM 9](https://github.com/precice/openfoam-adapter/tree/OpenFOAM9) From c39634e1d0ba62dfb4839acf3bab5133e58d8e1b Mon Sep 17 00:00:00 2001 From: MuhammadAashirAslam Date: Tue, 30 Jun 2026 14:02:24 +0500 Subject: [PATCH 57/70] Use Hugo-native URLs with legacy aliases (#412) --- docs/README.md | 4 ++-- docs/config.md | 3 ++- docs/extend.md | 3 ++- docs/get.md | 3 ++- docs/openfoam-support.md | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index f5e6d856..4e494c7a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,10 @@ --- title: The OpenFOAM adapter permalink: adapter-openfoam-overview.html -url: /adapter-openfoam-overview.html -redirect_from: adapter-openfoam.html aliases: + - /adapter-openfoam-overview.html - /adapter-openfoam.html +redirect_from: adapter-openfoam.html keywords: adapter, openfoam, cite, versions summary: An OpenFOAM function object for CHT, FSI, and fluid-fluid coupled simulations using preCICE. --- diff --git a/docs/config.md b/docs/config.md index d34aabb8..ec254cbc 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,7 +1,8 @@ --- title: Configure the OpenFOAM adapter permalink: adapter-openfoam-config.html -url: /adapter-openfoam-config.html +aliases: + - /adapter-openfoam-config.html keywords: adapter, openfoam, configuration, preciceDict, controlDict summary: "Write a system/preciceDict, set compatible boundary conditions, and activate the adapter in your system/controlDict." --- diff --git a/docs/extend.md b/docs/extend.md index de1a0538..b083b3e4 100644 --- a/docs/extend.md +++ b/docs/extend.md @@ -1,7 +1,8 @@ --- title: Extend the OpenFOAM adapter permalink: adapter-openfoam-extend.html -url: /adapter-openfoam-extend.html +aliases: + - /adapter-openfoam-extend.html keywords: adapter, openfoam, development, modules summary: "An overview of the OpenFOAM adapter's architecture and which parts to modify if you want to add functionality." --- diff --git a/docs/get.md b/docs/get.md index 5a4cbc04..eacec717 100644 --- a/docs/get.md +++ b/docs/get.md @@ -1,7 +1,8 @@ --- title: Get the OpenFOAM adapter permalink: adapter-openfoam-get.html -url: /adapter-openfoam-get.html +aliases: + - /adapter-openfoam-get.html keywords: adapter, openfoam, building summary: "Get the code from GitHub and run ./Allwmake. If this fails, look into wmake.log and ldd.log." --- diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index 203626f0..06d7a11b 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -1,7 +1,8 @@ --- title: OpenFOAM support permalink: adapter-openfoam-support.html -url: /adapter-openfoam-support.html +aliases: + - /adapter-openfoam-support.html keywords: adapter, openfoam, support, versions summary: Recent OpenFOAM.com versions work out-of-the-box. Recent OpenFOAM.org versions are also supported, but you will need a version-specific branch. --- From dcaa9b08378020fe1842edf1bfb8066cfb7eaeb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:36:56 +0200 Subject: [PATCH 58/70] Bump actions/setup-python from 6 to 7 (#413) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pre-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 876e28bc..e6e0ee82 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout preCICE uses: actions/checkout@v7 - name: Setup python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.x' check-latest: true From 5a9eec4548cefe9467cd4e770e177c17f9c66530 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 5 Aug 2026 17:16:27 +0200 Subject: [PATCH 59/70] Fix end-of-line in Utilities.C Resolves the warning `wmkdepend: parse error while scanning 'Utilities.C' ... perhaps missing a final newline` Reported in https://github.com/precice/openfoam-adapter/pull/405 --- Utilities.C | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Utilities.C b/Utilities.C index 603b3f99..7d57360d 100644 --- a/Utilities.C +++ b/Utilities.C @@ -71,4 +71,4 @@ bool matchingStrings(std::string s, std::string match) std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); std::transform(match.begin(), match.end(), match.begin(), [](unsigned char c) { return std::tolower(c); }); return s.find(match) == 0; -} \ No newline at end of file +} From c7e95a8a2cc868e5e5cc8fbab05e874dd56d6a0c Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 19 Aug 2026 10:17:20 +0200 Subject: [PATCH 60/70] Bump default OpenFOAM version to v2606 (#411) --- .github/workflows/build-custom.input | 2 +- .github/workflows/build-custom.yml | 1 + .github/workflows/build.yml | 4 ++-- README.md | 2 +- changelog-entries/410.md | 2 +- docs/openfoam-support.md | 12 +++++++----- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-custom.input b/.github/workflows/build-custom.input index d53d8057..3b4fcc16 100644 --- a/.github/workflows/build-custom.input +++ b/.github/workflows/build-custom.input @@ -1,6 +1,6 @@ virtualEnvironment="ubuntu-22.04" refAdapter="develop" -versionOpenFOAM="2512" +versionOpenFOAM="2606" versionpreCICE="v3.4.1" runTutorialHeatedPlate="false" runTutorialQuickstart="false" diff --git a/.github/workflows/build-custom.yml b/.github/workflows/build-custom.yml index 04d38056..e33f2819 100644 --- a/.github/workflows/build-custom.yml +++ b/.github/workflows/build-custom.yml @@ -21,6 +21,7 @@ on: required: true type: choice options: + - 2606 - 2512 - 2506 - 2412 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 969d2cfd..d5dd4719 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build with OpenFOAM v2512 +name: Build with OpenFOAM v2606 on: push: branches-ignore: @@ -28,6 +28,6 @@ jobs: - name: Set up OpenFOAM uses: gerlero/setup-openfoam@v1 with: - openfoam-version: 2512 + openfoam-version: 2606 - name: Build OpenFOAM-preCICE adapter run: ./Allwmake diff --git a/README.md b/README.md index 33dc03cb..3ae810ed 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Cite](https://img.shields.io/badge/cite-literature_guide-d45815)](https://precice.org/fundamentals-literature-guide.html) [![Changelog](https://img.shields.io/badge/Keep%20a%20Changelog--555.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmMTVkMzAiIHZpZXdCb3g9IjAgMCAxODcgMTg1Ij48cGF0aCBkPSJNNjIgN2MtMTUgMy0yOCAxMC0zNyAyMmExMjIgMTIyIDAgMDAtMTggOTEgNzQgNzQgMCAwMDE2IDM4YzYgOSAxNCAxNSAyNCAxOGE4OSA4OSAwIDAwMjQgNCA0NSA0NSAwIDAwNiAwbDMtMSAxMy0xYTE1OCAxNTggMCAwMDU1LTE3IDYzIDYzIDAgMDAzNS01MiAzNCAzNCAwIDAwLTEtNWMtMy0xOC05LTMzLTE5LTQ3LTEyLTE3LTI0LTI4LTM4LTM3QTg1IDg1IDAgMDA2MiA3em0zMCA4YzIwIDQgMzggMTQgNTMgMzEgMTcgMTggMjYgMzcgMjkgNTh2MTJjLTMgMTctMTMgMzAtMjggMzhhMTU1IDE1NSAwIDAxLTUzIDE2bC0xMyAyaC0xYTUxIDUxIDAgMDEtMTItMWwtMTctMmMtMTMtNC0yMy0xMi0yOS0yNy01LTEyLTgtMjQtOC0zOWExMzMgMTMzIDAgMDE4LTUwYzUtMTMgMTEtMjYgMjYtMzMgMTQtNyAyOS05IDQ1LTV6TTQwIDQ1YTk0IDk0IDAgMDAtMTcgNTQgNzUgNzUgMCAwMDYgMzJjOCAxOSAyMiAzMSA0MiAzMiAyMSAyIDQxLTIgNjAtMTRhNjAgNjAgMCAwMDIxLTE5IDUzIDUzIDAgMDA5LTI5YzAtMTYtOC0zMy0yMy01MWE0NyA0NyAwIDAwLTUtNWMtMjMtMjAtNDUtMjYtNjctMTgtMTIgNC0yMCA5LTI2IDE4em0xMDggNzZhNTAgNTAgMCAwMS0yMSAyMmMtMTcgOS0zMiAxMy00OCAxMy0xMSAwLTIxLTMtMzAtOS01LTMtOS05LTEzLTE2YTgxIDgxIDAgMDEtNi0zMiA5NCA5NCAwIDAxOC0zNSA5MCA5MCAwIDAxNi0xMmwxLTJjNS05IDEzLTEzIDIzLTE2IDE2LTUgMzItMyA1MCA5IDEzIDggMjMgMjAgMzAgMzYgNyAxNSA3IDI5IDAgNDJ6bS00My03M2MtMTctOC0zMy02LTQ2IDUtMTAgOC0xNiAyMC0xOSAzN2E1NCA1NCAwIDAwNSAzNGM3IDE1IDIwIDIzIDM3IDIyIDIyLTEgMzgtOSA0OC0yNGE0MSA0MSAwIDAwOC0yNCA0MyA0MyAwIDAwLTEtMTJjLTYtMTgtMTYtMzEtMzItMzh6bS0yMyA5MWgtMWMtNyAwLTE0LTItMjEtN2EyNyAyNyAwIDAxLTEwLTEzIDU3IDU3IDAgMDEtNC0yMCA2MyA2MyAwIDAxNi0yNWM1LTEyIDEyLTE5IDI0LTIxIDktMyAxOC0yIDI3IDIgMTQgNiAyMyAxOCAyNyAzM3MtMiAzMS0xNiA0MGMtMTEgOC0yMSAxMS0zMiAxMXptMS0zNHYxNGgtOFY2OGg4djI4bDEwLTEwaDExbC0xNCAxNSAxNyAxOEg5NnoiLz48L3N2Zz4K)](https://github.com/precice/openfoam-adapter/blob/develop/CHANGELOG.md) [![License](https://img.shields.io/github/license/precice/openfoam-adapter.svg)](https://github.com/precice/openfoam-adapter/blob/master/LICENSE) -[![Build with OpenFOAM v2512](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml) +[![Build with OpenFOAM v2606](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/precice/openfoam-adapter/actions/workflows/build.yml) [![OpenFOAM Package Index](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fexasim-project%2Fopi%2Frefs%2Fheads%2Fmain%2Fpkg%2Fprecice-adapter%2Fmetadata.json&query=%24.%5Bname%2Ctype%2Cversion%5D&label=opi)](https://github.com/exasim-project/opi) ## Start here diff --git a/changelog-entries/410.md b/changelog-entries/410.md index 009f5fa8..90450b80 100644 --- a/changelog-entries/410.md +++ b/changelog-entries/410.md @@ -1 +1 @@ -- Fixed a compatibility issue with OpenFOAM v2606 affecting the coupledVelocity and coupledPressure boundary conditions [#410](https://github.com/precice/openfoam-adapter/pull/410) \ No newline at end of file +- Fixed a compatibility issue with OpenFOAM v2606 affecting the coupledVelocity and coupledPressure boundary conditions [#410](https://github.com/precice/openfoam-adapter/pull/410), [#411](https://github.com/precice/openfoam-adapter/pull/411) \ No newline at end of file diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index 06d7a11b..f74365b6 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -9,14 +9,14 @@ summary: Recent OpenFOAM.com versions work out-of-the-box. Recent OpenFOAM.org v ## How to get OpenFOAM -The easiest way to start is to get binary packages for your Linux distribution. For example, to [get OpenFOAM v2512 on Ubuntu](https://gitlab.com/openfoam/core/openfoam/-/wikis/precompiled/debian): +The easiest way to start is to get binary packages for your Linux distribution. For example, to [get OpenFOAM v2606 on Ubuntu](https://gitlab.com/openfoam/core/openfoam/-/wikis/precompiled/debian): ```bash # Add the signing key, add the repository, update: wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash -# Install OpenFOAM v2512: -sudo apt-get install openfoam2512-dev +# Install OpenFOAM v2606: +sudo apt-get install openfoam2606-dev ``` As these steps change your `.profile`, you need to log out and in again to make OpenFOAM fully discoverable. @@ -36,9 +36,10 @@ Currently, and since v1.3.0 of the adapter (first one for preCICE v3), only the We provide version-specific [release archives](https://github.com/precice/openfoam-adapter/releases/latest) and respective Git branches for: - OpenCFD / ESI (openfoam.com) - main focus: - - [OpenFOAM v1812-v2512](https://github.com/precice/openfoam-adapter) or newer + - [OpenFOAM v1812-v2606](https://github.com/precice/openfoam-adapter) or newer + - See [compatibility changes and from v2512 to v2606](https://github.com/precice/openfoam-adapter/pull/410) + - See [expected results differences in FSI cases (mesh motion) with OpenFOAM v2606](https://github.com/precice/openfoam-adapter/pull/411) - [OpenFOAM v1612-v1806](https://github.com/precice/openfoam-adapter/tree/OpenFOAMv1806) (not tested) - - For OpenFOAM v2606, see a [patch (already merged)](https://github.com/precice/openfoam-adapter/pull/410) - OpenFOAM Foundation (openfoam.org) - secondary, consider experimental: - [OpenFOAM 10](https://github.com/precice/openfoam-adapter/tree/OpenFOAM10) - [OpenFOAM 9](https://github.com/precice/openfoam-adapter/tree/OpenFOAM9) @@ -67,6 +68,7 @@ The following table gives an overview, including versions that we do not support | version | 24.04 | 22.04 | 20.04 | 18.04 | | --- | --- | --- | --- | --- | +| (com) v2606 | x | x | x | x | | (com) v2512 | x | x | x | x | | (com) v2506 | x | x | x | x | | (com) v2412 | x | x | x | x | From 9a4952a202eab1a05f6723b139226a6e58695e84 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 1 Sep 2026 10:25:37 +0200 Subject: [PATCH 61/70] docs/README.md: Move disclaimer up to fix footnotes (#419) --- docs/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 4e494c7a..f8d14a0f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -73,6 +73,10 @@ The volume coupling functionality was contributed by Tina Vladimirova, in the co The adapter is [easily extensible](https://precice.org/adapter-openfoam-extend.html). +{% disclaimer %} +This offering is not approved or endorsed by OpenCFD Limited, producer and distributor of the OpenFOAM software via www.openfoam.com, and owner of the OPENFOAM® and OpenCFD® trade marks. +{% enddisclaimer %} + ### Related literature [^1]: Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25.
    @@ -95,7 +99,3 @@ URL: https://mediatum.ub.tum.de/node?id=1696254&change_language=en [^7]: Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023.
    URL: https://mediatum.ub.tum.de/1734883 - -{% disclaimer %} -This offering is not approved or endorsed by OpenCFD Limited, producer and distributor of the OpenFOAM software via www.openfoam.com, and owner of the OPENFOAM® and OpenCFD® trade marks. -{% enddisclaimer %} From f9683ce7f8fa3f8915f4f7fdc53b92a74c7839e0 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 1 Sep 2026 10:55:09 +0200 Subject: [PATCH 62/70] Replace literature footnotes with a list Not rendered correctly on the website and other environments. --- docs/README.md | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/docs/README.md b/docs/README.md index f8d14a0f..31259d55 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,20 +56,20 @@ our [training session from the 15th OpenFOAM Workshop](https://mediatum.ub.tum.d ## Cite -Please cite this adapter using our [reference paper in the OpenFOAM Journal](https://doi.org/10.51560/ofj.v3.88)[^1]. See the [preCICE literature guide](https://precice.org/fundamentals-literature-guide.html) for more details. +Please cite this adapter using our [reference paper in the OpenFOAM Journal](https://doi.org/10.51560/ofj.v3.88) [1]. See the [preCICE literature guide](https://precice.org/fundamentals-literature-guide.html) for more details. ## History This project is actively maintained on [precice/openfoam-adapter](https://github.com/precice/openfoam-adapter). Current maintainers: [@MakisH](https://github.com/MakisH/) and [@DavidSCN](https://github.com/DavidSCN). -This adapter was developed as part of [Gerasimos Chourdakis' master's thesis](https://mediatum.ub.tum.de/1462269)[^2]. -It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia Cheung ([master's thesis](https://www.simscale.com/)[^3], in cooperation with [SimScale](https://www.simscale.com/)). +This adapter was developed as part of [Gerasimos Chourdakis' master's thesis](https://mediatum.ub.tum.de/1462269) [2]. +It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia Cheung ([master's thesis](https://www.simscale.com/) [3], in cooperation with [SimScale](https://www.simscale.com/)). -The fluid-structure interaction module was developed in close collaboration between Gerasimos Chourdakis and Derek Risseeuw (TU Delft), in the context of the [master's thesis of the latter](http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123)[^4]. We would also like to thank David Schneider (Univ. Siegen / TUM) and Maximilian Müller (TU Braunschweig) for sharing the code and experience of their similar previous work. +The fluid-structure interaction module was developed in close collaboration between Gerasimos Chourdakis and Derek Risseeuw (TU Delft), in the context of the [master's thesis of the latter](http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123) [4]. We would also like to thank David Schneider (Univ. Siegen / TUM) and Maximilian Müller (TU Braunschweig) for sharing the code and experience of their similar previous work. -The fluid-fluid coupling module was added by Gerasimos Chourdakis, in the context of his dissertation. [#67](https://github.com/precice/openfoam-adapter/pull/67). Further contributions in this direction by Markus Mühlhäußer ([master's thesis](https://mediatum.ub.tum.de/node?id=1696254&change_language=en)[^5], [related publication](https://mediatum.ub.tum.de/node?id=1732401&change_language=en)[^6]). +The fluid-fluid coupling module was added by Gerasimos Chourdakis, in the context of his dissertation. [#67](https://github.com/precice/openfoam-adapter/pull/67). Further contributions in this direction by Markus Mühlhäußer ([master's thesis](https://mediatum.ub.tum.de/node?id=1696254&change_language=en) [5], [related publication](https://mediatum.ub.tum.de/node?id=1732401&change_language=en) [6]). -The volume coupling functionality was contributed by Tina Vladimirova, in the context of her [interdisciplinary project](https://mediatum.ub.tum.de/1734883)[^7], based on previous work by various community contributors. +The volume coupling functionality was contributed by Tina Vladimirova, in the context of her [interdisciplinary project](https://mediatum.ub.tum.de/1734883) [7], based on previous work by various community contributors. The adapter is [easily extensible](https://precice.org/adapter-openfoam-extend.html). @@ -79,23 +79,17 @@ This offering is not approved or endorsed by OpenCFD Limited, producer and distr ### Related literature -[^1]: Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25.
    +1. Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25.
    [DOI: 10.51560/ofj.v3.88](https://doi.org/10.51560/ofj.v3.88) - -[^2]: Gerasimos Chourdakis. A general OpenFOAM adapter for the coupling library preCICE. Master's thesis, Department of Informatics, Technical University of Munich, 2017.
    +2. Gerasimos Chourdakis. A general OpenFOAM adapter for the coupling library preCICE. Master's thesis, Department of Informatics, Technical University of Munich, 2017.
    URL: https://mediatum.ub.tum.de/1462269 - -[^3]: Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016.
    +3. Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016.
    URL: https://mediatum.ub.tum.de/1461907 - -[^4]: Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019.
    +4. Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019.
    URL: http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123 - -[^5]: Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022.
    +5. Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022.
    URL: https://mediatum.ub.tum.de/node?id=1696254&change_language=en - -[^6]: Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023.
    +6. Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023.
    [DOI: 10.23967/c.coupled.2023.014](https://doi.org/10.23967/c.coupled.2023.014) - -[^7]: Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023.
    +7. Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023.
    URL: https://mediatum.ub.tum.de/1734883 From c12c5f704c5b1ab08efdbc054c58c0ff9f73213b Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 1 Sep 2026 11:09:46 +0200 Subject: [PATCH 63/70] Fix literature URLs --- docs/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/README.md b/docs/README.md index 31259d55..ffb3eb5b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -63,7 +63,7 @@ Please cite this adapter using our [reference paper in the OpenFOAM Journal](htt This project is actively maintained on [precice/openfoam-adapter](https://github.com/precice/openfoam-adapter). Current maintainers: [@MakisH](https://github.com/MakisH/) and [@DavidSCN](https://github.com/DavidSCN). This adapter was developed as part of [Gerasimos Chourdakis' master's thesis](https://mediatum.ub.tum.de/1462269) [2]. -It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia Cheung ([master's thesis](https://www.simscale.com/) [3], in cooperation with [SimScale](https://www.simscale.com/)). +It is based on [previous work](https://github.com/ludcila/CHT-preCICE) by Lucia Cheung ([master's thesis](https://mediatum.ub.tum.de/1461907) [3], in cooperation with [SimScale](https://www.simscale.com/)). The fluid-structure interaction module was developed in close collaboration between Gerasimos Chourdakis and Derek Risseeuw (TU Delft), in the context of the [master's thesis of the latter](http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123) [4]. We would also like to thank David Schneider (Univ. Siegen / TUM) and Maximilian Müller (TU Braunschweig) for sharing the code and experience of their similar previous work. @@ -79,17 +79,17 @@ This offering is not approved or endorsed by OpenCFD Limited, producer and distr ### Related literature -1. Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25.
    +1. Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25. [DOI: 10.51560/ofj.v3.88](https://doi.org/10.51560/ofj.v3.88) -2. Gerasimos Chourdakis. A general OpenFOAM adapter for the coupling library preCICE. Master's thesis, Department of Informatics, Technical University of Munich, 2017.
    -URL: https://mediatum.ub.tum.de/1462269 -3. Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016.
    -URL: https://mediatum.ub.tum.de/1461907 -4. Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019.
    -URL: http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123 -5. Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022.
    -URL: https://mediatum.ub.tum.de/node?id=1696254&change_language=en -6. Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023.
    +2. Gerasimos Chourdakis. A general OpenFOAM adapter for the coupling library preCICE. Master's thesis, Department of Informatics, Technical University of Munich, 2017. +[URL: MediaTUM](https://mediatum.ub.tum.de/1462269) +3. Lucia Cheung Yau. Conjugate heat transfer with the multiphysics coupling library preCICE. Master’s thesis, Department of Informatics, Technical University of Munich, 2016. +[URL: MediaTUM](https://mediatum.ub.tum.de/1461907) +4. Derek Risseeuw. Fluid Structure Interaction Modelling of Flapping Wings. Master's thesis, Faculty of Aerospace Engineering, Delft University of Technology, 2019. +[URL: TUDelft](http://resolver.tudelft.nl/uuid:70beddde-e870-4c62-9a2f-8758b4e49123) +5. Markus Mühlhäußer. Partitioned flow simulations with preCICE and OpenFOAM. Master's thesis, School of Computation, Information and Technology, Technical University of Munich, 2022. +[URL: MediaTUM](https://mediatum.ub.tum.de/node?id=1696254&change_language=en) +6. Mühlhäußer, M., Chourdakis, G., & Uekermann, B. (2023). Partitioned flow simulations with preCICE and OpenFOAM. Proceedings of the ECCOMAS Coupled Problems 2023. [DOI: 10.23967/c.coupled.2023.014](https://doi.org/10.23967/c.coupled.2023.014) -7. Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023.
    -URL: https://mediatum.ub.tum.de/1734883 +7. Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023. +[URL: MediaTUM](https://mediatum.ub.tum.de/1734883) From c7643d3a21a2d83a9ded59eda7f310d0d2032126 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 1 Sep 2026 11:24:51 +0200 Subject: [PATCH 64/70] docs/README.md: Move disclaimer down again --- docs/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index ffb3eb5b..dddb3282 100644 --- a/docs/README.md +++ b/docs/README.md @@ -73,10 +73,6 @@ The volume coupling functionality was contributed by Tina Vladimirova, in the co The adapter is [easily extensible](https://precice.org/adapter-openfoam-extend.html). -{% disclaimer %} -This offering is not approved or endorsed by OpenCFD Limited, producer and distributor of the OpenFOAM software via www.openfoam.com, and owner of the OPENFOAM® and OpenCFD® trade marks. -{% enddisclaimer %} - ### Related literature 1. Chourdakis, G., Schneider, D., & Uekermann, B. (2023). OpenFOAM-preCICE: Coupling OpenFOAM with External Solvers for Multi-Physics Simulations. OpenFOAM® Journal, 3, 1–25. @@ -93,3 +89,7 @@ This offering is not approved or endorsed by OpenCFD Limited, producer and distr [DOI: 10.23967/c.coupled.2023.014](https://doi.org/10.23967/c.coupled.2023.014) 7. Tina Vladimirova. Design, implementation, and validation of a volume coupling extension for the OpenFOAM-preCICE adapter. IDP report, School of Computation, Information and Technology, Technical University of Munich, 2023. [URL: MediaTUM](https://mediatum.ub.tum.de/1734883) + +{% disclaimer %} +This offering is not approved or endorsed by OpenCFD Limited, producer and distributor of the OpenFOAM software via www.openfoam.com, and owner of the OPENFOAM® and OpenCFD® trade marks. +{% enddisclaimer %} From 8b765f46c731d06faf7a9f5d5a8c5e2756e81586 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Tue, 8 Sep 2026 18:32:02 +0200 Subject: [PATCH 65/70] Clarify field support for solvers in README (#392) Co-authored-by: David Schneider --- FF/AlphaGradient.C | 9 ++++- FF/Phi.C | 9 ++++- changelog-entries/392.md | 1 + docs/README.md | 87 ++++++++++++++++++++++++++++------------ docs/config.md | 2 +- 5 files changed, 79 insertions(+), 29 deletions(-) create mode 100644 changelog-entries/392.md diff --git a/FF/AlphaGradient.C b/FF/AlphaGradient.C index 00260801..faf63a0a 100644 --- a/FF/AlphaGradient.C +++ b/FF/AlphaGradient.C @@ -64,7 +64,14 @@ void preciceAdapter::FF::AlphaGradient::read(double* buffer, const unsigned int bool preciceAdapter::FF::AlphaGradient::isLocationTypeSupported(const bool meshConnectivity) const { - return (this->locationType_ == LocationType::faceCenters); + if (meshConnectivity) + { + return false; + } + else + { + return (this->locationType_ == LocationType::faceCenters); + } } std::string preciceAdapter::FF::AlphaGradient::getDataName() const diff --git a/FF/Phi.C b/FF/Phi.C index d2001395..75ab0a74 100644 --- a/FF/Phi.C +++ b/FF/Phi.C @@ -50,7 +50,14 @@ void preciceAdapter::FF::Phi::read(double* buffer, const unsigned int dim) bool preciceAdapter::FF::Phi::isLocationTypeSupported(const bool meshConnectivity) const { - return (this->locationType_ == LocationType::faceCenters); + if (meshConnectivity) + { + return false; + } + else + { + return (this->locationType_ == LocationType::faceCenters); + } } std::string preciceAdapter::FF::Phi::getDataName() const diff --git a/changelog-entries/392.md b/changelog-entries/392.md new file mode 100644 index 00000000..59341342 --- /dev/null +++ b/changelog-entries/392.md @@ -0,0 +1 @@ +- Added a table of supported fields per read/write in `docs/README.md` [#392](https://github.com/precice/openfoam-adapter/pull/392) \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index dddb3282..a84ae539 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,32 +15,67 @@ This preCICE adapter is a plug-in (function object) for OpenFOAM, which can work ## What can it do? -This adapter can read/write the following fields in a surface coupling setup: - -- Temperature (read + write) -- Temperature surface-normal gradient (read + write) -- Heat flux (read + write) -- Sink temperature (read + write) -- Heat transfer coefficient (read + write) -- Force (read + write) -- Stress (write) -- Displacement (read + write) -- Displacement delta (read) -- Pressure (read + write) -- Pressure surface-normal gradient (read + write) -- Velocity (read + write) -- Velocity surface-normal gradient (read + write) -- Phase fraction (alpha) (read + write) -- Phase fraction (alpha) gradient (read + write) -- Phase flux (phi) (read + write) - -In addition, the adapter supports the following fields in a volume coupling setup: - -- Temperature (write) -- Pressure (write) -- Velocity (read + write) - -All features of preCICE are supported, including implicit coupling and nearest-projection mapping. Even though OpenFOAM is 3D, this adapter can also work in the 2D mode of preCICE, defining only one layer of interface nodes (automatically). +This adapter has been demonstrated on different use cases (conjugate heat transfer, fluid-structure interaction, fluid-fluid coupling, and CFD-DEM), both in 2D (3D with one layer of cells in the z-axis) and 3D, and both for flow and solid OpenFOAM-based solvers (see [tutorials](https://precice.org/tutorials.html)). +The fields to read/write are provided by different adapter modules that one needs to [configure](https://precice.org/adapter-openfoam-config.html) and are described in the following tables. + +Legend on locations to read/write: + +- **N:** Mesh nodes (surface coupling) +- **F:** Face centers (surface coupling) +- **C:** Cell centers (volume coupling) +- ***:** Mesh connectivity supported (for, e.g., nearest-projection mapping) + +Regarding nearest-projection mapping, note that, for consistent data (e.g., displacement), it only makes sense that the writing participant provides mesh connectivity. For conservative data (e.g., forces), it only makes sense that the reading participant provides mesh connectivity. + +### Module: Conjugate heat transfer + +| Field | Write | Read | Config prefix | +| --- | --- | --- | --- | +| Heat flux | N*, F | F | `Heat-Flux` | +| Heat transfer coefficient | N*, F | F | `Heat-Transfer-Coefficient` | +| Sink temperature | N*, F | F | `Sink-Temperature` | +| Temperature | N*, F, C | F, C | `Temperature` | + +All fields are supported for both flow (compressible/incompressible) and basic (e.g., laplacianFoam) solvers. + +OpenFOAM provides temperature on face centers and cell centers. For mesh nodes (with mesh connectivity enabled), the adapter interpolates from the faces to the nodes. +Similarly, heat flux, heat transfer coefficient, and sink temperature are computed on the face centers and interpolated to the mesh nodes when mesh connectivity is enabled. + +### Module: Fluid-structure interaction + +| Field | Write | Read | Config prefix | +| --- | --- | --- | --- | +| Displacement: absolute | N*, [F*](https://github.com/precice/openfoam-adapter/issues/153) | N, F | `Displacement` | +| Displacement: relative | - | N, F | `DisplacementDelta` | +| Force | F (flow solvers) | F | `Force` | +| Stress | F (flow solvers) | - | `Stress` | + +Displacement reading and writing are supported for both flow (compressible or incompressible) and structure solvers. +For mesh nodes, the `pointDisplacement` field is used; for face centers, the `cellDisplacement` field is used. + +Only flow solvers can write forces or stresses. Solid solvers can read forces, but not stresses. + +Originally, only flow OpenFOAM solvers have been considered. Reading more fields and providing connectivity by solid solvers can still be implemented. + +### Module: Fluid-fluid coupling + +| Field | Write | Read | Config prefix | +| --- | --- | --- | --- | +| Drag force | F, C | F, C | `DragForce` | +| Momentum: explicit | F, C | F, C | `ExplicitMomentum` | +| Momentum: implicit | F, C | F, C | `ImplicitMomentum` | +| Phase flux | F | F | `Phi` | +| Volume fraction | F, C | F, C | `Alpha` | +| Phase fraction gradient | F | F | `AlphaGradient` | +| Pressure | F, C | F, C | `Pressure` | +| Pressure: full gradient | F, C | F | `PressureGradientFull` | +| Pressure: surface-normal gradient | F | F | `PressureGradient` | +| Temperature | F | F | `FlowTemperature` | +| Temperature surface-normal gradient | F | F | `FlowTemperatureGradient` | +| Velocity | F, C | F, C | `Velocity` | +| Velocity surface-normal gradient | F | F | `VelocityGradient` | + +All fields assume a flow solver, and some of these fields are mainly meant for a CFD-DEM coupling (`DragForce`, `ExplicitMomentum`, `ImplicitMomentum`). ## Try diff --git a/docs/config.md b/docs/config.md index ec254cbc..983fbc4a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -224,7 +224,7 @@ solver displacementLaplacian; #### FF -The fluid-fluid coupling module supports reading and writing `Pressure`, `Velocity`, `PressureGradient`, `VelocityGradient`, `FlowTemperature`, `FlowTemperatureGradient`, `Alpha`, `AlphaGradient` and the face flux `Phi`. +The fluid-fluid coupling module supports reading and writing `Pressure`, `Velocity`, `PressureGradient` (surface-normal gradient), `PressureGradientFull` (full pressure gradient), `VelocityGradient`, `FlowTemperature`, `FlowTemperatureGradient`, `Alpha`, `AlphaGradient`, and `Phi` (face flux). In the same module, but mainly intended for coupling to particles, there are also the fields `DragForce`, `ExplicitMomentum`, `ImplicitMomentum`. Similarly to the CHT module, you need a `fixedValue` boundary condition of the respective primary field in order to read and apply values, and a `fixedGradient` boundary condition of the respective gradient field in order to read and apply gradients. From bc8ce1ec80dc7e7d74d32ce3078fbfa6ddeb4a2f Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Wed, 9 Sep 2026 18:37:18 +0200 Subject: [PATCH 66/70] Extend instructions on getting OpenFOAM --- docs/openfoam-support.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/openfoam-support.md b/docs/openfoam-support.md index f74365b6..53e733ae 100644 --- a/docs/openfoam-support.md +++ b/docs/openfoam-support.md @@ -19,7 +19,13 @@ wget -q -O - https://dl.openfoam.com/add-debian-repo.sh | sudo bash sudo apt-get install openfoam2606-dev ``` -As these steps change your `.profile`, you need to log out and in again to make OpenFOAM fully discoverable. +Follow the instructions at the end to [make OpenFOAM discoverable](https://gitlab.com/openfoam/core/openfoam/-/wikis/running). +A common approach is to add the following to your `~/.bashrc`: + +```bash + echo "source /usr/lib/openfoam/openfoam2606/etc/bashrc" >> ~/.bashrc + source ~/.bashrc +``` ## Supported OpenFOAM solvers From c2eafdf62d7194dd6c8d79bde57c192028021019 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Sun, 13 Sep 2026 19:35:29 +0200 Subject: [PATCH 67/70] Replace checkpointing workflow image with Mermaid graph --- docs/extend.md | 56 ++++++++++++++++++- ...docs-adapter-openfoam-implicit-loop-src.md | 55 ------------------ 2 files changed, 55 insertions(+), 56 deletions(-) delete mode 100644 docs/images/docs-adapter-openfoam-implicit-loop-src.md diff --git a/docs/extend.md b/docs/extend.md index b083b3e4..6a86adac 100644 --- a/docs/extend.md +++ b/docs/extend.md @@ -38,7 +38,61 @@ The checkpoints are stored and reloaded to repeat implicit time steps. As the coupling data is exchanged, the adapter repeats the time step until convergence. See this diagram for an overview: -![Implicit checkpointing diagram](images/docs-adapter-openfoam-implicit-loop.png) +{% mermaid %} +flowchart TD + subgraph loop ["Implicit Iteration Loop for Window t"] + direction TB + B{"OpenFOAM Solve"} + C["Adapter::execute()"] + D["Adapter:writeCouplingData()"] + E["preCICE::advance()"] + F{"preCICE - Rollback Needed?"} + G["Adapter::readCheckpoint() (Rollback)"] + I["Adapter - Read NEW Coupled Solver Data"] + + J{"preCICE - Save State Needed?"} + K["Adapter::writeCheckpoint()"] + L["Adapter - Read FINAL/Next Window Data"] + + end + + B --> C; + C --> D; + D --> E; + E --> F; + + F -- Yes --> G; + G --> I; + I --> B; + + F -- No --> J; %% Converged or explicit + K --> L; + J -- Yes --> K; + J -- No --> L; + + L --> M["OpenFOAM - Advance to Next Window t+dt"]; + + %% Styles + style B fill:#f9f,stroke:#333 %% Solver Solve + style M fill:#f9f,stroke:#333 %% Solver Advance Window + + style C fill:#eef,stroke:#333 %% Adapter::execute() entry + + style D fill:#ffdacc,stroke:#333 %% Write Coupling Data + style K fill:#ffdacc,stroke:#333 %% Write Checkpoint + + style E fill:#eee,stroke:#333 %% preCICE advance + style F fill:#eee,stroke:#333,stroke-dasharray: 5 5 %% preCICE Rollback Check + style J fill:#eee,stroke:#333,stroke-dasharray: 5 5 %% preCICE Save Check + + style G fill:#cfc,stroke:#333 %% Read Checkpoint + style I fill:#cfc,stroke:#333 %% Read NEW Data + style L fill:#cfc,stroke:#333 %% Read FINAL/Next Data + + style loop stroke:#aaa,stroke-width:1px,stroke-dasharray: 3 +{% endmermaid %} + +(this graph is also available as an [image file](https://github.com/precice/openfoam-adapter/blob/develop/docs/images/docs-adapter-openfoam-implicit-loop.png)) While the adapter already takes care of checkpointing all of the usual field and mesh data, your solver or use specific scenario might need to checkpoint more objects. Start by looking at the `setupCheckpointing()` function in the [Adapter.C](https://github.com/precice/openfoam-adapter/blob/master/Adapter.C). diff --git a/docs/images/docs-adapter-openfoam-implicit-loop-src.md b/docs/images/docs-adapter-openfoam-implicit-loop-src.md deleted file mode 100644 index 6ff0fdc7..00000000 --- a/docs/images/docs-adapter-openfoam-implicit-loop-src.md +++ /dev/null @@ -1,55 +0,0 @@ -# Implicit coupling diagram - -```mermaid -flowchart TD - subgraph loop ["Implicit Iteration Loop for Window t"] - direction TB - B{"OpenFOAM Solve"} - C["Adapter::execute()"] - D["Adapter:writeCouplingData()"] - E["preCICE::advance()"] - F{"preCICE - Rollback Needed?"} - G["Adapter::readCheckpoint() (Rollback)"] - I["Adapter - Read NEW Coupled Solver Data"] - - J{"preCICE - Save State Needed?"} - K["Adapter::writeCheckpoint()"] - L["Adapter - Read FINAL/Next Window Data"] - - end - - B --> C; - C --> D; - D --> E; - E --> F; - - F -- Yes --> G; - G --> I; - I --> B; - - F -- No --> J; %% Converged or explicit - K --> L; - J -- Yes --> K; - J -- No --> L; - - L --> M["OpenFOAM - Advance to Next Window t+dt"]; - - %% Styles - style B fill:#f9f,stroke:#333 %% Solver Solve - style M fill:#f9f,stroke:#333 %% Solver Advance Window - - style C fill:#eef,stroke:#333 %% Adapter::execute() entry - - style D fill:#ffdacc,stroke:#333 %% Write Coupling Data - style K fill:#ffdacc,stroke:#333 %% Write Checkpoint - - style E fill:#eee,stroke:#333 %% preCICE advance - style F fill:#eee,stroke:#333,stroke-dasharray: 5 5 %% preCICE Rollback Check - style J fill:#eee,stroke:#333,stroke-dasharray: 5 5 %% preCICE Save Check - - style G fill:#cfc,stroke:#333 %% Read Checkpoint - style I fill:#cfc,stroke:#333 %% Read NEW Data - style L fill:#cfc,stroke:#333 %% Read FINAL/Next Data - - style loop stroke:#aaa,stroke-width:1px,stroke-dasharray: 3 -``` From f1a7a84f243c242712f56138a1f217de88ec5b5b Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Sun, 13 Sep 2026 20:02:04 +0200 Subject: [PATCH 68/70] Fix the checkpointing workflow Mermaid graph --- docs/extend.md | 80 ++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/docs/extend.md b/docs/extend.md index 6a86adac..90184a11 100644 --- a/docs/extend.md +++ b/docs/extend.md @@ -40,56 +40,52 @@ See this diagram for an overview: {% mermaid %} flowchart TD - subgraph loop ["Implicit Iteration Loop for Window t"] + subgraph loop["Implicit Iteration Loop for Window t"] direction TB + B{"OpenFOAM Solve"} C["Adapter::execute()"] - D["Adapter:writeCouplingData()"] + D["Adapter::writeCouplingData()"] E["preCICE::advance()"] - F{"preCICE - Rollback Needed?"} + F{"preCICE: Rollback Needed?"} G["Adapter::readCheckpoint() (Rollback)"] - I["Adapter - Read NEW Coupled Solver Data"] - - J{"preCICE - Save State Needed?"} + I["Adapter: Read NEW Coupled Solver Data"] + J{"preCICE: Save State Needed?"} K["Adapter::writeCheckpoint()"] - L["Adapter - Read FINAL/Next Window Data"] - + L["Adapter: Read FINAL/Next Window Data"] end - B --> C; - C --> D; - D --> E; - E --> F; - - F -- Yes --> G; - G --> I; - I --> B; - - F -- No --> J; %% Converged or explicit - K --> L; - J -- Yes --> K; - J -- No --> L; - - L --> M["OpenFOAM - Advance to Next Window t+dt"]; - - %% Styles - style B fill:#f9f,stroke:#333 %% Solver Solve - style M fill:#f9f,stroke:#333 %% Solver Advance Window - - style C fill:#eef,stroke:#333 %% Adapter::execute() entry - - style D fill:#ffdacc,stroke:#333 %% Write Coupling Data - style K fill:#ffdacc,stroke:#333 %% Write Checkpoint - - style E fill:#eee,stroke:#333 %% preCICE advance - style F fill:#eee,stroke:#333,stroke-dasharray: 5 5 %% preCICE Rollback Check - style J fill:#eee,stroke:#333,stroke-dasharray: 5 5 %% preCICE Save Check - - style G fill:#cfc,stroke:#333 %% Read Checkpoint - style I fill:#cfc,stroke:#333 %% Read NEW Data - style L fill:#cfc,stroke:#333 %% Read FINAL/Next Data - - style loop stroke:#aaa,stroke-width:1px,stroke-dasharray: 3 + B --> C + C --> D + D --> E + E --> F + + F -- Yes --> G + G --> I + I --> B + + F -- No --> J + J -- Yes --> K + J -- No --> L + K --> L + + L --> M["OpenFOAM: Advance to Next Window t+dt"] + + classDef solver fill:#f9f,stroke:#333 + classDef adapter fill:#eef,stroke:#333 + classDef writeOp fill:#ffdacc,stroke:#333 + classDef precice fill:#eee,stroke:#333 + classDef decision fill:#eee,stroke:#333,stroke-dasharray:5 5 + classDef readOp fill:#cfc,stroke:#333 + + class B,M solver + class C adapter + class D,K writeOp + class E precice + class F,J decision + class G,I,L readOp + + style loop fill:none,stroke:#aaa,stroke-width:1px,stroke-dasharray:3 3 {% endmermaid %} (this graph is also available as an [image file](https://github.com/precice/openfoam-adapter/blob/develop/docs/images/docs-adapter-openfoam-implicit-loop.png)) From f3ed1d01108ee997f29e8a7507df46700c21b45d Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Mon, 14 Sep 2026 10:00:55 +0200 Subject: [PATCH 69/70] Add changelog for v1.4.0 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ changelog-entries/338.md | 1 - changelog-entries/344.md | 1 - changelog-entries/346.md | 1 - changelog-entries/347.md | 1 - changelog-entries/348.md | 1 - changelog-entries/350.md | 1 - changelog-entries/351.md | 1 - changelog-entries/352.md | 2 -- changelog-entries/354.md | 1 - changelog-entries/358.md | 1 - changelog-entries/364.md | 1 - changelog-entries/365.md | 1 - changelog-entries/366.md | 1 - changelog-entries/369.md | 1 - changelog-entries/379.md | 1 - changelog-entries/380.md | 1 - changelog-entries/386.md | 1 - changelog-entries/387.md | 1 - changelog-entries/390.md | 1 - changelog-entries/392.md | 1 - changelog-entries/394.md | 1 - changelog-entries/398.md | 1 - changelog-entries/400.md | 1 - changelog-entries/410.md | 1 - 25 files changed, 37 insertions(+), 25 deletions(-) delete mode 100644 changelog-entries/338.md delete mode 100644 changelog-entries/344.md delete mode 100644 changelog-entries/346.md delete mode 100644 changelog-entries/347.md delete mode 100644 changelog-entries/348.md delete mode 100644 changelog-entries/350.md delete mode 100644 changelog-entries/351.md delete mode 100644 changelog-entries/352.md delete mode 100644 changelog-entries/354.md delete mode 100644 changelog-entries/358.md delete mode 100644 changelog-entries/364.md delete mode 100644 changelog-entries/365.md delete mode 100644 changelog-entries/366.md delete mode 100644 changelog-entries/369.md delete mode 100644 changelog-entries/379.md delete mode 100644 changelog-entries/380.md delete mode 100644 changelog-entries/386.md delete mode 100644 changelog-entries/387.md delete mode 100644 changelog-entries/390.md delete mode 100644 changelog-entries/392.md delete mode 100644 changelog-entries/394.md delete mode 100644 changelog-entries/398.md delete mode 100644 changelog-entries/400.md delete mode 100644 changelog-entries/410.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 90bb8b66..83d5970f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,43 @@ Read more details in the issue [#52: Releases and versioning](https://github.com +## [v1.4.0] 2026-09-14 + +### Added + +- Added an experimental `Generic` module for volume or surface coupling of arbitrary scalar and vector field types [#346](https://github.com/precice/openfoam-adapter/pull/346). + The module supports `value`, `gradient`, `surface-normal-gradient` ([#398](https://github.com/precice/openfoam-adapter/pull/398)), and `mixed` ([#400](https://github.com/precice/openfoam-adapter/pull/400)) operations and performs these without any knowledge on the physical meaning of the field. +- Added the drag force, implicit momentum coefficient, explicit momentum, and the full pressure gradient in the FF module, as coupling variables for CFD-DEM simulations [#380](https://github.com/precice/openfoam-adapter/pull/380). +- Added support for adapter configuration files that don't specify a `readData` or `writeData` entry at an interface, enabling uni-directional coupling [#348](https://github.com/precice/openfoam-adapter/pull/348). +- Added support for case-insensitive field names (e.g., both `DISPLACEMENTS` and `Displacements` are now accepted), enabling coupling to solvers that require all-uppercase names [#390](https://github.com/precice/openfoam-adapter/pull/390). +- Added exception handling for preCICE API calls [#366](https://github.com/precice/openfoam-adapter/pull/366). +- Added documentation on: + - subcycling limitations [#388](https://github.com/precice/openfoam-adapter/pull/388). + - relevant differences between OpenFOAM versions, to make porting between versions easier [#351](https://github.com/precice/openfoam-adapter/pull/351). + - running the CI locally, including hints on which OpenFOAM versions are available on which platforms [#350](https://github.com/precice/openfoam-adapter/pull/350). + - the adapter being available on the OpenFOAM Package Index (OPI) [#375](https://github.com/precice/openfoam-adapter/pull/375). + +### Fixed + +- Fixed a checkpointing issue leading to a `different dimensions` error by pruning checkpointed fields that do not (anymore) appear in the registry of objects at that timestep before reading the checkpoint [#344](https://github.com/precice/openfoam-adapter/pull/344). +- Fixed a compatibility issue with OpenFOAM v2606 affecting the `coupledVelocity` and `coupledPressure` boundary conditions [#410](https://github.com/precice/openfoam-adapter/pull/410), [#411](https://github.com/precice/openfoam-adapter/pull/411). +- Fixed an issue preventing compiling the adapter with the Intel OneAPI compiler [#358](https://github.com/precice/openfoam-adapter/pull/358). +- Fixed the wording in the error message thrown when reading stresses (was previously referring to forces) [#379](https://github.com/precice/openfoam-adapter/pull/379). + +### Changed + +- Simplified the error handling process in configuration. Configure simulations with the `errors strict` option for the adapter function object to force OpenFOAM to exit a simulation if the adapter cannot be loaded [#364](https://github.com/precice/openfoam-adapter/pull/364). +- Improved the mesh checkpointing by explicitly checkpointing mesh cell volumes (`V`, `V0`, `V00`), now supporting higher-order time derivative schemes [#369](https://github.com/precice/openfoam-adapter/pull/369), [#387](https://github.com/precice/openfoam-adapter/pull/387). +- Renamed `ADAPTER_PREP_FLAGS` to `ADAPTER_CFLAGS` in `Allwmake`, now setting it from `PRECICE_OPENFOAM_CFLAGS`, if defined in the environment. Similarly, now setting `ADAPTER_TARGET_DIR` from `PRECICE_OPENFOAM_TARGET_DIR`, if defined [#352](https://github.com/precice/openfoam-adapter/pull/352). +- Updated `preciceDict` parsing for upcoming schema changes [#354](https://github.com/precice/openfoam-adapter/pull/354). + The entries `readData` and `writeData` are now mixed lists of words and/or dictionaries. + These dictionaries allow specifying additional options, such as `solver_name`, `operation`, and `flip-normal` for each field. + Backwards compatibility with the previous `wordList` parsing is maintained. +- Updated `isLocationTypeSupported` across all modules to remove unreachable conditions and to better reflect the implementation. FSI now allows mesh connectivity when writing Displacement [#393](https://github.com/precice/openfoam-adapter/pull/393). +- Upgraded the default OpenFOAM version to v2606 [#411](https://github.com/precice/openfoam-adapter/pull/411). +- Migrate CI workflows to use the [setup-openfoam](https://github.com/marketplace/actions/setup-openfoam) action to install OpenFOAM [#338](https://github.com/precice/openfoam-adapter/pull/338). +- Improved the documentation regarding the supported fields, including their locations and support for mesh connectivity [#392](https://github.com/precice/openfoam-adapter/pull/392). + ## [v1.3.1] 2024-07-27 ### Fixed diff --git a/changelog-entries/338.md b/changelog-entries/338.md deleted file mode 100644 index 92cbb7e0..00000000 --- a/changelog-entries/338.md +++ /dev/null @@ -1 +0,0 @@ -- Use the [setup-openfoam](https://github.com/marketplace/actions/setup-openfoam) action to install OpenFOAM within CI workflows [#338](https://github.com/precice/openfoam-adapter/pull/338). diff --git a/changelog-entries/344.md b/changelog-entries/344.md deleted file mode 100644 index 3e05a373..00000000 --- a/changelog-entries/344.md +++ /dev/null @@ -1 +0,0 @@ -- Fixed a bug where a field was checkpointed, but doesn't exist anymore in OpenFOAM registry, causing a crash when reloading last timestep into OpenFOAM. This is done by pruning checkpointed fields that do not (anymore) appear in the registry of objects at that timestep before reading the checkpoint. [#344](https://github.com/precice/openfoam-adapter/pull/344) \ No newline at end of file diff --git a/changelog-entries/346.md b/changelog-entries/346.md deleted file mode 100644 index 19763f6a..00000000 --- a/changelog-entries/346.md +++ /dev/null @@ -1 +0,0 @@ -- Added support for arbitrary field volume and surface coupling of scalar and vector field types in the experimental `Generic` module [#346](https://github.com/precice/openfoam-adapter/pull/346). \ No newline at end of file diff --git a/changelog-entries/347.md b/changelog-entries/347.md deleted file mode 100644 index 8ffbf5fe..00000000 --- a/changelog-entries/347.md +++ /dev/null @@ -1 +0,0 @@ -- Updated default OpenFOAM version to v2412 [#347](https://github.com/precice/openfoam-adapter/pull/347) \ No newline at end of file diff --git a/changelog-entries/348.md b/changelog-entries/348.md deleted file mode 100644 index 11201c5e..00000000 --- a/changelog-entries/348.md +++ /dev/null @@ -1 +0,0 @@ -- Added support for preciceDict configuration files that don't specify an `readData` or `writeData` entry at an interface. [#348](https://github.com/precice/openfoam-adapter/pull/348) \ No newline at end of file diff --git a/changelog-entries/350.md b/changelog-entries/350.md deleted file mode 100644 index b16222ba..00000000 --- a/changelog-entries/350.md +++ /dev/null @@ -1 +0,0 @@ -- Documented running the CI locally, including hints on which OpenFOAM versions are available on which platforms. [#350](https://github.com/precice/openfoam-adapter/pull/350) \ No newline at end of file diff --git a/changelog-entries/351.md b/changelog-entries/351.md deleted file mode 100644 index 0c041c2b..00000000 --- a/changelog-entries/351.md +++ /dev/null @@ -1 +0,0 @@ -- Documented relevant differences between OpenFOAM versions, to make porting between versions easier. [#351](https://github.com/precice/openfoam-adapter/pull/351) \ No newline at end of file diff --git a/changelog-entries/352.md b/changelog-entries/352.md deleted file mode 100644 index 08789e44..00000000 --- a/changelog-entries/352.md +++ /dev/null @@ -1,2 +0,0 @@ -- Renamed `ADAPTER_PREP_FLAGS` to `ADAPTER_CFLAGS` and set it from `PRECICE_OPENFOAM_CFLAGS`. [#352](https://github.com/precice/openfoam-adapter/pull/352) -- Set `ADAPTER_TARGET_DIT` from `PRECICE_OPENFOAM_TARGET_DIR`. [#352](https://github.com/precice/openfoam-adapter/pull/352) diff --git a/changelog-entries/354.md b/changelog-entries/354.md deleted file mode 100644 index ae995013..00000000 --- a/changelog-entries/354.md +++ /dev/null @@ -1 +0,0 @@ -- Updated `preciceDict` parsing for upcoming schema changes. `readData` and `writeData` are now mixed lists of words and/or dictionaries. Dictionaries allow specifying additional options like `solver_name`, `operation`, and `flip-normal` for each field. Backwards compatibility with old schema `wordList` parsing. [#354](https://github.com/precice/openfoam-adapter/pull/354) diff --git a/changelog-entries/358.md b/changelog-entries/358.md deleted file mode 100644 index f5752695..00000000 --- a/changelog-entries/358.md +++ /dev/null @@ -1 +0,0 @@ -- Fixed an issue preventing compiling the adapter with the Intel compiler [#358](https://github.com/precice/openfoam-adapter/pull/358). \ No newline at end of file diff --git a/changelog-entries/364.md b/changelog-entries/364.md deleted file mode 100644 index b2f8dd15..00000000 --- a/changelog-entries/364.md +++ /dev/null @@ -1 +0,0 @@ -- Removed the error-deferred workaround, simplifying the error handling process in configuration. Configure simulations with the `errors strict` option for the adapter function object. [#364](https://github.com/precice/openfoam-adapter/pull/364) \ No newline at end of file diff --git a/changelog-entries/365.md b/changelog-entries/365.md deleted file mode 100644 index 6cc4d9ef..00000000 --- a/changelog-entries/365.md +++ /dev/null @@ -1 +0,0 @@ -- Changed the reference version of preCICE used by the CI workflows to v3.2.0. [#365](https://github.com/precice/openfoam-adapter/pull/365) \ No newline at end of file diff --git a/changelog-entries/366.md b/changelog-entries/366.md deleted file mode 100644 index fc227e8c..00000000 --- a/changelog-entries/366.md +++ /dev/null @@ -1 +0,0 @@ -- Added exception handling for preCICE API calls [#366](https://github.com/precice/openfoam-adapter/pull/366) \ No newline at end of file diff --git a/changelog-entries/369.md b/changelog-entries/369.md deleted file mode 100644 index 3bf3e6cc..00000000 --- a/changelog-entries/369.md +++ /dev/null @@ -1 +0,0 @@ -- Explicitly checkpoint mesh cell volumes (V, V0, V00) and support higher-order time derivative schemes, fix for implicit subcycling [#369](https://github.com/precice/openfoam-adapter/pull/369) diff --git a/changelog-entries/379.md b/changelog-entries/379.md deleted file mode 100644 index a950d624..00000000 --- a/changelog-entries/379.md +++ /dev/null @@ -1 +0,0 @@ -- Fixed the wording in the error message thrown when reading stresses (was previously referring to forces) [#379](https://github.com/precice/openfoam-adapter/pull/379) \ No newline at end of file diff --git a/changelog-entries/380.md b/changelog-entries/380.md deleted file mode 100644 index 0510ee02..00000000 --- a/changelog-entries/380.md +++ /dev/null @@ -1 +0,0 @@ -- Added the drag force, implicit momentum coefficient, explicit momentum, and the full pressure gradient as coupling variables for CFD-DEM. diff --git a/changelog-entries/386.md b/changelog-entries/386.md deleted file mode 100644 index 0c32bb3f..00000000 --- a/changelog-entries/386.md +++ /dev/null @@ -1 +0,0 @@ -- Changed the default OpenFOAM version to v2512 and preCICE to v3.3.0 [#386](https://github.com/precice/openfoam-adapter/pull/386) \ No newline at end of file diff --git a/changelog-entries/387.md b/changelog-entries/387.md deleted file mode 100644 index 6c12fb23..00000000 --- a/changelog-entries/387.md +++ /dev/null @@ -1 +0,0 @@ -- Mesh points checkpointing refactor. Removed unused code related to mesh field and mesh volume checkpointing [#387](https://github.com/precice/openfoam-adapter/pull/387) \ No newline at end of file diff --git a/changelog-entries/390.md b/changelog-entries/390.md deleted file mode 100644 index 33d4548c..00000000 --- a/changelog-entries/390.md +++ /dev/null @@ -1 +0,0 @@ -- Add support for case-insensitive field names (e.g., both `DISPLACEMENTS` and `Displacements`) [#390](https://github.com/precice/openfoam-adapter/pull/390). diff --git a/changelog-entries/392.md b/changelog-entries/392.md deleted file mode 100644 index 59341342..00000000 --- a/changelog-entries/392.md +++ /dev/null @@ -1 +0,0 @@ -- Added a table of supported fields per read/write in `docs/README.md` [#392](https://github.com/precice/openfoam-adapter/pull/392) \ No newline at end of file diff --git a/changelog-entries/394.md b/changelog-entries/394.md deleted file mode 100644 index f9e42b1f..00000000 --- a/changelog-entries/394.md +++ /dev/null @@ -1 +0,0 @@ -Updated `isLocationTypeSupported` across all modules to remove unreachable conditions and to better reflect the implementation. FSI now allows mesh connectivity when writing Displacement. \ No newline at end of file diff --git a/changelog-entries/398.md b/changelog-entries/398.md deleted file mode 100644 index 292fbcc3..00000000 --- a/changelog-entries/398.md +++ /dev/null @@ -1 +0,0 @@ -- Added support for `gradient` operation in the `Generic` module [#398](https://github.com/precice/openfoam-adapter/pull/398) \ No newline at end of file diff --git a/changelog-entries/400.md b/changelog-entries/400.md deleted file mode 100644 index aa718579..00000000 --- a/changelog-entries/400.md +++ /dev/null @@ -1 +0,0 @@ -- Add support for Robin or mixed boundary condition in the Generic module. [#400](https://github.com/precice/openfoam-adapter/pull/400). \ No newline at end of file diff --git a/changelog-entries/410.md b/changelog-entries/410.md deleted file mode 100644 index 90450b80..00000000 --- a/changelog-entries/410.md +++ /dev/null @@ -1 +0,0 @@ -- Fixed a compatibility issue with OpenFOAM v2606 affecting the coupledVelocity and coupledPressure boundary conditions [#410](https://github.com/precice/openfoam-adapter/pull/410), [#411](https://github.com/precice/openfoam-adapter/pull/411) \ No newline at end of file From a752e0177fa0d6bbb782c4771e4df439355fe184 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Mon, 14 Sep 2026 10:02:17 +0200 Subject: [PATCH 70/70] Bump version to v1.4.0 --- Adapter.C | 2 +- tools/create-release-archives.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Adapter.C b/Adapter.C index e98615fe..40b0c1a8 100644 --- a/Adapter.C +++ b/Adapter.C @@ -11,7 +11,7 @@ preciceAdapter::Adapter::Adapter(const Time& runTime, const fvMesh& mesh) : runTime_(runTime), mesh_(mesh) { - adapterInfo("Loaded the OpenFOAM-preCICE adapter - v1.3.1.", "info"); + adapterInfo("Loaded the OpenFOAM-preCICE adapter - v1.4.0.", "info"); return; } diff --git a/tools/create-release-archives.sh b/tools/create-release-archives.sh index 4ae8c892..e853bb1f 100755 --- a/tools/create-release-archives.sh +++ b/tools/create-release-archives.sh @@ -4,7 +4,7 @@ set -u remote_name=origin -adapter_version="v1.3.1" +adapter_version="v1.4.0" # Declare branch names for which archives will be created declare -a branches=("master" "OpenFOAMv1806" "OpenFOAM10" "OpenFOAM9" "OpenFOAM8" "OpenFOAM7" "OpenFOAM6" "OpenFOAM5")