BACKPORT: error handling for empty surfaces in surfaceFieldValue (#2966)

- for workflows with appearing/disappearing patches (for example)
  can specify that empty surfaces should be ignored or warned about
  instead of raising a FatalError.

  Note that this handling is additional to the regular top-level
  "errors" specification. So specifying 'strict' will only actually
  result in a FatalError if the "errors" does not trap errors.

- "ignore" : any empty surfaces are simply ignored and no
  file output (besides the header).

- "warn" : empty surfaces are warned about a few times (10)
  and the file output contains a NaN entry

- "strict" : corresponds to the default behaviour.
  Throws a FatalError if the surface is empty.
  This error may still be caught by the top-level "errors" handling.
This commit is contained in:
Mark Olesen 2023-08-30 16:45:30 +02:00
parent d3a079b4da
commit 93f4b6aea0
2 changed files with 298 additions and 72 deletions

View File

@ -6,7 +6,7 @@
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2011-2017 OpenFOAM Foundation Copyright (C) 2011-2017 OpenFOAM Foundation
Copyright (C) 2017-2021 OpenCFD Ltd. Copyright (C) 2017-2023 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -36,8 +36,25 @@ License
#include "PatchTools.H" #include "PatchTools.H"
#include "addToRunTimeSelectionTable.H" #include "addToRunTimeSelectionTable.H"
// BACKPORT:
const Foam::Enum
<
Foam::functionObjects::fieldValues::surfaceFieldValue::error_handlerTypes
>
Foam::functionObjects::fieldValues::surfaceFieldValue::error_handlerNames
({
{ error_handlerTypes::DEFAULT, "default" },
{ error_handlerTypes::IGNORE, "ignore" },
{ error_handlerTypes::WARN, "warn" },
{ error_handlerTypes::STRICT, "strict" },
});
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
// Max number of warnings
static constexpr const unsigned maxWarnings = 10u;
namespace Foam namespace Foam
{ {
namespace functionObjects namespace functionObjects
@ -136,39 +153,13 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setFaceZoneFaces()
mesh_.faceZones().indices(selectionNames_) mesh_.faceZones().indices(selectionNames_)
); );
// Total number of faces selected // Total number of faces that could be selected (before patch filtering)
label numFaces = 0; label numFaces = 0;
for (const label zoneId : zoneIds) for (const label zoneId : zoneIds)
{ {
numFaces += mesh_.faceZones()[zoneId].size(); numFaces += mesh_.faceZones()[zoneId].size();
} }
if (zoneIds.empty())
{
FatalErrorInFunction
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_] << '(' << regionName_ << "):" << nl
<< " No matching face zone(s): "
<< flatOutput(selectionNames_) << nl
<< " Known face zones: "
<< flatOutput(mesh_.faceZones().names()) << nl
<< exit(FatalError);
}
// Could also check this
#if 0
if (!returnReduce(bool(numFaces), orOp<bool>()))
{
WarningInFunction
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_] << '(' << regionName_ << "):" << nl
<< " The faceZone specification: "
<< flatOutput(selectionNames_) << nl
<< " resulted in 0 faces" << nl
<< exit(FatalError);
}
#endif
faceId_.resize(numFaces); faceId_.resize(numFaces);
facePatchId_.resize(numFaces); facePatchId_.resize(numFaces);
faceFlip_.resize(numFaces); faceFlip_.resize(numFaces);
@ -231,7 +222,75 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setFaceZoneFaces()
faceId_.resize(numFaces); faceId_.resize(numFaces);
facePatchId_.resize(numFaces); facePatchId_.resize(numFaces);
faceFlip_.resize(numFaces); faceFlip_.resize(numFaces);
nFaces_ = returnReduce(faceId_.size(), sumOp<label>()); nFaces_ = returnReduce(numFaces, sumOp<label>());
if (!nFaces_)
{
// Raise warning or error
refPtr<OSstream> os;
bool fatal = false;
++nWarnings_; // Always increment (even if ignore etc)
switch (emptySurfaceError_)
{
case error_handlerTypes::IGNORE:
{
break;
}
case error_handlerTypes::WARN:
{
if (nWarnings_ <= maxWarnings)
{
os.ref(WarningInFunction);
}
break;
}
// STRICT / DEFAULT
default:
{
os.ref(FatalErrorInFunction);
fatal = true;
break;
}
}
if (os)
{
os.ref()
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_]
<< '(' << regionName_ << "):" << nl;
if (zoneIds.empty())
{
os.ref()
<< " No matching face zones: "
<< flatOutput(selectionNames_) << nl
<< " Known face zones: "
<< flatOutput(mesh_.faceZones().names()) << nl;
}
else
{
os.ref()
<< " The face zones: "
<< flatOutput(selectionNames_) << nl
<< " resulted in 0 faces" << nl;
}
if (fatal)
{
FatalError<< exit(FatalError);
}
else if (nWarnings_ == maxWarnings)
{
os.ref()
<< "... suppressing further warnings." << nl;
}
}
}
} }
@ -307,36 +366,10 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setPatchFaces()
patchIds = std::move(selected); patchIds = std::move(selected);
} }
if (patchIds.empty())
{
FatalErrorInFunction
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_] << '(' << regionName_ << "):" << nl
<< " No matching patch name(s): "
<< flatOutput(selectionNames_) << nl
<< " Known patch names:" << nl
<< mesh_.boundaryMesh().names() << nl
<< exit(FatalError);
}
// Could also check this
#if 0
if (!returnReduce(bool(numFaces), orOp<bool>()))
{
WarningInFunction
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_] << '(' << regionName_ << "):" << nl
<< " The patch specification: "
<< flatOutput(selectionNames_) << nl
<< " resulted in 0 faces" << nl
<< exit(FatalError);
}
#endif
faceId_.resize(numFaces); faceId_.resize(numFaces);
facePatchId_.resize(numFaces); facePatchId_.resize(numFaces);
faceFlip_.resize(numFaces); faceFlip_.resize(numFaces);
nFaces_ = returnReduce(faceId_.size(), sumOp<label>()); nFaces_ = returnReduce(numFaces, sumOp<label>());
numFaces = 0; numFaces = 0;
for (const label patchi : patchIds) for (const label patchi : patchIds)
@ -350,6 +383,74 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setPatchFaces()
numFaces += len; numFaces += len;
} }
if (!nFaces_)
{
// Raise warning or error
refPtr<OSstream> os;
bool fatal = false;
++nWarnings_; // Always increment (even if ignore etc)
switch (emptySurfaceError_)
{
case error_handlerTypes::IGNORE:
{
break;
}
case error_handlerTypes::WARN:
{
if (nWarnings_ <= maxWarnings)
{
os.ref(WarningInFunction);
}
break;
}
// STRICT / DEFAULT
default:
{
os.ref(FatalErrorInFunction);
fatal = true;
break;
}
}
if (os)
{
os.ref()
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_]
<< '(' << regionName_ << "):" << nl;
if (patchIds.empty())
{
os.ref()
<< " No matching patches: "
<< flatOutput(selectionNames_) << nl
<< " Known patch names:" << nl
<< mesh_.boundaryMesh().names() << nl;
}
else
{
os.ref()
<< " The patches: "
<< flatOutput(selectionNames_) << nl
<< " resulted in 0 faces" << nl;
}
if (fatal)
{
FatalError<< exit(FatalError);
}
else if (nWarnings_ == maxWarnings)
{
os.ref()
<< "... suppressing further warnings." << nl;
}
}
}
} }
@ -583,20 +684,30 @@ bool Foam::functionObjects::fieldValues::surfaceFieldValue::update()
return false; return false;
} }
// Reset some values
totalArea_ = 0;
nFaces_ = 0;
bool checkEmptyFaces = true;
switch (regionType_) switch (regionType_)
{ {
case stFaceZone: case stFaceZone:
{ {
// Raises warning or error internally, don't check again
setFaceZoneFaces(); setFaceZoneFaces();
checkEmptyFaces = false;
break; break;
} }
case stPatch: case stPatch:
{ {
// Raises warning or error internally, don't check again
setPatchFaces(); setPatchFaces();
checkEmptyFaces = false;
break; break;
} }
case stObject: case stObject:
{ {
// TBD: special handling of cast errors?
const polySurface& s = dynamicCast<const polySurface>(obr()); const polySurface& s = dynamicCast<const polySurface>(obr());
nFaces_ = returnReduce(s.size(), sumOp<label>()); nFaces_ = returnReduce(s.size(), sumOp<label>());
break; break;
@ -610,23 +721,76 @@ bool Foam::functionObjects::fieldValues::surfaceFieldValue::update()
// Compiler warning if we forgot an enumeration // Compiler warning if we forgot an enumeration
} }
if (nFaces_ == 0) if (nFaces_)
{ {
FatalErrorInFunction // Appears to be successful
<< type() << ' ' << name() << ": " needsUpdate_ = false;
<< regionTypeNames_[regionType_] << '(' << regionName_ << "):" << nl totalArea_ = totalArea(); // Update the area
<< " Region has no faces" << exit(FatalError); nWarnings_ = 0u; // Reset the warnings counter
}
else if (checkEmptyFaces)
{
// Raise warning or error
refPtr<OSstream> os;
bool fatal = false;
++nWarnings_; // Always increment (even if ignore etc)
switch (emptySurfaceError_)
{
case error_handlerTypes::IGNORE:
{
break;
} }
totalArea_ = totalArea(); case error_handlerTypes::WARN:
{
if (nWarnings_ <= maxWarnings)
{
os.ref(WarningInFunction);
}
break;
}
// STRICT / DEFAULT
default:
{
os.ref(FatalErrorInFunction);
fatal = true;
break;
}
}
if (os)
{
os.ref()
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_]
<< '(' << regionName_ << "):" << nl
<< " Region has no faces" << endl;
if (fatal)
{
FatalError<< exit(FatalError);
}
else if (nWarnings_ == maxWarnings)
{
os.ref()
<< "... suppressing further warnings." << nl;
}
}
}
Log << " total faces = " << nFaces_ << nl Log << " total faces = " << nFaces_ << nl
<< " total area = " << totalArea_ << nl << " total area = " << totalArea_ << nl
<< endl; << endl;
// Emit file header on success or change of state
if (nWarnings_ <= 1)
{
writeFileHeader(file()); writeFileHeader(file());
}
needsUpdate_ = false;
return true; return true;
} }
@ -919,10 +1083,12 @@ Foam::functionObjects::fieldValues::surfaceFieldValue::surfaceFieldValue
), ),
needsUpdate_(true), needsUpdate_(true),
writeArea_(false), writeArea_(false),
emptySurfaceError_(error_handlerTypes::DEFAULT),
selectionNames_(), selectionNames_(),
weightFieldNames_(), weightFieldNames_(),
totalArea_(0), totalArea_(0),
nFaces_(0), nFaces_(0),
nWarnings_(0),
faceId_(), faceId_(),
facePatchId_(), facePatchId_(),
faceFlip_() faceFlip_()
@ -953,10 +1119,12 @@ Foam::functionObjects::fieldValues::surfaceFieldValue::surfaceFieldValue
), ),
needsUpdate_(true), needsUpdate_(true),
writeArea_(false), writeArea_(false),
emptySurfaceError_(error_handlerTypes::DEFAULT),
selectionNames_(), selectionNames_(),
weightFieldNames_(), weightFieldNames_(),
totalArea_(0), totalArea_(0),
nFaces_(0), nFaces_(0),
nWarnings_(0),
faceId_(), faceId_(),
facePatchId_(), facePatchId_(),
faceFlip_() faceFlip_()
@ -976,10 +1144,19 @@ bool Foam::functionObjects::fieldValues::surfaceFieldValue::read
needsUpdate_ = true; needsUpdate_ = true;
writeArea_ = dict.getOrDefault("writeArea", false); writeArea_ = dict.getOrDefault("writeArea", false);
emptySurfaceError_ = error_handlerNames.getOrDefault
(
"empty-surface",
dict,
error_handlerTypes::DEFAULT,
true // Failsafe behaviour
);
weightFieldNames_.clear(); weightFieldNames_.clear();
totalArea_ = 0; totalArea_ = 0;
nFaces_ = 0; nFaces_ = 0;
nWarnings_ = 0;
faceId_.clear(); faceId_.clear();
facePatchId_.clear(); facePatchId_.clear();
faceFlip_.clear(); faceFlip_.clear();
@ -1159,12 +1336,39 @@ bool Foam::functionObjects::fieldValues::surfaceFieldValue::write()
writeCurrentTime(file()); writeCurrentTime(file());
} }
// Handle ignore/warn about empty-surface
if (!nFaces_)
{
totalArea_ = 0; // Update the area (safety)
if (operation_ != opNone)
{
if (emptySurfaceError_ == error_handlerTypes::WARN)
{
if (writeArea_) if (writeArea_)
{ {
Log << " total area = " << totalArea_ << endl;
file() << tab << totalArea_;
}
file() << tab << "NaN";
Log << endl;
}
file() << endl;
}
// Early exit on error
return true;
}
if (writeArea_)
{
// Update the area
totalArea_ = totalArea(); totalArea_ = totalArea();
Log << " total area = " << totalArea_ << endl; Log << " total area = " << totalArea_ << endl;
if (operation_ != opNone && Pstream::master()) if (operation_ != opNone && UPstream::master())
{ {
file() << tab << totalArea_; file() << tab << totalArea_;
} }

View File

@ -6,7 +6,7 @@
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2011-2017 OpenFOAM Foundation Copyright (C) 2011-2017 OpenFOAM Foundation
Copyright (C) 2015-2020 OpenCFD Ltd. Copyright (C) 2015-2023 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -88,6 +88,7 @@ Usage
scaleFactor 1.0; scaleFactor 1.0;
writeArea false; writeArea false;
surfaceFormat none; surfaceFormat none;
empty-surface warn; // default | warn | ignore | strict
// Optional (inherited) entries // Optional (inherited) entries
... ...
@ -111,6 +112,7 @@ Usage
writeArea | Write the surface area | bool | no | false writeArea | Write the surface area | bool | no | false
surfaceFormat | Output value format | word <!-- surfaceFormat | Output value format | word <!--
--> | conditional on writeFields | none --> | conditional on writeFields | none
empty-surface | Error handling for empty surfaces | enum | no | default
\endtable \endtable
The inherited entries are elaborated in: The inherited entries are elaborated in:
@ -177,12 +179,11 @@ Note
Instead specify the regionType 'functionObjectSurface' and provide Instead specify the regionType 'functionObjectSurface' and provide
the name. the name.
- Using \c sampledSurface: - Using \c sampledSurface:
- not available for surface fields - surface fields only supported by some surfaces
- if interpolate=true they use \c interpolationCellPoint - default uses sampleScheme \c cell
otherwise they use cell values - each face in \c sampledSurface is logically only in one cell
- each triangle in \c sampledSurface is logically only in one cell so sampling will be wrong when they are larger than cells.
so interpolation will be wrong when triangles are larger than This can only happen for sampling on a \c triSurfaceMesh
cells. This can only happen for sampling on a \c triSurfaceMesh
- take care when using isoSurfaces - these might have duplicate - take care when using isoSurfaces - these might have duplicate
triangles and so integration might be wrong triangles and so integration might be wrong
@ -382,6 +383,21 @@ private:
scalar totalArea() const; scalar totalArea() const;
// BACKPORT:
//- Handling of errors. The exact handling depends on the local context.
enum class error_handlerTypes : char
{
DEFAULT = 0, //!< Default behaviour (local meaning)
IGNORE, //!< Ignore on errors/problems
WARN, //!< Warn on errors/problems
STRICT //!< Fatal on errors/problems
};
//- Names of the error handler types
static const Enum<error_handlerTypes> error_handlerNames;
protected: protected:
// Protected Data // Protected Data
@ -401,6 +417,9 @@ protected:
//- Optionally write the area of the surfaceFieldValue //- Optionally write the area of the surfaceFieldValue
bool writeArea_; bool writeArea_;
//- Handling of empty surfaces (nFaces = 0). Default is Fatal.
error_handlerTypes emptySurfaceError_; // BACKPORT
//- Extended selections //- Extended selections
wordRes selectionNames_; wordRes selectionNames_;
@ -413,6 +432,9 @@ protected:
//- Global number of faces //- Global number of faces
label nFaces_; label nFaces_;
//- Number of warnings emitted since the last valid update
unsigned nWarnings_;
// If operating on mesh faces (faceZone, patch) // If operating on mesh faces (faceZone, patch)