diff --git a/.gitignore b/.gitignore index b7f69be9..02cb3a9c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,8 @@ lnInclude/ Make/linux64GccDPInt32Opt/ Make/darwin64ClangDPInt32Opt/ +**/Make/linux64GccDPInt32Opt/ +**/Make/darwin64ClangDPInt32Opt/ # Editors .cproject diff --git a/Adapter.C b/Adapter.C index 6c2c8568..63b882ba 100644 --- a/Adapter.C +++ b/Adapter.C @@ -887,6 +887,51 @@ void preciceAdapter::Adapter::setupCheckpointing() { SETUP_TIMER(); +#ifdef ADAPTER_ENABLE_LAGRANGIAN + // Lagrangian particle checkpoint setup + activeKinematicClouds_.clear(); + activeThermoClouds_.clear(); + activeKinematicCollidingClouds_.clear(); + activeReactingClouds_.clear(); + activeReactingMultiphaseClouds_.clear(); + + // macro to loop over all names of the cloud type, define a cloud pointer, and populate + // the activeCloud vector with the pointer contents for a given cloud type + DEBUG(adapterInfo("Adding in checkpointed clouds...")); + +#undef setupCloudType +#define setupCloudType(CloudType, ActiveList) \ + for (const word& cloudName : mesh_.thisDb().sortedNames()) \ + { \ + CloudType* cloudPtr = const_cast(mesh_.thisDb().getObjectPtr(cloudName)); \ + if (cloudPtr) \ + { \ + ActiveList.push_back(cloudPtr); \ + lagrangianCheckPointed_ = true; \ + adapterInfo("Successfully located " #CloudType " '" + cloudName + "' for Lagrangian checkpointing.", "info"); \ + } \ + } + + // run macro for all available cloud types + setupCloudType(basicKinematicCloud, activeKinematicClouds_); + setupCloudType(basicThermoCloud, activeThermoClouds_); + setupCloudType(basicReactingCloud, activeReactingClouds_); + setupCloudType(basicReactingMultiphaseCloud, activeReactingMultiphaseClouds_); + setupCloudType(basicKinematicCollidingCloud, activeKinematicCollidingClouds_); + +#undef setupCloudType + + // Allocate the 2D backup array with the number of clouds found + if (lagrangianCheckPointed_) + { + backupKinematicParcelsLists_.resize(activeKinematicClouds_.size()); + backupThermoParcelsLists_.resize(activeThermoClouds_.size()); + backupReactingParcelsLists_.resize(activeReactingClouds_.size()); + backupReactingMultiphaseParcelsLists_.resize(activeReactingMultiphaseClouds_.size()); + backupKinematicCollidingParcelsLists_.resize(activeKinematicCollidingClouds_.size()); + } +#endif + // Add fields in the checkpointing list - sorted for parallel consistency DEBUG(adapterInfo("Adding in checkpointed fields...")); @@ -1269,6 +1314,38 @@ void preciceAdapter::Adapter::readCheckpoint() } } +#ifdef ADAPTER_ENABLE_LAGRANGIAN + // Lagrangian particle checkpoint read; restore state + if (lagrangianCheckPointed_) + { + // macro to restore the state of different cloud types +#undef restoreCloudType +#define restoreCloudType(CloudType, ActiveList, BackupList) \ + for (size_t c = 0; c < ActiveList.size(); ++c) \ + { \ + auto& cloud = ActiveList[c]; \ + auto& backups = BackupList[c]; \ + /* Erase the future state for this cloud */ \ + cloud->clear(); \ + /* Resurrect the past from this cloud's backups */ \ + for (auto& pPtr : backups) \ + { \ + CloudType::particleType* restoredPtr = static_cast(pPtr().clone().ptr()); \ + cloud->addParticle(restoredPtr); \ + } \ + DEBUG(adapterInfo("Restored " + std::to_string(backups.size()) + " parcels for cloud '" + cloud->name() + "'.")); \ + } + + restoreCloudType(basicKinematicCloud, activeKinematicClouds_, backupKinematicParcelsLists_); + restoreCloudType(basicThermoCloud, activeThermoClouds_, backupThermoParcelsLists_); + restoreCloudType(basicReactingCloud, activeReactingClouds_, backupReactingParcelsLists_); + restoreCloudType(basicReactingMultiphaseCloud, activeReactingMultiphaseClouds_, backupReactingMultiphaseParcelsLists_); + restoreCloudType(basicKinematicCollidingCloud, activeKinematicCollidingClouds_, backupKinematicCollidingParcelsLists_); + +#undef restoreCloudType + } +#endif + // NOTE: Add here other field types to read, if needed. DEBUG(adapterInfo("Checkpoint was read. Time = " + std::to_string(runTime_.value()))); @@ -1353,6 +1430,39 @@ void preciceAdapter::Adapter::writeCheckpoint() { *(pointTensorFieldCopies_.at(i)) == *(pointTensorFields_.at(i)); } + +#ifdef ADAPTER_ENABLE_LAGRANGIAN + // Lagrangian particle checkpoint write; save current state + if (lagrangianCheckPointed_) + { +#undef backupCloudType +#define backupCloudType(CloudType, ActiveList, BackupList) \ + /* loop over all clouds */ \ + for (size_t c = 0; c < ActiveList.size(); ++c) \ + { \ + auto& cloud = ActiveList[c]; \ + auto& backups = BackupList[c]; \ + /* clear previous backups */ \ + backups.clear(); \ + /* Clone the current state for this specific cloud */ \ + forAllIter(CloudType, *cloud, iter) \ + { \ + CloudType::particleType* clonedPtr = static_cast(iter().clone().ptr()); \ + backups.push_back(autoPtr(clonedPtr)); \ + } \ + DEBUG(adapterInfo("Checkpointed " + std::to_string(backups.size()) + " parcels for cloud '" + cloud->name() + "'.")); \ + } + + backupCloudType(basicKinematicCloud, activeKinematicClouds_, backupKinematicParcelsLists_); + backupCloudType(basicThermoCloud, activeThermoClouds_, backupThermoParcelsLists_); + backupCloudType(basicReactingCloud, activeReactingClouds_, backupReactingParcelsLists_); + backupCloudType(basicReactingMultiphaseCloud, activeReactingMultiphaseClouds_, backupReactingMultiphaseParcelsLists_); + backupCloudType(basicKinematicCollidingCloud, activeKinematicCollidingClouds_, backupKinematicCollidingParcelsLists_); + +#undef backupCloudType + } +#endif + // NOTE: Add here other types to write, if needed. DEBUG(adapterInfo("Checkpoint for time t = " + std::to_string(runTime_.value()) + " was stored.")); @@ -1566,6 +1676,25 @@ void preciceAdapter::Adapter::teardown() Generic_ = nullptr; } +#ifdef ADAPTER_ENABLE_LAGRANGIAN + // Delete Lagrangian particle backups +#undef teardownCloudType +#define teardownCloudType(ActiveList, BackupList) \ + for (auto& backups : BackupList) \ + { \ + backups.clear(); \ + } \ + BackupList.clear(); \ + ActiveList.clear(); + + teardownCloudType(activeKinematicClouds_, backupKinematicParcelsLists_); + teardownCloudType(activeThermoClouds_, backupThermoParcelsLists_); + teardownCloudType(activeReactingClouds_, backupReactingParcelsLists_); + teardownCloudType(activeReactingMultiphaseClouds_, backupReactingMultiphaseParcelsLists_); + teardownCloudType(activeKinematicCollidingClouds_, backupKinematicCollidingParcelsLists_); + +#undef teardownCloudType +#endif // NOTE: Delete your new module here return; diff --git a/Adapter.H b/Adapter.H index dff9b6a8..abd88938 100644 --- a/Adapter.H +++ b/Adapter.H @@ -14,6 +14,15 @@ #include "module-generic/Generic.H" +// Lagrangian particle checkpointing includes +#ifdef ADAPTER_ENABLE_LAGRANGIAN +#include "basicThermoCloud.H" +#include "basicReactingCloud.H" +#include "basicReactingMultiphaseCloud.H" +#include "basicKinematicCloud.H" +#include "basicKinematicCollidingCloud.H" +#endif + // 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 @@ -108,6 +117,11 @@ private: //- Switch to enable the FluidFluid module bool FFenabled_ = false; +#ifdef ADAPTER_ENABLE_LAGRANGIAN + //- Switch for Lagrangian particle checkpointing + bool lagrangianCheckPointed_ = false; +#endif + // NOTE: Add a switch for your new module here //- Switch to enable General module @@ -144,6 +158,23 @@ private: //- Stored (fixed) timestep double timestepStored_; + // Lagrangian particle cloud pointers +#ifdef ADAPTER_ENABLE_LAGRANGIAN + //- Pointer to all active particle clouds + std::vector activeThermoClouds_; + std::vector activeReactingClouds_; + std::vector activeReactingMultiphaseClouds_; + std::vector activeKinematicClouds_; + std::vector activeKinematicCollidingClouds_; + + //- 2D Array of cloned particles to hold the current state for each particle cloud + std::vector>> backupThermoParcelsLists_; + std::vector>> backupReactingParcelsLists_; + std::vector>> backupReactingMultiphaseParcelsLists_; + std::vector>> backupKinematicParcelsLists_; + std::vector>> backupKinematicCollidingParcelsLists_; +#endif + // Checkpointing //- Checkpointed time (value) diff --git a/Allclean b/Allclean index e7f7ee4a..8b4971bc 100755 --- a/Allclean +++ b/Allclean @@ -9,6 +9,7 @@ if [ -z "${WM_PROJECT_VERSION:-}" ]; then fi # Cleanup all the WMake directories +wclean implicitPatchInjection/ wclean && echo "Cleaning complete!" # Delete the log files diff --git a/Allwmake b/Allwmake index ec7df980..80a35cc7 100755 --- a/Allwmake +++ b/Allwmake @@ -6,6 +6,7 @@ set -e -u # Optional: Preprocessor flags # "-DADAPTER_DEBUG_MODE" enables debug messages # "-DADAPTER_ENABLE_TIMINGS" enables time measurements +# "-DADAPTER_ENABLE_LAGRANGIAN" enables lagrangian particle support ADAPTER_CFLAGS="${PRECICE_OPENFOAM_CFLAGS:-}" # Build command and options @@ -13,6 +14,12 @@ ADAPTER_CFLAGS="${PRECICE_OPENFOAM_CFLAGS:-}" # e.g., add "export WM_NCOMPPROCS=4" to your ~/.bashrc # Make sure that these options are supported by your OpenFOAM version. adapter_build_command(){ + if [ "${ADAPTER_ENABLE_LAGRANGIAN:-0}" = "1" ]; then + log "Building implicit injectors..." + wmake libso implicitInjectionWrapper/ + fi + + log "Building preCICE adapter..." wmake libso } @@ -37,6 +44,19 @@ warning() { # Information header log "Building the OpenFOAM-preCICE adapter..." +# Check if the user opted into Lagrangian particle features +if [ "${ADAPTER_ENABLE_LAGRANGIAN:-0}" = "1" ]; then + log "Enabling Lagrangian particle tracking support..." + export ADAPTER_CFLAGS="${ADAPTER_CFLAGS} -DADAPTER_ENABLE_LAGRANGIAN" + + # export these variables so Make/options can link to them dynamically + export ADAPTER_LAGRANGIAN_INC="-I\$(LIB_SRC)/finiteArea/lnInclude -I\$(LIB_SRC)/faOptions/lnInclude -I\$(LIB_SRC)/lagrangian/basic/lnInclude -I\$(LIB_SRC)/lagrangian/intermediate/lnInclude -I\$(LIB_SRC)/lagrangian/distributionModels/lnInclude -I\$(LIB_SRC)/regionModels/lnInclude -I\$(LIB_SRC)/regionModels/regionModel/lnInclude -I\$(LIB_SRC)/regionModels/surfaceFilmModels/lnInclude -I\$(LIB_SRC)/regionFaModels/lnInclude -I\$(LIB_SRC)/thermophysicalModels/SLGThermo/lnInclude -I\$(LIB_SRC)/thermophysicalModels/specie/lnInclude -I\$(LIB_SRC)/thermophysicalModels/thermophysicalProperties/lnInclude -I\$(LIB_SRC)/thermophysicalModels/reactionThermo/lnInclude" + export ADAPTER_LAGRANGIAN_LIBS="-llagrangian -llagrangianIntermediate -lspecie -lSLGThermo -lregionModels -lsurfaceFilmModels -lreactionThermophysicalModels -lthermophysicalProperties -lregionFaModels -lfiniteArea" +else + export ADAPTER_LAGRANGIAN_INC="" + export ADAPTER_LAGRANGIAN_LIBS="" +fi + # Export the environment variables export ADAPTER_CFLAGS export ADAPTER_TARGET_DIR diff --git a/Make/options b/Make/options index af0f6292..d1bbac47 100644 --- a/Make/options +++ b/Make/options @@ -12,6 +12,7 @@ EXE_INC = \ -I$(LIB_SRC)/TurbulenceModels/compressible/lnInclude \ -I$(LIB_SRC)/TurbulenceModels/incompressible/lnInclude \ -I$(LIB_SRC)/triSurface/lnInclude \ + $(ADAPTER_LAGRANGIAN_INC) \ $(ADAPTER_PKG_CONFIG_CFLAGS) \ -I../ \ $(ADAPTER_CFLAGS) @@ -22,5 +23,6 @@ LIB_LIBS = \ -lcompressibleTurbulenceModels \ -lincompressibleTurbulenceModels \ -limmiscibleIncompressibleTwoPhaseMixture \ + $(ADAPTER_LAGRANGIAN_LIBS) \ $(ADAPTER_PKG_CONFIG_LIBS) \ -lprecice 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 +} diff --git a/implicitInjectionWrapper/Make/files b/implicitInjectionWrapper/Make/files new file mode 100644 index 00000000..152f9f53 --- /dev/null +++ b/implicitInjectionWrapper/Make/files @@ -0,0 +1,7 @@ +makeImplicitInjectorsKinematic.C +makeImplicitInjectorsKinematicColliding.C +makeImplicitInjectorsThermo.C +makeImplicitInjectorsReacting.C +makeImplicitInjectorsReactingMultiphase.C + +LIB = $(FOAM_USER_LIBBIN)/libimplicitInjectors diff --git a/implicitInjectionWrapper/Make/options b/implicitInjectionWrapper/Make/options new file mode 100644 index 00000000..c9e34f66 --- /dev/null +++ b/implicitInjectionWrapper/Make/options @@ -0,0 +1,36 @@ +EXE_INC = \ + -I$(LIB_SRC)/finiteVolume/lnInclude \ + -I$(LIB_SRC)/lagrangian/basic/lnInclude \ + -I$(LIB_SRC)/lagrangian/intermediate/lnInclude \ + -I$(LIB_SRC)/lagrangian/distributionModels/lnInclude \ + -I$(LIB_SRC)/meshTools/lnInclude \ + -I$(LIB_SRC)/thermophysicalModels/basic/lnInclude \ + -I$(LIB_SRC)/thermophysicalModels/specie/lnInclude \ + -I$(LIB_SRC)/thermophysicalModels/thermophysicalProperties/lnInclude \ + -I$(LIB_SRC)/thermophysicalModels/reactionThermo/lnInclude \ + -I$(LIB_SRC)/thermophysicalModels/SLGThermo/lnInclude \ + -I$(LIB_SRC)/regionModels/regionModel/lnInclude \ + -I$(LIB_SRC)/regionModels/surfaceFilmModels/lnInclude \ + -I$(LIB_SRC)/regionFaModels/lnInclude \ + -I$(LIB_SRC)/finiteArea/lnInclude \ + -I$(LIB_SRC)/faOptions/lnInclude \ + -I$(LIB_SRC)/transportModels/incompressible/lnInclude \ + -I$(LIB_SRC)/transportModels/compressible/lnInclude + +LIB_LIBS = \ + -lfiniteVolume \ + -llagrangian \ + -llagrangianIntermediate \ + -ldistributionModels \ + -lmeshTools \ + -lfluidThermophysicalModels \ + -lspecie \ + -lthermophysicalProperties \ + -lSLGThermo \ + -lregionModels \ + -lsurfaceFilmModels \ + -lregionFaModels \ + -lfiniteArea \ + -lfaOptions \ + -lcompressibleTransportModels \ + -lincompressibleTransportModels diff --git a/implicitInjectionWrapper/README.md b/implicitInjectionWrapper/README.md new file mode 100644 index 00000000..b51c6bcf --- /dev/null +++ b/implicitInjectionWrapper/README.md @@ -0,0 +1,30 @@ +# Implicit Injectors Library + +This library is provided in addition to the OpenFOAM preCICE adapter in order to properly handle implicit coupling of Lagrangian particles via preCICE. Proper implicit coupling of Lagrangian particles involves not only accounting for existing particles in the domain during checkpointing, but also adjusting the particle injection logic for implicit coupling loops within time-windows as well. Particle injection is handled in OpenFOAM via injectionModels. injectionModels lie outside the OpenFOAM solver objectRegistry, so they cannot be directly modified by the existing OpenFOAM preCICE adapter functionObject. Therefore, in order to properly handle checkpoint logic for injectionModels, this independent library was added. + +## Source Files + +- `implicitInjectionWrapper.H` - a wrapper template class used to override the `prepareForNextTimeStep` function from the native OpenFOAM `InjectionModel` class for every `InjectionModel` type. This creates "implicit" versions of the native OpenFOAM InjectionModel types (`implicitPatchInjection`, `implicitConeNozzleInjection`, `implicitManualInjection`, etc.) that support checkpoint logic from preCICE implicit coupling. +- `makeImplicitInjectorsKinematic.C` - uses macro defined in `implicitInjectionWrapper.H` to create wrapped template classes for `implicitPatchInjection`, `implicitConeNozzleInjection`, and `implicitManualInjection` types registered to the `basicKinematicCloud` type +- `makeImplicitInjectorsKinematicColliding.C` - uses macro defined in `implicitInjectionWrapper.H` to create wrapped template classes for `implicitPatchInjection`, `implicitConeNozzleInjection`, and `implicitManualInjection` types registered to the `basicKinematicCollidingCloud` type +- `makeImplicitInjectorsReacting.C` - uses macro defined in `implicitInjectionWrapper.H` to create wrapped template classes for `implicitPatchInjection`, `implicitConeNozzleInjection`, and `implicitManualInjection` types registered to the `basicReactingCloud` type +- `makeImplicitInjectorsReactingMultiphase.C` - uses macro defined in `implicitInjectionWrapper.H` to create wrapped template classes for `implicitPatchInjection`, `implicitConeNozzleInjection`, and `implicitManualInjection` types registered to the `basicReactingMultiphaseCloud` type +- `makeImplicitInjectorsThermo.C` - uses macro defined in `implicitInjectionWrapper.H` to create wrapped template classes for `implicitPatchInjection`, `implicitConeNozzleInjection`, and `implicitManualInjection` types registered to the `basicThermoCloud` type + +## User-Guide + +The successfully built `libimplicitInjectors.so` file should exist in the same location as a successfully built `libpreciceAdapterFunctionObject.so` file. To use one of these implicit-aware injection types the user must do two things: + +1. Load the `libimplicitInjectors.so` file as a libarary in your system/controlDict file so the solver is aware of the existence of these implicit-aware injectors. To do so, add the following just prior to your "functions" entries: + ```bash + libs + ( + "libimplicitInjectors.so" + ); + ``` + +2. Define the implicit injector type in your cloud properties file (constant/KinematicCloudProperties, constant/reactingCloud1Properties, etc.) Particle injector types are usually defined within this file in the subModels.InjectionModels.{modelName} sub-dictionary. Define the type to be either implicitPatchInjection, implicitConeNozzleInjection, or implicitManualInjection. + +## 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. diff --git a/implicitInjectionWrapper/implicitInjectionWrapper.H b/implicitInjectionWrapper/implicitInjectionWrapper.H new file mode 100644 index 00000000..cdb142f3 --- /dev/null +++ b/implicitInjectionWrapper/implicitInjectionWrapper.H @@ -0,0 +1,208 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2011-2017 OpenFOAM Foundation + Copyright (C) 2021 OpenCFD Ltd. + Copyright (C) 2026 Corvid Technologies +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see . + +Class + Foam::implicitInjectionWrapper + +Group + grpLagrangianIntermediateInjectionSubModels + +Description + + Wrapper template class for overriding the prepareForNextTimeStep function + from the InjectionModel class for every InjectionModel type. + + This creates "implicit" versions of the native OpenFOAM InjectionModel + types (implicitPatchInjection, implicitConeNozzleInjection, + implicitManualInjection, etc.) that support time-rewind from preCICE + implicit coupling. + + Derived from native InjectionModels (v2412) to support Eulerian-Lagrangian + implicit coupling. Explicitly handles injection memory removal, + delayedVolume_ fractional mass caching, time0_ and timeStep0_ caching, + and rndGen() seed restoration during preCICE sub-iteration rewinds to + ensure strict mass conservation and eliminate Monte Carlo noise. + + Native OpenFOAM InjectionModels found here: https://gitlab.com/openfoam/core/openfoam/-/tree/OpenFOAM-v2412/src/lagrangian/intermediate/submodels/Kinematic/InjectionModel + +SourceFiles + implicitInjectionWrapper.C + +\*---------------------------------------------------------------------------*/ + +#ifndef implicitInjectionWrapper_H +#define implicitInjectionWrapper_H + +#include "InjectionModel.H" +#include + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ + +// Base class for implicit injectors of various kinds. Specific instances +// will be generated using the DEFINE_IMPLICIT_INJECTOR macro defined later. +template class BaseInjector, class CloudType> +class ImplicitInjectorBase : public BaseInjector +{ +private: + /*- preCICE implicit coupling timeline buffers */ + + /*- Cache the history of times to determine which index corresponds to */ + /* the beginning of the time window */ + std::vector histTime_; + + /*- Cache the time-window history of delayed volume to prevent */ + /* fractional mass leaks on rewind */ + std::vector histDelayedVolume_; + + /*- cache the time-window history of the mass injected value */ + std::vector histMassInjected_; + + /*- cache the time-window history of the parcels added value */ + std::vector