ENH: 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:08:30 +02:00
committed by Andrew Heather
parent ff2abdf1f0
commit 2e3f0811a0
2 changed files with 267 additions and 69 deletions

View File

@ -37,6 +37,9 @@ License
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
// Max number of warnings
static constexpr const unsigned maxWarnings = 10u;
namespace Foam namespace Foam
{ {
namespace functionObjects namespace functionObjects
@ -135,39 +138,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 (!returnReduceOr(numFaces))
{
WarningInFunction
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_] << '(' << regionName_ << "):" << nl
<< " The faceZone specification: "
<< flatOutput(selectionNames_) << nl
<< " resulted in 0 faces" << nl
<< exit(FatalError);
}
#endif
faceId_.resize_nocopy(numFaces); faceId_.resize_nocopy(numFaces);
facePatchId_.resize_nocopy(numFaces); facePatchId_.resize_nocopy(numFaces);
faceFlip_.resize_nocopy(numFaces); faceFlip_.resize_nocopy(numFaces);
@ -223,7 +200,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;
}
}
}
} }
@ -283,7 +328,7 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setPatchFaces()
nGood = 0; nGood = 0;
for (const label patchi : selected) for (const label patchi : selected)
{ {
if (!bad.found(patchi)) if (!bad.contains(patchi))
{ {
patchIds[nGood] = patchi; patchIds[nGood] = patchi;
++nGood; ++nGood;
@ -295,36 +340,10 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setPatchFaces()
patchIds = std::move(selected); patchIds = std::move(selected);
} }
if (patchIds.empty()) faceId_.resize_nocopy(numFaces);
{ facePatchId_.resize_nocopy(numFaces);
FatalErrorInFunction faceFlip_.resize_nocopy(numFaces);
<< type() << ' ' << name() << ": " nFaces_ = returnReduce(numFaces, sumOp<label>());
<< 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 (!returnReduceOr(numFaces))
{
WarningInFunction
<< type() << ' ' << name() << ": "
<< regionTypeNames_[regionType_] << '(' << regionName_ << "):" << nl
<< " The patch specification: "
<< flatOutput(selectionNames_) << nl
<< " resulted in 0 faces" << nl
<< exit(FatalError);
}
#endif
faceId_.resize(numFaces);
facePatchId_.resize(numFaces);
faceFlip_.resize(numFaces);
nFaces_ = returnReduce(faceId_.size(), sumOp<label>());
numFaces = 0; numFaces = 0;
for (const label patchi : patchIds) for (const label patchi : patchIds)
@ -338,6 +357,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;
}
}
}
} }
@ -511,20 +598,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 auto& s = refCast<const polySurface>(obr()); const auto& s = refCast<const polySurface>(obr());
nFaces_ = returnReduce(s.size(), sumOp<label>()); nFaces_ = returnReduce(s.size(), sumOp<label>());
break; break;
@ -538,23 +635,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;
totalArea_ = totalArea(); ++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
<< " 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;
writeFileHeader(file()); // Emit file header on success or change of state
if (nWarnings_ <= 1)
{
writeFileHeader(file());
}
needsUpdate_ = false;
return true; return true;
} }
@ -931,10 +1081,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_()
@ -965,10 +1117,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_()
@ -995,12 +1149,21 @@ 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();
// future? // future?
// sampleFaceScheme_ = dict.getOrDefault<word>("sampleScheme", "cell"); // sampleFaceScheme_ = dict.getOrDefault<word>("sampleScheme", "cell");
totalArea_ = 0; totalArea_ = 0;
nFaces_ = 0; nFaces_ = 0;
nWarnings_ = 0;
faceId_.clear(); faceId_.clear();
facePatchId_.clear(); facePatchId_.clear();
faceFlip_.clear(); faceFlip_.clear();
@ -1182,12 +1345,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_)
{
Log << " total area = " << totalArea_ << endl;
file() << tab << totalArea_;
}
file() << tab << "NaN";
Log << endl;
}
file() << endl;
}
// Early exit on error
return true;
}
if (writeArea_) 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:
@ -400,6 +402,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_;
//- Extended selections //- Extended selections
wordRes selectionNames_; wordRes selectionNames_;
@ -412,6 +417,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)