diff --git a/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.C b/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.C index dbc4f0c31506fc33e64fa3e4010eafd2f50054ec..c02cd44b2ff407ae444348e81d19565ee5b489a1 100644 --- a/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.C +++ b/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2017 OpenFOAM Foundation - Copyright (C) 2017-2021 OpenCFD Ltd. + Copyright (C) 2017-2023 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -36,8 +36,25 @@ License #include "PatchTools.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 * * * * * * * * * * * * * // +// Max number of warnings +static constexpr const unsigned maxWarnings = 10u; + namespace Foam { namespace functionObjects @@ -136,39 +153,13 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setFaceZoneFaces() mesh_.faceZones().indices(selectionNames_) ); - // Total number of faces selected + // Total number of faces that could be selected (before patch filtering) label numFaces = 0; for (const label zoneId : zoneIds) { 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); facePatchId_.resize(numFaces); faceFlip_.resize(numFaces); @@ -231,7 +222,75 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setFaceZoneFaces() faceId_.resize(numFaces); facePatchId_.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); } - 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); facePatchId_.resize(numFaces); faceFlip_.resize(numFaces); - nFaces_ = returnReduce(faceId_.size(), sumOp<label>()); + nFaces_ = returnReduce(numFaces, sumOp<label>()); numFaces = 0; for (const label patchi : patchIds) @@ -350,6 +383,74 @@ void Foam::functionObjects::fieldValues::surfaceFieldValue::setPatchFaces() 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; } + // Reset some values + totalArea_ = 0; + nFaces_ = 0; + bool checkEmptyFaces = true; + switch (regionType_) { case stFaceZone: { + // Raises warning or error internally, don't check again setFaceZoneFaces(); + checkEmptyFaces = false; break; } case stPatch: { + // Raises warning or error internally, don't check again setPatchFaces(); + checkEmptyFaces = false; break; } case stObject: { + // TBD: special handling of cast errors? const polySurface& s = dynamicCast<const polySurface>(obr()); nFaces_ = returnReduce(s.size(), sumOp<label>()); break; @@ -610,23 +721,76 @@ bool Foam::functionObjects::fieldValues::surfaceFieldValue::update() // Compiler warning if we forgot an enumeration } - if (nFaces_ == 0) + if (nFaces_) { - FatalErrorInFunction - << type() << ' ' << name() << ": " - << regionTypeNames_[regionType_] << '(' << regionName_ << "):" << nl - << " Region has no faces" << exit(FatalError); + // Appears to be successful + needsUpdate_ = false; + totalArea_ = totalArea(); // Update the area + 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 << " total area = " << totalArea_ << nl << endl; - writeFileHeader(file()); + // Emit file header on success or change of state + if (nWarnings_ <= 1) + { + writeFileHeader(file()); + } - needsUpdate_ = false; return true; } @@ -919,10 +1083,12 @@ Foam::functionObjects::fieldValues::surfaceFieldValue::surfaceFieldValue ), needsUpdate_(true), writeArea_(false), + emptySurfaceError_(error_handlerTypes::DEFAULT), selectionNames_(), weightFieldNames_(), totalArea_(0), nFaces_(0), + nWarnings_(0), faceId_(), facePatchId_(), faceFlip_() @@ -953,10 +1119,12 @@ Foam::functionObjects::fieldValues::surfaceFieldValue::surfaceFieldValue ), needsUpdate_(true), writeArea_(false), + emptySurfaceError_(error_handlerTypes::DEFAULT), selectionNames_(), weightFieldNames_(), totalArea_(0), nFaces_(0), + nWarnings_(0), faceId_(), facePatchId_(), faceFlip_() @@ -976,10 +1144,19 @@ bool Foam::functionObjects::fieldValues::surfaceFieldValue::read needsUpdate_ = true; writeArea_ = dict.getOrDefault("writeArea", false); + emptySurfaceError_ = error_handlerNames.getOrDefault + ( + "empty-surface", + dict, + error_handlerTypes::DEFAULT, + true // Failsafe behaviour + ); + weightFieldNames_.clear(); totalArea_ = 0; nFaces_ = 0; + nWarnings_ = 0; faceId_.clear(); facePatchId_.clear(); faceFlip_.clear(); @@ -1159,12 +1336,39 @@ bool Foam::functionObjects::fieldValues::surfaceFieldValue::write() 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_) { + // Update the area totalArea_ = totalArea(); Log << " total area = " << totalArea_ << endl; - if (operation_ != opNone && Pstream::master()) + if (operation_ != opNone && UPstream::master()) { file() << tab << totalArea_; } diff --git a/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.H b/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.H index 1933802913d8b72bae544fd8f5e549351c03c7ae..e5c6ecfaf317388a1032a037cd6480b8cb5419f7 100644 --- a/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.H +++ b/src/functionObjects/field/fieldValues/surfaceFieldValue/surfaceFieldValue.H @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2017 OpenFOAM Foundation - Copyright (C) 2015-2020 OpenCFD Ltd. + Copyright (C) 2015-2023 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -88,6 +88,7 @@ Usage scaleFactor 1.0; writeArea false; surfaceFormat none; + empty-surface warn; // default | warn | ignore | strict // Optional (inherited) entries ... @@ -111,6 +112,7 @@ Usage writeArea | Write the surface area | bool | no | false surfaceFormat | Output value format | word <!-- --> | conditional on writeFields | none + empty-surface | Error handling for empty surfaces | enum | no | default \endtable The inherited entries are elaborated in: @@ -177,12 +179,11 @@ Note Instead specify the regionType 'functionObjectSurface' and provide the name. - Using \c sampledSurface: - - not available for surface fields - - if interpolate=true they use \c interpolationCellPoint - otherwise they use cell values - - each triangle in \c sampledSurface is logically only in one cell - so interpolation will be wrong when triangles are larger than - cells. This can only happen for sampling on a \c triSurfaceMesh + - surface fields only supported by some surfaces + - default uses sampleScheme \c cell + - each face in \c sampledSurface is logically only in one cell + so sampling will be wrong when they are larger than cells. + This can only happen for sampling on a \c triSurfaceMesh - take care when using isoSurfaces - these might have duplicate triangles and so integration might be wrong @@ -382,6 +383,21 @@ private: 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 Data @@ -401,6 +417,9 @@ protected: //- Optionally write the area of the surfaceFieldValue bool writeArea_; + //- Handling of empty surfaces (nFaces = 0). Default is Fatal. + error_handlerTypes emptySurfaceError_; // BACKPORT + //- Extended selections wordRes selectionNames_; @@ -413,6 +432,9 @@ protected: //- Global number of faces label nFaces_; + //- Number of warnings emitted since the last valid update + unsigned nWarnings_; + // If operating on mesh faces (faceZone, patch)