Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,83 @@ To get started, you can visit the one and only [wiki page](https://github.com/ST

- Everything compiles to your `$FOAM_USER_LIBBIN` with a recent ESI ([openfoam.com](https://openfoam.com)) version.
- Our favorite refinement cell selector is the [`coded`](https://github.com/STFS-TUDa/blastAMR/blob/0e70469d718ee53a5ce892e0e24a4f940bfba369/tutorials/poly_freelyPropFlame2D_H2/constant/dynamicMeshDict#L25). Although a wide range of selectors is provided, they are not well tested. So, use the coded refinement whenever possible.

## Overset meshes

`adaptiveOversetFvMesh` is `dynamicOversetFvMesh` with AMR and load balancing on
top, so mesh motion, refinement and balancing are all driven from a single
`constant/dynamicMeshDict`:

```
dynamicFvMesh adaptiveOversetFvMesh;

solvers
{} // or a motionSolver list, as for dynamicOversetFvMesh

adaptiveOversetFvMeshCoeffs
{
// the usual blastAMR settings
}
```

Zone book-keeping across topology changes and redistribution is handled by a
run-time selectable `oversetHandler`, picked up automatically for any mesh with
an `overset` patch. Set `oversetHandler none;` to switch it off, or register
your own type if you use custom `cellCellStencil` code:

```
class myHandler : public oversetHandler { ... };
addToRunTimeSelectionTable(oversetHandler, myHandler, mesh);
```

Cells carried by a motion solver must be kept out of adaptation:

```
protectZones ( movingZone ); // cell zones excluded from AMR
```

OpenFOAM places points introduced by refinement in the reference configuration
by assuming the motion is a pure scaling (`points0MotionSolver::updateMesh`).
That is wrong under rotation, and the mesh tangles as the body turns. Refine the
background around the body instead, which is the useful case anyway.

See `tutorials/oversetHeatTransfer` (static zones, AMR + load balancing) and
`tutorials/oversetRotatingSquare` (moving body, AMR).

### Load balancing an overset mesh

The default `cellCount` load policy weights every cell the same, which
misprices an overset mesh: a hole cell is masked out of the solution and
carries no physics, yet counts as a full cell, so a rank owning much of the
hole region reports as loaded while doing almost no work. `cellCountWithOverset`
prices cells by overset type instead, with user-configurable factors:

```
loadPolicy cellCountWithOverset;

holeWeight 0.01; // masked out, no physics solved
interpolatedWeight 1.5; // extra matrix faces + donor comms
porousWeight 1.0; // no donors found
specialWeight 1.0; // hole acting as a donor
```

Only the hole discount is on by default; the rest default to 1, i.e. to plain
`cellCount`. Calculated cells always keep their weight, particle contributions
are never discounted, and a mesh with no overset patch falls back to
`cellCount` untouched. Both overset tutorials use this policy.

Expect it to report *larger* imbalances than `cellCount`, because it can see
imbalance that cell counting cannot. On `tutorials/oversetRotatingSquare`,
where holes run 18-47% of the mesh, `cellCount` peaks at 2.8% imbalance while
`cellCountWithOverset` peaks at 20.9%. **A threshold tuned for `cellCount` is
therefore far too aggressive**: at `allowableImbalance 0.01` that case
rebalances 176 times instead of 12. Pick a threshold clear of the noise
floor: on that case the overset-aware metric sits at a 1-2.6% mean, so the
tutorial uses 0.05, which gives 24 balances. Raise `allowableImbalance`
whenever you switch policy.

Known limitations:

- Refinement driven by the `loadBalancedAMR` function object needs
`adaptiveOversetFvMesh`; the function object on a stock `dynamicOversetFvMesh`
covers load balancing only.
1 change: 1 addition & 0 deletions src/dynamicFvMesh/Make/files
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
adaptiveFvMesh/adaptiveFvMesh.C
adaptiveOversetFvMesh/adaptiveOversetFvMesh.C

LIB = $(FOAM_USER_LIBBIN)/libamrDynamicFvMesh
2 changes: 2 additions & 0 deletions src/dynamicFvMesh/Make/options
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ EXE_INC = $(DEBUG_FLAGS) --std=c++17 \
-I$(LIB_SRC)/finiteVolume/lnInclude \
-I$(LIB_SRC)/dynamicMesh/lnInclude \
-I$(LIB_SRC)/dynamicFvMesh/lnInclude \
-I$(LIB_SRC)/overset/lnInclude \
-I$(LIB_SRC)/lagrangian/basic/lnInclude \
-I$(LIB_SRC)/sampling/lnInclude \
-I$(LIB_SRC)/surfMesh/lnInclude \
Expand All @@ -19,6 +20,7 @@ LIB_LIBS = \
-lfiniteVolume \
-ldynamicMesh \
-ldynamicFvMesh \
-loverset \
-llagrangian \
-lsampling \
-lsurfMesh \
Expand Down
19 changes: 11 additions & 8 deletions src/dynamicFvMesh/adaptiveFvMesh/adaptiveFvMesh.C
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ License
#include "addToRunTimeSelectionTable.H"
#include "cloudSupport.H"
#include "sampledSurfaceWorkaround.H"
#include "dynMeshTools.H"

// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

Expand Down Expand Up @@ -99,12 +100,14 @@ void Foam::adaptiveFvMesh::readDict()

void Foam::adaptiveFvMesh::updateMesh(const mapPolyMesh& map)
{
// Call parent updateMesh first: flux correction indexes the fluxes
// against the new face count and reads the new mesh geometry, so the
// fields have to be mapped to the new topology before amrCore runs
fvMesh::updateMesh(map);

// Delegate flux correction and refiner update to amrCore
amrCore_.updateMesh(map);

// Call parent updateMesh
fvMesh::updateMesh(map);

// Remap clouds AFTER fvMesh::updateMesh completes.
// This must happen after fvMesh::updateMesh because cloud.autoMap
// triggers mesh_.V() which reconstructs volumes. If done before,
Expand Down Expand Up @@ -167,11 +170,11 @@ void Foam::adaptiveFvMesh::mapFields(const mapPolyMesh& mpm)

// Correct surface fields on introduced internal faces. These get
// created out-of-nothing so get an interpolated value.
mapNewInternalFaces<scalar>(mpm.faceMap());
mapNewInternalFaces<vector>(mpm.faceMap());
mapNewInternalFaces<sphericalTensor>(mpm.faceMap());
mapNewInternalFaces<symmTensor>(mpm.faceMap());
mapNewInternalFaces<tensor>(mpm.faceMap());
meshTools::mapNewInternalFaces<scalar>(*this, mpm.faceMap());
meshTools::mapNewInternalFaces<vector>(*this, mpm.faceMap());
meshTools::mapNewInternalFaces<sphericalTensor>(*this, mpm.faceMap());
meshTools::mapNewInternalFaces<symmTensor>(*this, mpm.faceMap());
meshTools::mapNewInternalFaces<tensor>(*this, mpm.faceMap());
}

bool Foam::adaptiveFvMesh::firstUpdate()
Expand Down
6 changes: 0 additions & 6 deletions src/dynamicFvMesh/adaptiveFvMesh/adaptiveFvMesh.H
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,6 @@ protected:
//- Distribute additional information
virtual void distribute(const mapDistributePolyMesh& map);

//- Map non-flux surface<Type>Fields for new internal faces
// (from cell splitting)
template <class T>
void mapNewInternalFaces(const labelList& faceMap)
{}

bool firstUpdate();

public:
Expand Down
206 changes: 206 additions & 0 deletions src/dynamicFvMesh/adaptiveOversetFvMesh/adaptiveOversetFvMesh.C
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2025
\\/ M anipulation |
-------------------------------------------------------------------------------
License
This file is a derivative work 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 <http://www.gnu.org/licenses/>.

\*---------------------------------------------------------------------------*/

#include "adaptiveOversetFvMesh.H"
#include "addToRunTimeSelectionTable.H"
#include "cloudSupport.H"
#include "sampledSurfaceWorkaround.H"
#include "dynMeshTools.H"

// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

namespace Foam
{
defineTypeNameAndDebug(adaptiveOversetFvMesh, 0);
addToRunTimeSelectionTable
(
dynamicFvMesh,
adaptiveOversetFvMesh,
IOobject
);
}


// * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * * //

Foam::IOobject Foam::adaptiveOversetFvMesh::dynamicMeshDictIOobject
(
const IOobject& io
)
{
// Not registered: dynamicOversetFvMesh reads the same dictionary
return IOobject
(
"dynamicMeshDict",
io.time().constant(),
(io.name() == polyMesh::defaultRegion ? "" : io.name()),
io.db(),
IOobject::MUST_READ_IF_MODIFIED,
IOobject::NO_WRITE,
false
);
}


// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //

void Foam::adaptiveOversetFvMesh::readDict()
{
amrCore_.readDict
(
dynamicMeshDict().optionalSubDict(typeName + "Coeffs")
);
}


void Foam::adaptiveOversetFvMesh::updateMesh(const mapPolyMesh& mpm)
{
// zoneID has to be right before anything rebuilds the stencil, and
// correcting the overset boundary conditions inside fvMesh::updateMesh
// does exactly that
if (oversetHandler* handler = amrCore_.oversetHandlerPtr())
{
handler->updateMesh(mpm);
}

// Motion solvers, mesh objects (including the overset stencil) and all
// registered fields. Runs before amrCore so that flux correction sees
// fields already resized to the new topology
dynamicOversetFvMesh::updateMesh(mpm);

// Flux correction and refiner update
amrCore_.updateMesh(mpm);

// Clouds have to be remapped after fvMesh::updateMesh, see adaptiveFvMesh
cloudSupport::autoMapClouds(*this, mpm);

expireSampledSurfaces(time(), amrCore_.expireSampledSurfacesOnLB());
}


// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //

Foam::adaptiveOversetFvMesh::adaptiveOversetFvMesh(const IOobject& io)
:
dynamicOversetFvMesh(io),
dynamicMeshDict_(dynamicMeshDictIOobject(io)),
amrCore_(*this),
currentTimeIndex_(-1)
{
const dictionary refineDict
(
dynamicMeshDict().optionalSubDict(typeName + "Coeffs")
);

amrCore_.initializeAMR(refineDict);

if (refineDict.getOrDefault("balance", false))
{
amrCore_.initializeLB(refineDict);
}
}


// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //

void Foam::adaptiveOversetFvMesh::mapFields(const mapPolyMesh& mpm)
{
dynamicOversetFvMesh::mapFields(mpm);

// Correct surface fields on introduced internal faces. These get
// created out-of-nothing so get an interpolated value.
meshTools::mapNewInternalFaces<scalar>(*this, mpm.faceMap());
meshTools::mapNewInternalFaces<vector>(*this, mpm.faceMap());
meshTools::mapNewInternalFaces<sphericalTensor>(*this, mpm.faceMap());
meshTools::mapNewInternalFaces<symmTensor>(*this, mpm.faceMap());
meshTools::mapNewInternalFaces<tensor>(*this, mpm.faceMap());
}


bool Foam::adaptiveOversetFvMesh::refine()
{
readDict();

return amrCore_.refine();
}


bool Foam::adaptiveOversetFvMesh::update()
{
bool adapted = false;

if (currentTimeIndex_ < time().timeIndex())
{
currentTimeIndex_ = time().timeIndex();

adapted =
(
amrCore_.refiner().canRefine(true)
|| amrCore_.refiner().canUnrefine(true)
)
&& refine();

if (amrCore_.lbInitialized() && Pstream::parRun() && amrCore_.balance())
{
adapted = true;
}

reduce(adapted, orOp<bool>());
}

// Mesh motion runs after adaptation, not before: balancing resets the
// motion state, and only a subsequent movePoints re-establishes the mesh
// flux the solver asks for. This also refreshes the overset addressing
const bool moved = dynamicOversetFvMesh::update();

if (adapted && !moved)
{
// Topology changed but nothing moved, so rebuild the extended
// addressing and re-interpolate before the solver runs
oversetFvMeshBase::update();
}

return moved || adapted;
}


bool Foam::adaptiveOversetFvMesh::writeObject
(
IOstreamOption streamOpt,
const bool valid
) const
{
// zoneID matching the adapted mesh, so that restarts do not fall back
// to the zoneID of the original mesh
if (amrCore_.oversetHandlerPtr())
{
amrCore_.oversetHandlerPtr()->writeZoneID();
}

return dynamicOversetFvMesh::writeObject(streamOpt, valid);
}


// ************************************************************************* //
Loading
Loading