/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | Website: https://openfoam.org \\ / A nd | Copyright (C) 2022-2023 OpenFOAM Foundation \\/ M anipulation | ------------------------------------------------------------------------------- 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 "patchCutLayerAverage.H" #include "cutPolyIntegral.H" #include "OSspecific.H" #include "volFields.H" #include "writeFile.H" #include "polyTopoChangeMap.H" #include "polyMeshMap.H" #include "polyDistributionMap.H" #include "addToRunTimeSelectionTable.H" // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // namespace Foam { namespace functionObjects { defineTypeNameAndDebug(patchCutLayerAverage, 0); addToRunTimeSelectionTable ( functionObject, patchCutLayerAverage, dictionary ); } } // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // Foam::List Foam::functionObjects::patchCutLayerAverage::calcNonInterpolatingWeights ( const scalarField& pointXs, const scalarField& faceMinXs, const scalarField& faceMaxXs, const labelList& faceMinOrder, const scalarField& plotXs, const bool normalise ) const { const polyPatch& pp = mesh_.boundaryMesh()[patchName_]; const faceList& faces = pp.localFaces(); const vectorField::subField& faceAreas = pp.faceAreas(); const vectorField& faceNormals = pp.faceNormals(); const pointField& points = pp.localPoints(); // One-value for every point. Needed for the cutPoly routines. const scalarField pointOnes(points.size(), scalar(1)); // Generate weights for each face in turn DynamicList dynWeights(faces.size()*2); label layeri = 0; forAll(faceMinOrder, i) { const label facei = faceMinOrder[i]; const scalar a = faceAreas[facei] & faceNormals[facei]; // Find the next relevant layer while (faceMinXs[facei] > plotXs[layeri + 1]) { layeri ++; } // Loop over all relevant layer intervals label layerj = layeri; while (faceMaxXs[facei] > plotXs[layerj]) { const scalar x0 = plotXs[layerj], x1 = plotXs[layerj + 1]; // Add a new weight dynWeights.append({facei, layerj, a}); // Left interval if (faceMinXs[facei] < x0) { dynWeights.last().value -= cutPoly::faceCutAreaIntegral ( faces[facei], faceAreas[facei], scalar(1), cutPoly::faceCuts(faces[facei], pointXs, x0), points, pointOnes, pointXs, x0, true ).first() & faceNormals[facei]; } // Right interval if (faceMaxXs[facei] > x1) { dynWeights.last().value -= cutPoly::faceCutAreaIntegral ( faces[facei], faceAreas[facei], scalar(1), cutPoly::faceCuts(faces[facei], pointXs, x1), points, pointOnes, pointXs, x1, false ).first() & faceNormals[facei]; } layerj ++; } } // Transfer to non-dynamic storage List weights; weights.transfer(dynWeights); // Normalise, if requested if (normalise) { scalarField layerWeightSums(nLayers_, scalar(0)); forAll(weights, weighti) { layerWeightSums[weights[weighti].layeri] += weights[weighti].value; } Pstream::listCombineGather(layerWeightSums, plusEqOp()); Pstream::listCombineScatter(layerWeightSums); forAll(weights, weighti) { weights[weighti].value /= (layerWeightSums[weights[weighti].layeri] + vSmall); } } return weights; } Foam::List Foam::functionObjects::patchCutLayerAverage::calcInterpolatingWeights ( const scalarField& pointXs, const scalarField& faceMinXs, const scalarField& faceMaxXs, const labelList& faceMinOrder, const scalarField& plotXs, const bool normalise ) const { const polyPatch& pp = mesh_.boundaryMesh()[patchName_]; const faceList& faces = pp.localFaces(); const vectorField::subField& faceAreas = pp.faceAreas(); const vectorField& faceNormals = pp.faceNormals(); const pointField& points = pp.localPoints(); // Values of the (piecewise linear) interpolation basis function. Different // for every interval. Is constantly being set on a sub-set of the // currently relevant points in the loop below. scalarField pointFs(points.size(), scalar(0)); // Generate weights for each face in turn DynamicList dynWeights(faces.size()*2); label layeri = 0; forAll(faceMinOrder, i) { const label facei = faceMinOrder[i]; const scalar a = faceAreas[facei] & faceNormals[facei]; // Find the next relevant plot point while (faceMinXs[facei] > plotXs[layeri + 1]) { layeri ++; } // Loop over all relevant plot points label layerj = layeri; while (layerj == 0 || faceMaxXs[facei] > plotXs[layerj - 1]) { const scalar xLeft = layerj > 0 ? plotXs[layerj - 1] : NaN; const scalar xMid = plotXs[layerj]; const scalar xRight = layerj < nLayers_ - 1 ? plotXs[layerj + 1] : NaN; // Add a new weight dynWeights.append({facei, layerj, 0}); // Left interval if (layerj > 0 && faceMinXs[facei] < xMid) { forAll(faces[facei], facePointi) { const label pointi = faces[facei][facePointi]; pointFs[pointi] = (pointXs[pointi] - xLeft)/(xMid - xLeft); } const scalar f = faces[facei].average(points, pointFs); // Add the whole face's contribution dynWeights.last().value += f*a; // Cut off anything before the left point if (faceMinXs[facei] < xLeft) { dynWeights.last().value -= cutPoly::faceCutAreaIntegral ( faces[facei], faceAreas[facei], f, cutPoly::faceCuts(faces[facei], pointXs, xLeft), points, pointFs, pointXs, xLeft, true ).second() & faceNormals[facei]; } // Cut off anything after the middle point if (faceMaxXs[facei] > xMid) { dynWeights.last().value -= cutPoly::faceCutAreaIntegral ( faces[facei], faceAreas[facei], f, cutPoly::faceCuts(faces[facei], pointXs, xMid), points, pointFs, pointXs, xMid, false ).second() & faceNormals[facei]; } } // Right interval if (layerj < nLayers_ - 1 && faceMaxXs[facei] > xMid) { forAll(faces[facei], facePointi) { const label pointi = faces[facei][facePointi]; pointFs[pointi] = (xRight - pointXs[pointi])/(xRight - xMid); } const scalar f = faces[facei].average(points, pointFs); // Add the whole face's contribution dynWeights.last().value += f*a; // Cut off anything before the middle point if (faceMinXs[facei] < xMid) { dynWeights.last().value -= cutPoly::faceCutAreaIntegral ( faces[facei], faceAreas[facei], f, cutPoly::faceCuts(faces[facei], pointXs, xMid), points, pointFs, pointXs, xMid, true ).second() & faceNormals[facei]; } // Cut off anything after the right point if (faceMaxXs[facei] > xRight) { dynWeights.last().value -= cutPoly::faceCutAreaIntegral ( faces[facei], faceAreas[facei], f, cutPoly::faceCuts(faces[facei], pointXs, xRight), points, pointFs, pointXs, xRight, false ).second() & faceNormals[facei]; } } layerj ++; } } // Transfer to non-dynamic storage List weights; weights.transfer(dynWeights); // Normalise, if requested. Otherwise, double the weight values on the ends // to account for the fact that these points only have half a basis function // contributing to their sums. if (normalise) { scalarField layerWeightSums(nLayers_, scalar(0)); forAll(weights, weighti) { layerWeightSums[weights[weighti].layeri] += weights[weighti].value; } Pstream::listCombineGather(layerWeightSums, plusEqOp()); Pstream::listCombineScatter(layerWeightSums); forAll(weights, weighti) { weights[weighti].value /= (layerWeightSums[weights[weighti].layeri] + vSmall); } } else { forAll(weights, weighti) { if ( weights[weighti].layeri == 0 || weights[weighti].layeri == nLayers_ - 1 ) { weights[weighti].value *= 2; } } } return weights; } Foam::List Foam::functionObjects::patchCutLayerAverage::calcWeights ( const scalarField& pointXs, const scalarField& faceMinXs, const scalarField& faceMaxXs, const labelList& faceMinOrder, const scalarField& plotXs, const bool normalise ) const { return interpolate_ ? calcInterpolatingWeights ( pointXs, faceMinXs, faceMaxXs, faceMinOrder, plotXs, normalise ) : calcNonInterpolatingWeights ( pointXs, faceMinXs, faceMaxXs, faceMinOrder, plotXs, normalise ); } template inline Foam::tmp> Foam::functionObjects::patchCutLayerAverage::applyWeights ( const List& weights, const Field& faceValues ) const { tmp> tLayerValues(new Field(nLayers_, Zero)); forAll(weights, weighti) { tLayerValues.ref()[weights[weighti].layeri] += weights[weighti].value*faceValues[weights[weighti].facei]; } Pstream::listCombineGather(tLayerValues.ref(), plusEqOp()); Pstream::listCombineScatter(tLayerValues.ref()); return tLayerValues; } void Foam::functionObjects::patchCutLayerAverage::initialise() { const polyPatch& pp = mesh_.boundaryMesh()[patchName_]; const faceList& faces = pp.localFaces(); const pointField& points = pp.localPoints(); // If interpolating, then the layers and the plot points are coincident. If // not interpolating, then the layers lie in between the plot points, so // there is one more point than there are layers. const label nPlot = interpolate_ ? nLayers_ : nLayers_ + 1; // Calculate point coordinates const scalarField pointXs(points & direction_); // Determine face min and max coordinates scalarField faceMinXs(faces.size(), vGreat); scalarField faceMaxXs(faces.size(), -vGreat); forAll(faces, facei) { forAll(faces[facei], facePointi) { const label pointi = faces[facei][facePointi]; faceMinXs[facei] = min(faceMinXs[facei], pointXs[pointi]); faceMaxXs[facei] = max(faceMaxXs[facei], pointXs[pointi]); } } // Create orderings of the faces based on their min and max coordinates labelList faceMinOrder(faces.size()); sortedOrder(faceMinXs, faceMinOrder); // Assume equal spacing to begin with const scalar xMin = gMin(pointXs), xMax = gMax(pointXs); scalarField plotXs ( (xMin + scalarList(identityMap(nPlot))/(nPlot - 1)*(xMax - xMin)) ); // Names and fields for debug output of the counts, to observe the effect // of iterative improvement of the spacing wordList fieldNames; #define DeclareTypeFieldValues(Type, nullArg) \ PtrList> Type##FieldValues; FOR_ALL_FIELD_TYPES(DeclareTypeFieldValues); #undef DeclareTypeFieldValues // Iteratively optimise the spacing between the plot points to achieve an // approximately equal number of data points in each interval for (label iteri = 0; iteri < nOptimiseIter_ + debug; ++ iteri) { // Determine the count of faces that contribute to each layer const List weights = calcWeights ( pointXs, faceMinXs, faceMaxXs, faceMinOrder, plotXs, false ); const scalarField layerCounts ( applyWeights(weights, (1/pp.magFaceAreas())()) ); if (debug) { const label nFields0 = (2 + !interpolate_)*iteri; const label nFields = (2 + !interpolate_)*(iteri + 1); fieldNames.resize(nFields); #define ResizeTypeFieldValues(Type, nullArg) \ Type##FieldValues.resize(nFields); FOR_ALL_FIELD_TYPES(ResizeTypeFieldValues); #undef ResizeTypeFieldValues if (!interpolate_) { const SubField distance0s(plotXs, nLayers_); const SubField distance1s(plotXs, nLayers_, 1); fieldNames[nFields0] = "distance-" + Foam::name(iteri); scalarFieldValues.set(nFields0, (distance0s + distance1s)/2); fieldNames[nFields0 + 1] = "thickness-" + Foam::name(iteri); scalarFieldValues.set(nFields0 + 1, distance1s - distance0s); } else { fieldNames[nFields0] = "distance-" + Foam::name(iteri); scalarFieldValues.set(nFields0, new scalarField(plotXs)); } fieldNames[nFields - 1] = "count-" + Foam::name(iteri); scalarFieldValues.set(nFields - 1, new scalarField(layerCounts)); if (iteri == nOptimiseIter_) break; } // Do a cumulative sum of the layer counts across all plot points scalarField plotSumCounts(nPlot, 0); for (label ploti = 0; ploti < nPlot - 1; ++ ploti) { plotSumCounts[ploti + 1] = plotSumCounts[ploti] + ( interpolate_ ? (layerCounts[ploti + 1] + layerCounts[ploti])/2 : layerCounts[ploti] ); } // Compute the desired count in each interval const scalar plotDeltaCount = plotSumCounts.last()/(nPlot - 1); // Compute the new spacing between the points scalarField plot0Xs(plotXs); plotXs = -vGreat; plotXs.first() = xMin; label ploti = 1; for (label ploti0 = 0; ploti0 < nPlot - 1; ++ ploti0) { while (plotSumCounts[ploti0 + 1] > ploti*plotDeltaCount) { const scalar f = (ploti*plotDeltaCount - plotSumCounts[ploti0]) /(plotSumCounts[ploti0 + 1] - plotSumCounts[ploti0]); plotXs[ploti] = (1 - f)*plot0Xs[ploti0] + f*plot0Xs[ploti0 + 1]; ploti ++; } } plotXs.last() = xMax; } if (debug) { mkDir(outputPath()); formatter_->write ( outputPath(), typeName + "_count", coordSet(labelList(nLayers_, 1)), fieldNames #define TypeFieldValuesParameter(Type, nullArg) \ , Type##FieldValues FOR_ALL_FIELD_TYPES(TypeFieldValuesParameter) #undef TypeFieldValuesParameter ); } // Finally, calculate the actual normalised interpolation weights weights_ = calcWeights ( pointXs, faceMinXs, faceMaxXs, faceMinOrder, plotXs, true ); // Calculate plot coordinates and widths if (interpolate_) { layerDistances_ = plotXs; } else { const SubField distance0s(plotXs, nLayers_); const SubField distance1s(plotXs, nLayers_, 1); layerDistances_ = (distance0s + distance1s)/2; layerThicknesses_.reset((distance1s - distance0s).ptr()); } // Calculate the plot positions layerPositions_ = applyWeights(weights_, pointField(pp.faceCentres())); } Foam::fileName Foam::functionObjects::patchCutLayerAverage::outputPath() const { return time_.globalPath() /writeFile::outputPrefix /(mesh_.name() != polyMesh::defaultRegion ? mesh_.name() : word()) /name() /time_.name(); } // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // Foam::functionObjects::patchCutLayerAverage::patchCutLayerAverage ( const word& name, const Time& runTime, const dictionary& dict ) : fvMeshFunctionObject(name, runTime, dict) { read(dict); } // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * // Foam::functionObjects::patchCutLayerAverage::~patchCutLayerAverage() {} // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // bool Foam::functionObjects::patchCutLayerAverage::read(const dictionary& dict) { patchName_ = dict.lookup("patch"); direction_ = normalised(dict.lookup("direction")); nLayers_ = dict.lookup