fanMomentumSource.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2022 Louis Vittoz, SimScale GmbH
9  Copyright (C) 2023 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
29 #include "fanMomentumSource.H"
30 #include "IFstream.H"
34 
35 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
36 
37 namespace Foam
38 {
39 namespace fv
40 {
41  defineTypeNameAndDebug(fanMomentumSource, 0);
42  addToRunTimeSelectionTable(option, fanMomentumSource, dictionary);
43 }
44 }
45 
46 
47 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
48 
49 void Foam::fv::fanMomentumSource::writeProps
50 (
51  const scalar gradP,
52  const scalar flowRate
53 ) const
54 {
55  // Only write on output time
56  if (mesh_.time().writeTime())
57  {
58  IOdictionary propsDict
59  (
60  IOobject
61  (
62  name_ + "Properties",
63  mesh_.time().timeName(),
64  "uniform",
65  mesh_,
69  )
70  );
71  propsDict.add("gradient", gradP);
72  propsDict.add("flow_rate", flowRate);
73  propsDict.regIOobject::write();
74  }
75 }
76 
77 
78 void Foam::fv::fanMomentumSource::initUpstreamFaces()
79 {
80  // First calculate the centre of gravity of the cell zone
81  const vectorField& cellCentres = mesh_.cellCentres();
82  const scalarField& cellVolumes = mesh_.cellVolumes();
83 
84  vector centreGravityCellZone(Zero);
85  scalar cellZoneVolume = 0;
86  for (const label celli : cells_)
87  {
88  const scalar cellVolume = cellVolumes[celli];
89  centreGravityCellZone += cellCentres[celli]*cellVolume;
90  cellZoneVolume += cellVolume;
91  }
92 
93  reduce(centreGravityCellZone, sumOp<vector>());
94  reduce(cellZoneVolume, sumOp<scalar>());
95 
96  if (cellZoneVolume < SMALL)
97  {
99  << "Cannot initialize upstream faces because the total cell zone"
100  << " volume is zero or negative: " << cellZoneVolume << "." << nl
101  << "Check whether there are cells in fan momentum source cell zone."
102  << abort(FatalError);
103  }
104 
105  centreGravityCellZone /= cellZoneVolume;
106 
107  // Collect faces upstream of the centre of gavity
108  const faceZone& fZone = mesh_.faceZones()[surroundingFaceZoneID_];
109  const vectorField& faceCentreGravity = mesh_.faceCentres();
110 
111  upstreamPatchFaceInfo_.resize_nocopy(fZone.size());
112 
113  label count = 0;
114  for (const label facei : fZone)
115  {
116  if
117  (
118  (flowDir_ & (faceCentreGravity[facei] - centreGravityCellZone)) < 0
119  )
120  {
121  labelPair patchFaceInfo(-1, -1);
122 
123  if (mesh_.isInternalFace(facei))
124  {
125  // Patch ID already set to -1, set only the face ID
126  patchFaceInfo.second() = facei;
127  }
128  else
129  {
130  patchFaceInfo.first() = mesh_.boundaryMesh().whichPatch(facei);
131  const polyPatch& pp = mesh_.boundaryMesh()[patchFaceInfo.first()];
132  const auto* cpp = isA<coupledPolyPatch>(pp);
133 
134  if (cpp)
135  {
136  patchFaceInfo.second() =
137  cpp->owner()
138  ? pp.whichFace(facei)
139  : -1;
140  }
141  else if (!isA<emptyPolyPatch>(pp))
142  {
143  patchFaceInfo.second() = pp.whichFace(facei);
144  }
145  // else both face ID and patch ID remain at -1
146  }
147 
148  // If this is an upstream face, set it in the list
149  if (patchFaceInfo.second() >= 0)
150  {
151  upstreamPatchFaceInfo_[count] = patchFaceInfo;
152  count++;
153  }
154  }
155  }
156 
157  upstreamPatchFaceInfo_.setSize(count);
158 
159  // Fill cellsInZones_ with all cell IDs
160  for (const label celli : cells_)
161  {
162  cellsInZones_.insert(celli);
163  }
164 
165  // Sanity check
166  const labelUList& owners = mesh_.owner();
167  const labelUList& neighbours = mesh_.neighbour();
168  for (const labelPair& patchFaceInfo : upstreamPatchFaceInfo_)
169  {
170  if (patchFaceInfo.first() == -1)
171  {
172  const label facei = patchFaceInfo.second();
173  const label own = owners[facei];
174  const label nei = neighbours[facei];
175 
176  // To be valid: one cell has to be inside the cellZone and the other
177  // one, outside
178  if (cellsInZones_.found(own) == cellsInZones_.found(nei))
179  {
181  << "It seems that the faceZone is not part of the cellZone "
182  << "boundaries."
183  << abort(FatalError);
184  }
185  }
186  }
187 }
188 
189 
190 Foam::scalar
191 Foam::fv::fanMomentumSource::calcFlowRate
192 (
193  const surfaceScalarField& phi,
194  const surfaceScalarField& rhof
195 ) const
196 {
197  // Sanity check to make sure we're not left in an inconsistent state because
198  // here we're operating on primitive (non-dimensional) fields
199  if (isNull(rhof) != (phi.dimensions() == dimVolume/dimTime))
200  {
202  << "Incorrect usage of the function. " << nl
203  << "For incompressible flow, pass surfaceScalarField::null for rhof"
204  << " and volumetric flux for phi." << nl
205  << "For compressible flow, pass face-interpolated density as rhof"
206  << " and mass flux for phi."
207  << exit(FatalError);
208  }
209 
210  // Calculate the flow rate through the upstream faces
211  scalarList phif(upstreamPatchFaceInfo_.size());
212 
213  const labelUList& owners = mesh_.owner();
214 
215  forAll(upstreamPatchFaceInfo_, i)
216  {
217  const labelPair& patchFaceInfo = upstreamPatchFaceInfo_[i];
218 
219  if (isNull(rhof))
220  {
221  // Incompressible variant (phi = volumetric flux)
222  phif[i] =
223  patchFaceInfo.first() < 0
224  ? phi[patchFaceInfo.second()]
225  : phi.boundaryField()[patchFaceInfo];
226  }
227  else
228  {
229  // Compressible variant (phi = mass flux)
230  phif[i] =
231  patchFaceInfo.first() < 0
232  ? phi[patchFaceInfo.second()]/
233  rhof.internalField()[patchFaceInfo.second()]
234  : phi.boundaryField()[patchFaceInfo]/
235  rhof.boundaryField()[patchFaceInfo];
236  }
237 
238  // Sign of the flux needs to be flipped if this is an internal face
239  // whose owner is found in the cell zone
240  if
241  (
242  patchFaceInfo.first() < 0
243  && cellsInZones_.found(owners[patchFaceInfo.second()])
244  )
245  {
246  phif[i] *= -1;
247  }
248  }
249 
250  return gSum(phif);
251 }
252 
253 
254 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
255 
257 (
258  const word& sourceName,
259  const word& modelType,
260  const dictionary& dict,
261  const fvMesh& mesh
262 )
263 :
264  fv::cellSetOption(sourceName, modelType, dict, mesh),
265  upstreamPatchFaceInfo_(),
266  cellsInZones_(),
267  fanCurvePtr_(Function1<scalar>::New("fanCurve", coeffs_, &mesh)),
268  UName_(coeffs_.getOrDefault<word>("U", "U")),
269  flowDir_(coeffs_.get<vector>("flowDir")),
270  thickness_(coeffs_.get<scalar>("thickness")),
271  gradPFan_(0.0),
272  surroundingFaceZoneID_(-1),
273  rho_(coeffs_.getOrDefault<scalar>("rho", SMALL)),
274  useRefRho_(coeffs_.found("rho"))
275 {
276  // Skip all the checks if the source term has been deactivated
277  // because there are no selected cells.
278  // Such a situation typically occurs for multiple fluid regions
279  if (fv::option::isActive())
280  {
281  const word faceZoneName = coeffs_.getWord("faceZone");
282 
283  surroundingFaceZoneID_ = mesh_.faceZones().findZoneID(faceZoneName);
284 
285  if (surroundingFaceZoneID_ < 0)
286  {
288  << type() << " " << this->name() << ": "
289  << " Unknown face zone name: " << faceZoneName
290  << ". Valid face zones are: " << mesh_.faceZones().names()
291  << exit(FatalError);
292  }
293 
294  flowDir_.normalise();
295  if (mag(flowDir_) < SMALL)
296  {
298  << "'flowDir' cannot be a zero-valued vector."
299  << exit(FatalIOError);
300  }
301 
302  if (thickness_ < VSMALL)
303  {
305  << "'thickness' cannot be non-positive."
306  << exit(FatalIOError);
307  }
308 
309  if (coeffs_.found("fields"))
310  {
312  << "Found 'fields' entry, which will be ignored. This fvOption"
313  << " will operate only on field 'U' = " << UName_
314  << endl;
315  }
316  fieldNames_.resize(1, UName_);
317 
319 
320  // Read the initial pressure gradient from file if it exists
321  IFstream propsFile
322  (
323  mesh_.time().timePath()/"uniform"/(name_ + "Properties")
324  );
325 
326  if (propsFile.good())
327  {
328  Info<< " Reading pressure gradient from file" << endl;
329  dictionary propsDict(propsFile);
330  propsDict.readEntry("gradient", gradPFan_);
331  }
332 
333  Info<< " Initial pressure gradient = " << gradPFan_ << nl << endl;
334 
335  if (rho_ < SMALL)
336  {
338  << "'rho' cannot be zero or negative."
339  << exit(FatalIOError);
340  }
341 
342  initUpstreamFaces();
343  }
344 }
345 
346 
347 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
348 
350 (
351  fvMatrix<vector>& eqn,
352  const label fieldi
353 )
354 {
356  (
357  IOobject
358  (
359  name_ + fieldNames_[fieldi] + "Sup",
360  mesh_.time().timeName(),
361  mesh_,
365  ),
366  mesh_,
368  );
369 
370  const auto& phi = mesh().lookupObject<surfaceScalarField>("phi");
371 
372  if (phi.dimensions() != dimVelocity*dimArea)
373  {
375  << "You called incompressible variant of addSup for case with "
376  << "a mass flux and not volumetric flux. This is not allowed."
377  << abort(FatalError);
378  }
379 
380  if (!useRefRho_)
381  {
383  << "You called incompressible addSup without providing a "
384  << "reference density. Add 'rho' entry to the dictionary."
385  << abort(FatalError);
386  }
387 
388  const scalar flowRate = calcFlowRate(phi, surfaceScalarField::null());
389 
390  // Pressure drop for this flow rate
391  // if flow rate is negative, pressure is clipped at the static pressure
392  gradPFan_ =
393  fanCurvePtr_->value(max(flowRate, scalar(0)))/thickness_/rho_;
394 
395  // Create the source term
396  UIndirectList<vector>(Su, cells_) = flowDir_*gradPFan_;
397 
398  eqn += Su;
399 
400  writeProps(gradPFan_, flowRate);
401 }
402 
403 
405 (
406  const volScalarField& rho,
407  fvMatrix<vector>& eqn,
408  const label fieldi
409 )
410 {
412  (
413  IOobject
414  (
415  name_ + fieldNames_[fieldi] + "Sup",
416  mesh_.time().timeName(),
417  mesh_,
421  ),
422  mesh_,
424  );
425 
426  const auto& phi = mesh().lookupObject<surfaceScalarField>("phi");
427 
428  if (phi.dimensions() != dimMass/dimTime)
429  {
431  << "You called compressible variant of addSup for case with "
432  << "a volumetric flux and not mass flux. This is not allowed."
433  << abort(FatalError);
434  }
435 
437  const scalar flowRate = calcFlowRate(phi, rhof);
438 
439  // Pressure drop for this flow rate
440  // if flow rate is negative, pressure is clipped at the static pressure
441  gradPFan_ = fanCurvePtr_->value(max(flowRate, scalar(0)))/thickness_;
442 
443  // Create the source term
444  UIndirectList<vector>(Su, cells_) = flowDir_*gradPFan_;
446  eqn += Su;
447 
448  writeProps(gradPFan_, flowRate);
449 }
450 
451 
453 {
455 
456  return false;
457 }
458 
459 
460 // ************************************************************************* //
List< ReturnType > get(const UPtrList< T > &list, const AccessOp &aop)
List of values generated by applying the access operation to each list item.
List< scalar > scalarList
List of scalar.
Definition: scalarList.H:32
Top level data entry class for use in dictionaries. Provides a mechanism to specify a variable as a c...
const T & first() const noexcept
Access the first element.
Definition: Pair.H:137
dictionary dict
zeroField Su
Definition: alphaSuSp.H:1
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
wordList fieldNames_
Field names to apply source to - populated by derived models.
Definition: fvOption.H:157
void resize(const label len)
Adjust allocated size of list.
Definition: ListI.H:153
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:129
defineTypeNameAndDebug(atmAmbientTurbSource, 0)
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:608
fileName timePath() const
Return current time path = path/timeName.
Definition: Time.H:520
const fvMesh & mesh_
Reference to the mesh database.
Definition: fvOption.H:142
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
dimensioned< vector > dimensionedVector
Dimensioned vector obtained from generic dimensioned type.
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
tmp< DimensionedField< TypeR, GeoMesh > > New(const tmp< DimensionedField< TypeR, GeoMesh >> &tf1, const word &name, const dimensionSet &dimensions, const bool initCopy=false)
Global function forwards to reuseTmpDimensionedField::New.
void resize_nocopy(const label len)
Adjust allocated size of list without necessarily.
Definition: ListI.H:168
Ignore writing from objectRegistry::writeObject()
bool writeTime() const noexcept
True if this is a write interval.
Definition: TimeStateI.H:73
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:360
Macros for easy insertion into run-time selection tables.
UList< label > labelUList
A UList of labels.
Definition: UList.H:78
IOdictionary propsDict(dictIO)
bool found(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find an entry (const access) with the given keyword.
Definition: dictionaryI.H:104
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
volVectorField gradP(fvc::grad(p))
fileName::Type type(const fileName &name, const bool followLink=true)
Return the file type: DIRECTORY or FILE, normally following symbolic links.
Definition: POSIX.C:799
const dimensionSet dimVolume(pow3(dimLength))
Definition: dimensionSets.H:58
unsigned int count(const UList< bool > &bools, const bool val=true)
Count number of &#39;true&#39; entries.
Definition: BitOps.H:73
dynamicFvMesh & mesh
Type gSum(const FieldField< Field, Type > &f)
fanMomentumSource(const word &sourceName, const word &modelType, const dictionary &dict, const fvMesh &mesh)
Construct from explicit source name and mesh.
label findZoneID(const word &zoneName) const
Find zone index by name, return -1 if not found.
Definition: ZoneMesh.C:718
const word name_
Source name.
Definition: fvOption.H:132
A class for handling words, derived from Foam::string.
Definition: word.H:63
labelList fv(nPoints)
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
static const GeometricField< scalar, fvsPatchField, surfaceMesh > & null() noexcept
Return a null GeometricField (reference to a nullObject).
A special matrix type and solver, designed for finite volume solutions of scalar equations. Face addressing is used to make all matrix assembly and solution loops vectorise.
Definition: fvPatchField.H:64
const word & name() const noexcept
Return const access to the source name.
Definition: fvOptionI.H:24
Vector< scalar > vector
Definition: vector.H:57
word getWord(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Same as get< word >(const word&, keyType::option)
Definition: dictionary.H:1680
errorManip< error > abort(error &err)
Definition: errorManip.H:139
bool isNull(const T *ptr) noexcept
True if ptr is a pointer (of type T) to the nullObject.
Definition: nullObject.H:248
addToRunTimeSelectionTable(option, atmAmbientTurbSource, dictionary)
A Vector of values with scalar precision, where scalar is float/double depending on the compilation f...
static word timeName(const scalar t, const int precision=precision_)
Return a time name for the given scalar time value formatted with the given precision.
Definition: Time.C:714
Pair< label > labelPair
A pair of labels.
Definition: Pair.H:51
const faceZoneMesh & faceZones() const noexcept
Return face zone mesh.
Definition: polyMesh.H:671
virtual void addSup(fvMatrix< vector > &eqn, const label fieldi)
Add explicit contribution to momentum equation.
static tmp< GeometricField< Type, fvsPatchField, surfaceMesh > > interpolate(const GeometricField< Type, fvPatchField, volMesh > &tvf, const surfaceScalarField &faceFlux, Istream &schemeData)
Interpolate field onto faces using scheme given by Istream.
const dimensionSet & dimensions() const noexcept
Definition: fvMatrix.H:528
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:637
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:78
A List with indirect addressing. Like IndirectList but does not store addressing. ...
Definition: faMatrix.H:52
void resetApplied()
Resize/reset applied flag list for all fieldNames_ entries.
Definition: fvOption.C:41
Field with dimensions and associated with geometry type GeoMesh which is used to size the field and a...
Nothing to be read.
const dimensionSet dimTime(0, 0, 1, 0, 0, 0, 0)
Definition: dimensionSets.H:51
wordList names() const
A list of the zone names.
Definition: ZoneMesh.C:451
messageStream Info
Information stream (stdout output on master, null elsewhere)
Field< vector > vectorField
Specialisation of Field<T> for vector.
Intermediate abstract class for handling cell-set options for the derived fvOptions.
virtual bool isActive()
Is the source active?
Definition: fvOption.C:112
#define IOWarningInFunction(ios)
Report an IO warning using Foam::Warning.
const dimensionSet dimMass(1, 0, 0, 0, 0, 0, 0)
Definition: dimensionSets.H:49
GeometricField< scalar, fvsPatchField, surfaceMesh > surfaceScalarField
#define NotImplemented
Issue a FatalErrorIn for a function not currently implemented.
Definition: error.H:696
Defines the attributes of an object for which implicit objectRegistry management is supported...
Definition: IOobject.H:180
dictionary coeffs_
Dictionary containing source coefficients.
Definition: fvOption.H:152
bool found
surfaceScalarField rhof(fvc::interpolate(rho, "div(phi,rho)"))
Do not request registration (bool: false)
void reduce(T &value, const BinaryOp &bop, const int tag=UPstream::msgType(), const label comm=UPstream::worldComm)
Reduce inplace (cf. MPI Allreduce) using linear/tree communication schedule.
const dimensionSet dimArea(sqr(dimLength))
Definition: dimensionSets.H:57
uindirectPrimitivePatch pp(UIndirectList< face >(mesh.faces(), faceLabels), mesh.points())
Namespace for OpenFOAM.
virtual bool read(const dictionary &dict)
Read source dictionary.
IOerror FatalIOError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL IO ERROR&#39; header text and ...
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127
const dimensionSet dimVelocity