diff --git a/src/functionObjects/field/Make/files b/src/functionObjects/field/Make/files
index fe9eec3283..bd6b60b373 100644
--- a/src/functionObjects/field/Make/files
+++ b/src/functionObjects/field/Make/files
@@ -154,4 +154,6 @@ resolutionIndex/resolutionIndexModels/CelikEtaIndex/CelikEtaIndex.C
age/age.C
comfort/comfort.C
+fieldStatistics/fieldStatistics.cxx
+
LIB = $(FOAM_LIBBIN)/libfieldFunctionObjects
diff --git a/src/functionObjects/field/fieldStatistics/fieldStatistics.H b/src/functionObjects/field/fieldStatistics/fieldStatistics.H
new file mode 100644
index 0000000000..b00cd57fd5
--- /dev/null
+++ b/src/functionObjects/field/fieldStatistics/fieldStatistics.H
@@ -0,0 +1,367 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2025 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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::functionObjects::fieldStatistics
+
+Group
+ grpFieldFunctionObjects
+
+Description
+ Calculates various statistics of the specified fields.
+
+ Operands:
+ \table
+ Operand | Type | Location
+ input | vol\Field(s) | Registry
+ output file | dat | \/postProcessing/\/\/\
+ output field | - | -
+ \endtable
+
+ where \c \=Scalar/Vector/SphericalTensor/SymmTensor/Tensor.
+
+Usage
+ Minimal example by using \c system/controlDict.functions:
+ \verbatim
+ fieldStatistics1
+ {
+ // Mandatory entries
+ type fieldStatistics;
+ libs (fieldFunctionObjects);
+ fields ();
+ statistics ();
+
+ // Optional entries
+ mode ;
+ mean ;
+ extrema ;
+ internal ;
+
+ // Inherited entries
+ ...
+ }
+ \endverbatim
+
+ where the entries mean:
+ \table
+ Property | Description | Type | Reqd | Deflt
+ type | Type name: fieldStatistics | word | yes | -
+ libs | Library name: fieldFunctionObjects | word | yes | -
+ fields | List of operand fields | wordList | yes | -
+ statistics | List of operand statistics | wordList | yes | -
+ mode | Output format of the statistical results | word | no | magnitude
+ mean | Type of the mean operation | word | no | arithmetic
+ internal | Flag to use internal fields only in computing statistics | bool | no | false
+ extrema | Flag to enable extrema data calculations | bool | no | false
+ \endtable
+
+ Available statistics of the operand field through the \c statistics entry:
+ \verbatim
+ min | Minimum value
+ max | Maximum value
+ mean | Arithmetic mean value (optionally volume-weighted)
+ variance | Sample variance value (unbiased)
+ \endverbatim
+
+ Options for the \c mode entry:
+ \verbatim
+ magnitude | Output statistics magnitude-wise
+ component | Output statistics separately for each component
+ \endverbatim
+
+ Options for the \c mean entry:
+ \verbatim
+ arithmetic | Arithmetic mean (average)
+ volumetric | Volume-weighted arithmetic mean
+ \endverbatim
+
+ The inherited entries are elaborated in:
+ - \link functionObject.H \endlink
+ - \link writeFile.H \endlink
+
+SourceFiles
+ fieldStatistics.cxx
+ fieldStatisticsImpl.cxx
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_fieldStatistics_H
+#define functionObjects_fieldStatistics_H
+
+#include "fvMeshFunctionObject.H"
+#include "writeFile.H"
+#include "volFieldSelection.H"
+#include
+#include
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+
+/*---------------------------------------------------------------------------*\
+ Class fieldStatistics Declaration
+\*---------------------------------------------------------------------------*/
+
+class fieldStatistics
+:
+ public fvMeshFunctionObject,
+ public writeFile
+{
+ // Private Enumerations
+
+ //- Options for the output format of the statistical results
+ enum class modeType : char
+ {
+ MAG = 0, //!< "Output statistics magnitude-wise"
+ CMPT //!< "Output statistics separately for each component"
+ };
+
+ //- Names for modeType
+ static const Enum modeTypeNames_;
+
+ //- Options for the mean type of the specified fields
+ enum class meanType : char
+ {
+ ARITHMETIC = 0, //!< "Arithmetic mean (average)"
+ VOLUMETRIC //!< "Volume-weighted arithmetic mean"
+ };
+
+ //- Names for meanType
+ static const Enum meanTypeNames_;
+
+ //- Options for the type of statistics calculation
+ enum class calcType : char
+ {
+ UNKNOWN = 0, //!< placeholder
+ MIN, //!< min value
+ MAX, //!< max value
+ MEAN, //!< mean value
+ VARIANCE //!< variance
+ };
+
+ //- Names for calcType
+ static const Enum calcTypeNames_;
+
+
+ // Private Classes
+
+ //- Type-safe union for input field types
+ using variantInput = std::variant
+ <
+ scalarField,
+ vectorField,
+ sphericalTensorField,
+ symmTensorField,
+ tensorField
+ >;
+
+ //- Type-safe union for output data types
+ using variantOutput = std::variant
+ <
+ scalar,
+ vector,
+ sphericalTensor,
+ symmTensor,
+ tensor
+ >;
+
+ //- Class to encapsulate information about specified statistic
+ struct statistic
+ {
+ //- Name of the statistic
+ word name_;
+
+ //- Returns the value of the specified statistic
+ std::function calc;
+ };
+
+ //- Class to encapsulate the data about minima and maxima
+ struct extremaData
+ {
+ //- Value of the extremum
+ variantOutput value_;
+
+ //- Processor index of the extremum
+ label procID_;
+
+ //- Cell index of the extremum
+ label cellID_;
+
+ //- Position (cell or face centre) of the extremum
+ point position_;
+ };
+
+
+ // Private Data
+
+ //- Flag to use internal fields only in computing statistics
+ bool internal_;
+
+ //- Flag to enable extrema data calculations
+ bool extrema_;
+
+ //- Output-format mode - only applicable for tensor ranks > 0
+ modeType mode_;
+
+ //- Type of the mean of the specified fields
+ meanType mean_;
+
+ //- Operand fields on which statistics are computed
+ volFieldSelection fieldSet_;
+
+ //- List of file pointers; one file per field
+ HashPtrTable filePtrs_;
+
+ //- List of file pointers for extrema data; one file per field
+ HashPtrTable extremaFilePtrs_;
+
+ //- Hash table containing all specified statistics
+ HashTable statistics_;
+
+ //- Hash table containing all statistical results per field
+ HashTable> results_;
+
+ //- Hash table containing the results of the extrema per field
+ HashTable> extremaResults_;
+
+
+ // Private Member Functions
+
+ //- Return the statistic container
+ statistic createStatistic(const word& statName, const modeType mode);
+
+ //- Compute the specified statistics of a given field
+ template
+ bool calcStat(const word& fieldName);
+
+
+ // Central tendency statistics
+
+ //- Return the arithmetic mean of the given input field
+ template
+ T calcMean(const Field& field) const;
+
+
+ // Dispersion statistics
+
+ //- Return the minimum value of the given input field
+ // Store the processor index, cell index and location of the minimum
+ template
+ T calcMin(const Field& field) const;
+
+ //- Return the maximum value of the given input field
+ // Store the processor index, cell index and location of the maximum
+ template
+ T calcMax(const Field& field) const;
+
+ //- Return the sample variance of the given input field
+ template
+ T calcVariance(const Field& field) const;
+
+ //- Return a combined field: internal + flattened boundary
+ template
+ tmp>
+ flatten(const GeoField& field) const;
+
+ //- Return the extrema data of the specified field
+ template
+ Pair calcExtremaData(const GeoField& field) const;
+
+ //- Output the file header information
+ void writeFileHeader(Ostream& os, const word& fieldName);
+
+ //- Write the statistical data to the specified file
+ void writeStatData();
+
+ //- Write the statistical data to the standard stream
+ void logStatData();
+
+ //- Output the extrema-data file header information
+ void writeExtremaFileHeader(Ostream& os, const word& fieldName);
+
+ //- Write extrema data to the specified file
+ void writeExtremaData();
+
+ //- Write extrema data to the standard stream
+ void logExtremaData();
+
+
+public:
+
+ //- Runtime type information
+ TypeName("fieldStatistics");
+
+
+ // Generated Methods
+
+ //- No copy construct
+ fieldStatistics(const fieldStatistics&) = delete;
+
+ //- No copy assignment
+ void operator=(const fieldStatistics&) = delete;
+
+
+ // Constructors
+
+ //- Construct from name, Time and dictionary
+ fieldStatistics
+ (
+ const word& name,
+ const Time& runTime,
+ const dictionary& dict
+ );
+
+
+ //- Destructor
+ virtual ~fieldStatistics() = default;
+
+
+ // Member Functions
+
+ //- Read function object settings
+ virtual bool read(const dictionary&);
+
+ //- Execute function object
+ virtual bool execute();
+
+ //- Write function object results
+ virtual bool write();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/fieldStatistics/fieldStatistics.cxx b/src/functionObjects/field/fieldStatistics/fieldStatistics.cxx
new file mode 100644
index 0000000000..259a63d3fb
--- /dev/null
+++ b/src/functionObjects/field/fieldStatistics/fieldStatistics.cxx
@@ -0,0 +1,549 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2025 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 .
+
+\*---------------------------------------------------------------------------*/
+
+#include "fieldStatistics.H"
+#include "fieldTypes.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+ defineTypeNameAndDebug(fieldStatistics, 0);
+ addToRunTimeSelectionTable(functionObject, fieldStatistics, dictionary);
+}
+}
+
+const Foam::Enum
+<
+ Foam::functionObjects::fieldStatistics::modeType
+>
+Foam::functionObjects::fieldStatistics::modeTypeNames_
+({
+ { modeType::MAG, "magnitude" },
+ { modeType::CMPT, "component" },
+});
+
+const Foam::Enum
+<
+ Foam::functionObjects::fieldStatistics::meanType
+>
+Foam::functionObjects::fieldStatistics::meanTypeNames_
+({
+ { meanType::ARITHMETIC, "arithmetic" },
+ { meanType::VOLUMETRIC, "volumetric" },
+});
+
+const Foam::Enum
+<
+ Foam::functionObjects::fieldStatistics::calcType
+>
+Foam::functionObjects::fieldStatistics::calcTypeNames_
+({
+ // UNKNOWN is not enumerated
+ { calcType::MIN, "min" },
+ { calcType::MAX, "max" },
+ { calcType::MEAN, "mean" },
+ { calcType::VARIANCE, "variance" },
+});
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Implementation
+#include "fieldStatisticsImpl.cxx"
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+Foam::functionObjects::fieldStatistics::statistic
+Foam::functionObjects::fieldStatistics::createStatistic
+(
+ const word& statName,
+ const modeType mode
+)
+{
+ statistic stat;
+ stat.name_ = statName;
+
+ const auto statType(calcTypeNames_.lookup(statName, calcType::UNKNOWN));
+
+ stat.calc = [this, statType, mode](variantInput input) -> variantOutput
+ {
+ return std::visit
+ (
+ [this, statType, mode](auto&& arg) -> variantOutput
+ {
+ using T = std::decay_t;
+ using value_type = typename T::value_type;
+
+ switch (statType)
+ {
+ case calcType::MIN :
+ {
+ if (mode == modeType::MAG)
+ return calcMin(mag(arg));
+ else
+ return calcMin(arg);
+ }
+ case calcType::MAX :
+ {
+ if (mode == modeType::MAG)
+ return calcMax(mag(arg));
+ else
+ return calcMax(arg);
+ }
+ case calcType::MEAN :
+ {
+ if (mode == modeType::MAG)
+ return calcMean(mag(arg));
+ else
+ return calcMean(arg);
+ }
+ case calcType::VARIANCE :
+ {
+ if (mode == modeType::MAG)
+ return calcVariance(mag(arg));
+ else
+ return calcVariance(arg);
+ }
+ default :
+ {
+ // Default case (for compiler)
+ return scalar(0);
+ }
+ }
+ },
+ input
+ );
+ };
+
+ return stat;
+}
+
+
+void Foam::functionObjects::fieldStatistics::writeFileHeader
+(
+ Ostream& os,
+ const word& fieldName
+)
+{
+ writeHeader(os, word("Field Statistics: " + fieldName));
+ writeCommented(os, "Time");
+
+ // Number of input statistics (i.e., statistics_) should be the same with
+ // that of output statistics (i.e., results_). However, for consistency,
+ // the output file columns are based on output statistics.
+ const auto& result = results_(fieldName);
+
+ for (const auto& iter : result.csorted())
+ {
+ const word& name = iter.key();
+ writeTabbed(os, name);
+ }
+ os << endl;
+}
+
+
+void Foam::functionObjects::fieldStatistics::writeStatData()
+{
+ for (const word& fieldName : fieldSet_.selectionNames())
+ {
+ const auto& results = results_(fieldName);
+
+ if (!results.size()) break;
+
+ OFstream& file = *filePtrs_(fieldName);
+
+ writeCurrentTime(file);
+
+ for (const auto& iter : results.csorted())
+ {
+ const variantOutput& value = iter.val();
+
+ std::visit
+ (
+ [&file](const auto& v)
+ {
+ if constexpr
+ (
+ is_vectorspace_v>
+ )
+ {
+ for (const auto& val : v) file<< token::TAB << val;
+ }
+ else
+ {
+ file<< token::TAB << v;
+ }
+ },
+ value
+ );
+ }
+ file<< nl;
+ }
+}
+
+
+void Foam::functionObjects::fieldStatistics::logStatData()
+{
+ for (const word& fieldName : fieldSet_.selectionNames())
+ {
+ const auto& results = results_(fieldName);
+
+ if (!results.size()) break;
+
+ const word outputName
+ (
+ (mode_ == modeType::MAG)
+ ? word("mag(" + fieldName + ")")
+ : fieldName
+ );
+
+ Info<< nl << " Field " << outputName << nl;
+
+ for (const auto& iter : results.csorted())
+ {
+ const word& name = iter.key();
+ const variantOutput& value = iter.val();
+
+ Info<< " " << name;
+ std::visit
+ (
+ [](const auto& v)
+ {
+ if constexpr
+ (
+ is_vectorspace_v>
+ )
+ {
+ for (const auto& val : v) Info<< ' ' << val;
+ }
+ else
+ {
+ Info<< ' ' << v;
+ }
+ },
+ value
+ );
+ Info<< nl;
+ }
+ }
+ Info<< endl;
+}
+
+
+void Foam::functionObjects::fieldStatistics::writeExtremaFileHeader
+(
+ Ostream& os,
+ const word& fieldName
+)
+{
+ writeHeader(os, word("Field Extrema Data: " + fieldName));
+ writeCommented(os, "Time");
+ writeTabbed(os, "min");
+ writeTabbed(os, "min_procID");
+ writeTabbed(os, "min_cellID");
+ writeTabbed(os, "min_position");
+ writeTabbed(os, "max");
+ writeTabbed(os, "max_procID");
+ writeTabbed(os, "max_cellID");
+ writeTabbed(os, "max_position");
+ os << endl;
+}
+
+
+void Foam::functionObjects::fieldStatistics::writeExtremaData()
+{
+ for (const word& fieldName : fieldSet_.selectionNames())
+ {
+ const auto& min = extremaResults_(fieldName).first();
+ const auto& max = extremaResults_(fieldName).second();
+
+ OFstream& file = *extremaFilePtrs_(fieldName);
+
+ writeCurrentTime(file);
+
+ file<< token::TAB;
+
+ std::visit([&file](const auto& v){ file<< v; }, min.value_);
+
+ file<< token::TAB
+ << min.procID_ << token::TAB
+ << min.cellID_ << token::TAB
+ << min.position_ << token::TAB;
+
+ std::visit([&file](const auto& v){ file<< v; }, max.value_);
+
+ file<< token::TAB
+ << max.procID_ << token::TAB
+ << max.cellID_ << token::TAB
+ << max.position_ << nl;
+ }
+}
+
+
+void Foam::functionObjects::fieldStatistics::logExtremaData()
+{
+ for (const word& fieldName : fieldSet_.selectionNames())
+ {
+ const auto& min = extremaResults_(fieldName).first();
+ const auto& max = extremaResults_(fieldName).second();
+
+ const word outputName
+ (
+ (mode_ == modeType::MAG)
+ ? word("mag(" + fieldName + ")")
+ : fieldName
+ );
+
+ std::visit
+ (
+ [outputName](const auto& v)
+ {
+ Info<< " min(" << outputName << ") = " << v;
+ },
+ min.value_
+ );
+
+ Info<< " in cell " << min.cellID_
+ << " at location " << min.position_
+ << " on processor " << min.procID_;
+
+ std::visit
+ (
+ [outputName](const auto& v)
+ {
+ Info<< nl << " max(" << outputName << ") = " << v;
+ },
+ max.value_
+ );
+
+ Info<< " in cell " << max.cellID_
+ << " at location " << max.position_
+ << " on processor " << max.procID_ << endl;
+ }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::functionObjects::fieldStatistics::fieldStatistics
+(
+ const word& name,
+ const Time& runTime,
+ const dictionary& dict
+)
+:
+ fvMeshFunctionObject(name, runTime, dict),
+ writeFile(mesh_, name, typeName, dict),
+ internal_(false),
+ extrema_(false),
+ mode_(modeType::MAG),
+ mean_(meanType::ARITHMETIC),
+ fieldSet_(mesh_)
+{
+ read(dict);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::fieldStatistics::read(const dictionary& dict)
+{
+ if (!(fvMeshFunctionObject::read(dict) && writeFile::read(dict)))
+ {
+ return false;
+ }
+
+ internal_ = dict.getOrDefault("internal", false);
+ extrema_ = dict.getOrDefault("extrema", false);
+
+ mode_ = modeTypeNames_.getOrDefault("mode", dict, modeType::MAG);
+ mean_ = meanTypeNames_.getOrDefault("mean", dict, meanType::ARITHMETIC);
+
+ // Reset and reprepare the input field names
+ fieldSet_.clear();
+ fieldSet_.read(dict);
+
+ // Reset and reprepare the input statistics
+ statistics_.clear();
+
+ const wordHashSet statNames(dict.get("statistics"));
+ // NOTE: could also filter out bad/unknown types here
+
+ for (const word& statName : statNames.sortedToc())
+ {
+ statistics_.insert(statName, createStatistic(statName, mode_));
+ }
+
+ // Reset the output-statistics container
+ results_.clear();
+
+ // Reset the extrema-data container
+ extremaResults_.clear();
+
+ return true;
+}
+
+
+bool Foam::functionObjects::fieldStatistics::execute()
+{
+ fieldSet_.updateSelection();
+
+ // Calculate the specified statistics for the specified fields
+ for (const word& fieldName : fieldSet_.selectionNames())
+ {
+ const bool ok =
+ (
+ calcStat(fieldName)
+ || calcStat(fieldName)
+ || calcStat(fieldName)
+ || calcStat(fieldName)
+ || calcStat(fieldName)
+ );
+
+ if (!ok)
+ {
+ WarningInFunction
+ << "Unable to find field " << fieldName << endl;
+ }
+ }
+
+ // Store the statistical results into the state containers
+ for (const word& fieldName : fieldSet_.selectionNames())
+ {
+ const auto& results = results_(fieldName);
+
+ for (const auto& iter : results.csorted())
+ {
+ const word& name = iter.key();
+ const variantOutput& value = iter.val();
+
+ const word variableName(fieldName + "_" + name);
+
+ std::visit
+ (
+ [this, variableName](const auto& v)
+ {
+ this->setResult(variableName, v);
+ },
+ value
+ );
+ }
+
+ if (extrema_)
+ {
+ const auto& min = extremaResults_(fieldName).first();
+ std::visit
+ (
+ [this, fieldName](const auto& v)
+ {
+ this->setResult(word(fieldName + "_min"), v);
+ },
+ min.value_
+ );
+ this->setResult(word(fieldName + "_min_procID"), min.procID_);
+ this->setResult(word(fieldName + "_min_cellID"), min.cellID_);
+ this->setResult(word(fieldName + "_min_position"), min.position_);
+
+ const auto& max = extremaResults_(fieldName).second();
+ std::visit
+ (
+ [this, fieldName](const auto& v)
+ {
+ this->setResult(word(fieldName + "_max"), v);
+ },
+ max.value_
+ );
+ this->setResult(word(fieldName + "_max_procID"), max.procID_);
+ this->setResult(word(fieldName + "_max_cellID"), max.cellID_);
+ this->setResult(word(fieldName + "_max_position"), max.position_);
+ }
+ }
+
+ return true;
+}
+
+
+bool Foam::functionObjects::fieldStatistics::write()
+{
+ Log << type() << ' ' << name() << " write:" << endl;
+
+ // Create an output file per field
+ if (writeToFile() && !writtenHeader_)
+ {
+ for (const word& fieldName : fieldSet_.selectionNames())
+ {
+ filePtrs_.set(fieldName, newFileAtStartTime(fieldName));
+
+ OFstream& file = *filePtrs_(fieldName);
+
+ writeFileHeader(file, fieldName);
+ }
+
+ if (extrema_)
+ {
+ for (const word& fieldName : fieldSet_.selectionNames())
+ {
+ extremaFilePtrs_.set
+ (
+ fieldName,
+ newFileAtStartTime(word("fieldMinMax_" + fieldName))
+ );
+
+ OFstream& file = *extremaFilePtrs_(fieldName);
+ writeExtremaFileHeader(file, fieldName);
+ }
+ }
+
+ writtenHeader_ = true;
+ }
+
+ // Write the statistical results to the output file if requested
+ if (writeToFile())
+ {
+ if (extrema_) writeExtremaData();
+
+ writeStatData();
+ }
+
+ // Print the statistical results to the standard stream if requested
+ if (log)
+ {
+ if (extrema_) logExtremaData();
+
+ logStatData();
+ }
+
+ return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/functionObjects/field/fieldStatistics/fieldStatisticsImpl.cxx b/src/functionObjects/field/fieldStatistics/fieldStatisticsImpl.cxx
new file mode 100644
index 0000000000..21163704ed
--- /dev/null
+++ b/src/functionObjects/field/fieldStatistics/fieldStatisticsImpl.cxx
@@ -0,0 +1,297 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2025 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 .
+
+\*---------------------------------------------------------------------------*/
+
+#include "fieldStatistics.H"
+#include "volFields.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+template
+Foam::tmp>
+Foam::functionObjects::fieldStatistics::flatten(const GeoField& fld) const
+{
+ typedef typename GeoField::value_type value_type;
+ typedef Field FieldType;
+
+ label n(0);
+
+ if (!internal_)
+ {
+ // Count boundary values
+ for (const auto& pfld : fld.boundaryField())
+ {
+ if (!pfld.coupled())
+ {
+ n += pfld.size();
+ }
+ }
+ }
+
+ if (!n)
+ {
+ // No boundary values - quick return
+ return tmp(fld.primitiveField());
+ }
+
+
+ // Combined internal + flattened boundary fields
+ // - this adds extra storage, but necessary since the visitor pattern
+ // requires a single input
+
+ auto tflatFld = tmp::New(fld.size() + n);
+ auto& flatFld = tflatFld.ref();
+
+ // Copy internal values
+ flatFld.slice(0, fld.size()) = fld.primitiveField();
+
+ // Copy boundary values
+ n = fld.size();
+ for (const auto& pfld : fld.boundaryField())
+ {
+ if (!pfld.coupled())
+ {
+ flatFld.slice(n, pfld.size()) = pfld;
+ n += pfld.size();
+ }
+ }
+
+ return tflatFld;
+}
+
+
+template
+bool Foam::functionObjects::fieldStatistics::calcStat(const word& fieldName)
+{
+ typedef GeometricField VolFieldType;
+
+ const auto* fieldp = obr_.cfindObject(fieldName);
+ if (!fieldp)
+ {
+ return false;
+ }
+ const auto& field = *fieldp;
+
+ tmp> tfullfield = flatten(field);
+ const auto& fullfield = tfullfield.cref();
+
+ HashTable result;
+ for (const auto& iter : statistics_.csorted())
+ {
+ const statistic& stat = iter.val();
+
+ // Assign a new entry, overwriting existing entries
+ result.set(stat.name_, stat.calc(fullfield));
+ }
+
+ results_.set(fieldName, result);
+
+ if (extrema_)
+ {
+ extremaResults_.set
+ (
+ fieldName,
+ (mode_ == modeType::MAG)
+ ? calcExtremaData(mag(field)())
+ : calcExtremaData(field)
+ );
+ }
+
+ return true;
+}
+
+
+template
+T Foam::functionObjects::fieldStatistics::calcMean(const Field& field) const
+{
+ if (internal_ && (mean_ == meanType::VOLUMETRIC))
+ {
+ return gWeightedAverage(mesh_.V(), field);
+ }
+
+ return gAverage(field);
+}
+
+
+template
+T Foam::functionObjects::fieldStatistics::calcMin(const Field& field) const
+{
+ return gMin(field);
+}
+
+
+template
+T Foam::functionObjects::fieldStatistics::calcMax(const Field& field) const
+{
+ return gMax(field);
+}
+
+
+template
+T Foam::functionObjects::fieldStatistics::calcVariance
+(
+ const Field& field
+) const
+{
+ const T avg(calcMean(field));
+
+ T var = Zero;
+ for (const auto& elem : field)
+ {
+ var += (elem - avg);
+ }
+
+ label count = field.size();
+ Foam::sumReduce(var, count);
+
+ if (count <= 1)
+ {
+ return Zero;
+ }
+
+ return 1.0/(count - 1.0)*var;
+}
+
+
+template
+Foam::Pair
+Foam::functionObjects::fieldStatistics::calcExtremaData
+(
+ const GeoField& field
+) const
+{
+ typedef typename GeoField::value_type value_type;
+
+ const label proci = UPstream::myProcNo();
+
+ List minVs(UPstream::nProcs(), pTraits::max);
+ List minCells(UPstream::nProcs(), Foam::zero{});
+ List minCs(UPstream::nProcs(), Foam::zero{});
+
+ List maxVs(UPstream::nProcs(), pTraits::min);
+ List maxCells(UPstream::nProcs(), Foam::zero{});
+ List maxCs(UPstream::nProcs(), Foam::zero{});
+
+ // Find extrema within the internal field
+ {
+ auto minMaxIds = findMinMax(field);
+
+ if (label celli = minMaxIds.first(); celli >= 0)
+ {
+ minVs[proci] = field[celli];
+ minCells[proci] = celli;
+ minCs[proci] = mesh_.C()[celli];
+ }
+
+ if (label celli = minMaxIds.second(); celli >= 0)
+ {
+ maxVs[proci] = field[celli];
+ maxCells[proci] = celli;
+ maxCs[proci] = mesh_.C()[celli];
+ }
+ }
+
+ if (!internal_)
+ {
+ // Find extrema within the boundary fields
+ const auto& fieldBoundary = field.boundaryField();
+ const auto& CfBoundary = mesh_.C().boundaryField();
+
+ forAll(fieldBoundary, patchi)
+ {
+ const Field& fp = fieldBoundary[patchi];
+
+ if (fp.size())
+ {
+ const auto& Cfp = CfBoundary[patchi];
+
+ const auto& faceCells
+ = fieldBoundary[patchi].patch().faceCells();
+
+ auto minMaxIds = findMinMax(fp);
+
+ if
+ (
+ label facei = minMaxIds.first();
+ facei >= 0 && minVs[proci] > fp[facei]
+ )
+ {
+ minVs[proci] = fp[facei];
+ minCells[proci] = faceCells[facei];
+ minCs[proci] = Cfp[facei];
+ }
+
+ if
+ (
+ label facei = minMaxIds.second();
+ facei >= 0 && maxVs[proci] < fp[facei]
+ )
+ {
+ maxVs[proci] = fp[facei];
+ maxCells[proci] = faceCells[facei];
+ maxCs[proci] = Cfp[facei];
+ }
+ }
+ }
+ }
+
+ // Collect info from all processors
+ Pstream::allGatherList(minVs);
+ Pstream::allGatherList(minCells);
+ Pstream::allGatherList(minCs);
+
+ Pstream::allGatherList(maxVs);
+ Pstream::allGatherList(maxCells);
+ Pstream::allGatherList(maxCs);
+
+
+ Pair results;
+
+ // min
+ {
+ auto& slot = results.first();
+ const label procId = findMin(minVs);
+ slot.value_ = minVs[procId];
+ slot.procID_ = procId;
+ slot.cellID_ = minCells[procId];
+ slot.position_ = minCs[procId];
+ }
+
+ // max
+ {
+ auto& slot = results.second();
+ const label procId = findMax(maxVs);
+ slot.value_ = maxVs[procId];
+ slot.procID_ = procId;
+ slot.cellID_ = maxCells[procId];
+ slot.position_ = maxCs[procId];
+ }
+
+ return results;
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/pisoFoam/RAS/cavity/system/FOs/FOfieldStatistics b/tutorials/incompressible/pisoFoam/RAS/cavity/system/FOs/FOfieldStatistics
new file mode 100644
index 0000000000..1c6dc960cd
--- /dev/null
+++ b/tutorials/incompressible/pisoFoam/RAS/cavity/system/FOs/FOfieldStatistics
@@ -0,0 +1,46 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| ========= | |
+| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
+| \\ / O peration | Version: v2412 |
+| \\ / A nd | Website: www.openfoam.com |
+| \\/ M anipulation | |
+\*---------------------------------------------------------------------------*/
+
+fieldStatistics1
+{
+ // Mandatory entries
+ type fieldStatistics;
+ libs (fieldFunctionObjects);
+ fields ( U p );
+ statistics
+ (
+ min
+ max
+ mean
+ variance
+ );
+
+ // Optional entries
+ mode component;
+ mean arithmetic;
+ internal false;
+ extrema true;
+
+ // Optional (inherited) entries
+ writePrecision 10;
+ writeToFile true;
+ useUserTime true;
+
+ region region0;
+ enabled true;
+ log true;
+ timeStart 0;
+ timeEnd 1000;
+ executeControl timeStep;
+ executeInterval 1;
+ writeControl writeTime;
+ writeInterval -1;
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/pisoFoam/RAS/cavity/system/controlDict b/tutorials/incompressible/pisoFoam/RAS/cavity/system/controlDict
index aec5c78b4c..f05225d8cf 100644
--- a/tutorials/incompressible/pisoFoam/RAS/cavity/system/controlDict
+++ b/tutorials/incompressible/pisoFoam/RAS/cavity/system/controlDict
@@ -98,6 +98,7 @@ functions
#include "FOs/FOyPlus"
#include "FOs/FOzeroGradient"
#include "FOs/FOnorm"
+ #include "FOs/FOfieldStatistics"
// utility function objects
#include "FOs/FOsolverInfo"