fvExpressionField.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) 2021-2022 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 \*---------------------------------------------------------------------------*/
27 
28 #include "fvExpressionField.H"
29 #include "volFields.H"
30 #include "surfaceFields.H"
31 #include "pointMesh.H"
32 #include "pointFields.H"
33 #include "volumeExprDriver.H"
34 #include "calculatedFvPatchField.H"
36 
37 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
38 
39 namespace Foam
40 {
41 namespace functionObjects
42 {
43  defineTypeNameAndDebug(fvExpressionField, 0);
44  addToRunTimeSelectionTable(functionObject, fvExpressionField, dictionary);
45 }
46 }
47 
48 
49 const Foam::Enum
50 <
52 >
54 ({
55  { actionType::opNone, "none" },
56  { actionType::opNew, "new" },
57  { actionType::opModify, "modify" },
58 });
59 
60 
61 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
62 
63 namespace Foam
64 {
65 
67 {
68  switch (geoType)
69  {
70  case expressions::FieldAssociation::POINT_DATA : return "points"; break;
71  case expressions::FieldAssociation::FACE_DATA : return "faces"; break;
72  case expressions::FieldAssociation::VOLUME_DATA : return "cells"; break;
73  default: break;
74  }
75  return "unknown";
76 }
77 
78 
79 template<class Type>
81 (
82  bool correctBCs,
83  VolumeField<Type>& field
84 )
85 {
86  if (correctBCs)
87  {
88  // Info<< "Correcting boundary conditions: " << field.name() << nl;
89  field.correctBoundaryConditions();
90 
91  // Ensure that calculated patches are updated
92  for (auto& pf : field.boundaryFieldRef())
93  {
94  if (isA<calculatedFvPatchField<Type>>(pf))
95  {
96  pf = pf.patchInternalField();
97  }
98  }
99  }
100 }
101 
102 
103 template<class Type>
105 (
106  bool correctBCs,
107  PointField<Type>& field
108 )
109 {
110  if (correctBCs)
111  {
112  // Info<< "Correcting boundary conditions: " << field.name() << nl;
113  field.correctBoundaryConditions();
114  }
115 }
116 
117 
118 template<class Type>
120 (
121  bool correctBCs,
122  SurfaceField<Type>& field
123 )
124 {}
126 } // End namespace Foam
127 
128 
129 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
130 
131 template<class FieldType>
133 {
134  if (io.isHeaderClass<FieldType>())
135  {
136  // Store field on mesh database
137  Log << " Reading " << io.name()
138  << " (" << FieldType::typeName << ')' << endl;
139 
140  mesh_.objectRegistry::store(new FieldType(io, mesh_));
141  return true;
142  }
143 
144  return false;
145 }
146 
147 
148 template<class Type>
150 {
151  return
152  (
153  loadAndStore<VolumeField<Type>>(io)
155  || loadAndStore<SurfaceField<Type>>(io)
156  );
157 }
158 
159 
161 (
162  const UList<word>& fieldSet_
163 )
164 {
165  label nLoaded = 0;
166 
167  for (const word& fieldName : fieldSet_)
168  {
169  // Already loaded?
170  const auto* ptr = mesh_.cfindObject<regIOobject>(fieldName);
171 
172  if (ptr)
173  {
174  ++nLoaded;
175  DebugInfo
176  << "readFields : "
177  << ptr->name() << " (" << ptr->type()
178  << ") already in database" << endl;
179  continue;
180  }
181 
182  // Load field as necessary
183  IOobject io
184  (
185  fieldName,
186  mesh_.time().timeName(),
187  mesh_,
190  );
191 
192  const bool ok =
193  (
194  io.typeHeaderOk<regIOobject>(false)
195  &&
196  (
197  loadField<scalar>(io)
198  || loadField<vector>(io)
199  || loadField<sphericalTensor>(io)
200  || loadField<symmTensor>(io)
201  || loadField<tensor>(io)
202  )
203  );
204 
205  if (ok)
206  {
207  ++nLoaded;
208  }
209  else
210  {
211  DebugInfo
212  << "readFields : failed to load " << fieldName << endl;
213  }
214  }
216  return nLoaded;
217 }
218 
219 
220 template<class GeoField>
222 (
223  GeoField& output,
224  const GeoField& evaluated,
225  const boolField& fieldMask
226 )
227 {
228  label numValuesChanged = 0;
229 
230  // Internal field
231  if (fieldMask.empty())
232  {
233  // No field-mask - set all
234  numValuesChanged = output.size();
235 
236  output.primitiveFieldRef() = evaluated;
237  }
238  else
239  {
240  auto& internal = output.primitiveFieldRef();
241 
242  forAll(internal, idx)
243  {
244  if (fieldMask[idx])
245  {
246  internal[idx] = evaluated[idx];
247  ++numValuesChanged;
248  }
249  }
250  }
251 
252  // Boundary fields
253  forAll(evaluated.boundaryField(), patchi)
254  {
255  auto& pf = output.boundaryFieldRef()[patchi];
256 
257  if (pf.patch().coupled())
258  {
259  pf == evaluated.boundaryField()[patchi];
260  }
261  }
262 
264 
265  if (action_ == actionType::opModify && log)
266  {
267  const label numTotal = returnReduce(output.size(), sumOp<label>());
268  reduce(numValuesChanged, sumOp<label>());
269 
270  Info<< this->name() << ": set ";
271  if (numValuesChanged == numTotal)
272  {
273  Info<< "all ";
274  }
275  else
276  {
277  Info<< numValuesChanged << " of ";
278  }
279  Info<< numTotal << " values (field: "
280  << output.name() << ')' << nl << endl;
281  }
282 
283  if (hasDimensions_)
284  {
285  // Log<< "Setting dimensions to " << dims << endl;
286  output.dimensions().reset(dimensions_);
287  }
288 
289  return true;
290 }
291 
292 
293 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
294 
296 (
297  const word& name,
298  const Time& runTime,
299  const dictionary& dict,
300  const bool loadFromFiles
301 )
302 :
304  dict_(dict), // Deep copy
305  fieldName_(),
306  preloadFields_(),
307  maskExpr_(),
308  valueExpr_(),
309  dimensions_(),
310  action_(actionType::opNew),
311  autowrite_(false),
312  store_(true),
313  hasDimensions_(false),
314  loadFromFiles_(loadFromFiles)
315 {
316  read(dict);
317 }
318 
319 
320 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
323 {}
324 
325 
326 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
327 
329 {
330  switch (action_)
331  {
332  case actionType::opNone:
333  {
334  break; // No-op
335  }
336  case actionType::opNew:
337  {
338  return scopedName(fieldName_);
339  }
340  case actionType::opModify:
341  {
342  return fieldName_;
343  }
344  }
345 
346  return word::null;
347 }
348 
349 
351 {
353 
354  action_ = actionNames_.getOrDefault("action", dict, actionType::opNew);
355 
356  fieldName_ = dict.get<word>("field");
357  const word fldName = fieldName();
358 
359  Log << type() << ' ' << this->name() << ':' << nl
360  << " action = " << actionNames_[action_] << nl
361  << " field = " << fldName << nl;
362 
363  maskExpr_.clear();
364  valueExpr_.clear();
365 
366  preloadFields_.clear();
367  dict.readIfPresent("readFields", preloadFields_);
368 
369  switch (action_)
370  {
371  case actionType::opNone:
372  {
373  // No-op
374  break;
375  }
376  case actionType::opModify:
377  {
378  // Optional <fieldMask> for modify
379  maskExpr_.readIfPresent("fieldMask", dict);
380  [[fallthrough]];
381  }
382  case actionType::opNew:
383  {
384  // Mandatory <expression> for new and modify
385  valueExpr_.readEntry("expression", dict);
386  break;
387  }
388  }
389 
390  autowrite_ = dict.getOrDefault("autowrite", false);
391  store_ = dict.getOrDefault("store", true);
392 
393  // "dimensions" is optional
394  dimensions_.clear();
395  hasDimensions_ = dimensions_.readIfPresent("dimensions", dict);
396 
397  if (action_ == actionType::opNew)
398  {
399  if (!hasDimensions_)
400  {
401  Log << " no 'dimensions' : treat '" << fldName
402  << "' as dimensionless" << endl;
403  }
404  }
405  else
406  {
407  // Ignore for none/modify
408  hasDimensions_ = false;
409  }
410 
411 
412  if (action_ == actionType::opNone)
413  {
414  driver_.reset(nullptr);
415  return true; // Done
416  }
417 
418  driver_.reset
419  (
420  new expressions::volumeExprDriver(mesh_, dict_)
421  );
422 
423  driver_->setSearchBehaviour
424  (
426  (
428  | (
429  loadFromFiles_
431  : int(0)
432  )
433  ),
434  false // No caching
435  );
437  driver_->readDict(dict_);
438 
439  return true;
440 }
441 
442 
444 {
446 
447  if (!driver_ || action_ == actionType::opNone)
448  {
449  // No-op
450  return true;
451  }
452 
453  const word fldName = fieldName();
454 
455  if (loadFromFiles_)
456  {
457  loadFields(preloadFields_);
458  }
459 
460  if (action_ == actionType::opModify && loadFromFiles_)
461  {
462  loadFields(wordList({fldName}));
463  }
464 
465  auto& driver = *driver_;
466 
467 
468  // Current availability
469  auto* regIOobjectPtr = mesh_.getObjectPtr<regIOobject>(fldName);
470 
471  if (action_ == actionType::opModify && !regIOobjectPtr)
472  {
473  // Cannot continue
475  << type() << ' ' << this->name() << ':' << nl
476  << " missing-field: " << fldName << nl
477  << exit(FatalError);
478 
479  return false;
480  }
481 
482 
483  // Handle "field-mask" evaluation
484  bool evalFieldMask
485  (
486  (action_ == actionType::opModify)
487  && maskExpr_.size() && maskExpr_ != "true" && maskExpr_ != "1"
488  );
489 
490  boolField fieldMask;
492 
493  if (evalFieldMask)
494  {
495  driver.parse(maskExpr_);
496 
497  if (driver.isLogical())
498  {
499  auto& result = driver.result();
500  if (result.is_bool())
501  {
502  fieldMask = result.getResult<bool>();
503  maskFieldAssoc = driver.fieldAssociation();
504  }
505  }
506 
507  // Slightly pedantic...
508  driver.clearField();
509  driver.clearResult();
510 
511  evalFieldMask = (maskFieldAssoc != FieldAssociation::NO_DATA);
512 
513  if (!evalFieldMask)
514  {
516  << "field-mask: " << maskExpr_
517  << " does not evaluate to a logical expression: "
518  << driver.resultType() << nl
519  #ifdef FULLDEBUG
520  << "contents: " << fieldMask
521  #endif
522  << exit(FatalError);
523  }
524  }
525 
526 
527  // Start "expression" evaluation
528 
529  bool applied = false;
530  autoPtr<regIOobject> toutputField;
531 
532  {
533  driver.clearVariables();
534 
535  driver.parse(valueExpr_);
536 
537  if (evalFieldMask && maskFieldAssoc != driver.fieldAssociation())
538  {
540  << "Mismatch between field-mask geometric type ("
541  << fieldGeoType(maskFieldAssoc) << ") and" << nl
542  << "expression geometric type ("
543  << fieldGeoType(driver.fieldAssociation()) << ')' << nl
544  << nl
545  << "Expression: " << valueExpr_ << nl
546  << "Field-mask: " << maskExpr_ << nl
547  << nl
548  << exit(FatalError);
549  }
550 
551  // The output field does not appear to exist
552  // - create a new 'blank slate'
553  if (!regIOobjectPtr)
554  {
555  toutputField.reset(driver.dupZeroField());
556 
557  if (toutputField)
558  {
559  toutputField->rename(fldName);
560 
561  if (autowrite_)
562  {
563  toutputField->writeOpt(IOobject::AUTO_WRITE);
564  }
565  }
566 
567  if (!store_)
568  {
569  // Local (non-registered) field only
570  regIOobjectPtr = toutputField.get();
571  }
572  else
573  {
574  if (toutputField->checkIn() && toutputField->store())
575  {
576  // Register and transfer ownership to registry
577  toutputField.release();
578  }
579 
580  regIOobjectPtr = mesh_.getObjectPtr<regIOobject>(fldName);
581  }
582  }
583 
584 
585  // Additional checks (TBD):
586 
587  if (!regIOobjectPtr)
588  {
589  // Cannot continue
591  << type() << ' ' << this->name() << ':' << nl
592  << " missing-field: " << fldName << nl
593  << exit(FatalError);
594  }
595 
596  // const word oldFieldType = regIOobjectPtr->type();
597 
598  // if (driver.resultType() != oldFieldType)
599  // {
600  // FatalErrorInFunction
601  // << "Inconsistent types: " << fldName << " is "
602  // << oldFieldType
603  // << " but the expression evaluates to "
604  // << driver.resultType()
605  // << exit(FatalError);
606  // }
607 
608  switch (driver.fieldAssociation())
609  {
610  #undef doLocalCode
611  #define doLocalCode(GeoField) \
612  { \
613  /* FieldType */ \
614  auto* outPtr = dynamic_cast<GeoField*>(regIOobjectPtr); \
615  const auto* ptr = driver.isResultType<GeoField>(); \
616  \
617  if (outPtr && ptr) \
618  { \
619  applied = setField(*outPtr, *ptr, fieldMask); \
620  if (doWrite) \
621  { \
622  outPtr->write(); \
623  } \
624  break; \
625  } \
626  }
627 
629  {
635  break;
636  }
638  {
644  break;
645  }
647  {
653  break;
654  }
655 
656  default: break;
657  #undef doLocalCode
658  }
659  }
660 
661 
662  // Clear out heavier data
663  driver.clearResult();
664  driver.clearField();
665 
666  if (!applied)
667  {
668  // Or error?
670  << type() << ' ' << this->name() << ": Failed to apply "
671  << actionNames_[action_] << " for " << fldName
672  << nl;
673  }
674 
675  return true;
676 }
677 
680 {
681  return performAction(false);
682 }
683 
684 
686 {
687  return performAction(true);
688 }
689 
690 
691 // ************************************************************************* //
Foam::surfaceFields.
bool setField(GeoField &output, const GeoField &evaluated, const boolField &cond)
GeometricField< symmTensor, fvPatchField, volMesh > volSymmTensorField
Definition: volFieldsFwd.H:85
dictionary dict
word fieldGeoType(const expressions::FieldAssociation geoType)
defineTypeNameAndDebug(ObukhovLength, 0)
virtual word fieldName() const
Qualified/unqualified field name (depends on action)
rDeltaTY field()
virtual const fileName & name() const
The name of the stream.
Definition: IOstream.C:33
dimensionedScalar log(const dimensionedScalar &ds)
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
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
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:598
The field association for mesh (patch/volume) values.
bool loadField(const IOobject &io)
Forward to loadAndStore for supported types.
GeometricField< tensor, fvPatchField, volMesh > volTensorField
Definition: volFieldsFwd.H:86
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
bool empty() const noexcept
True if List is empty (ie, size() is zero)
Definition: UList.H:666
GeometricField< vector, fvsPatchField, surfaceMesh > surfaceVectorField
GeometricField< sphericalTensor, fvsPatchField, surfaceMesh > surfaceSphericalTensorField
engineTime & runTime
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
label loadFields(const UList< word > &fieldSet_)
Attempt to load specified fields.
GeometricField< vector, pointPatchField, pointMesh > pointVectorField
Generic GeometricField class.
Definition: areaFieldsFwd.H:50
Ignore writing from objectRegistry::writeObject()
static void doCorrectBoundaryConditions(bool correctBCs, VolumeField< Type > &field)
GeometricField< vector, fvPatchField, volMesh > volVectorField
Definition: volFieldsFwd.H:82
T returnReduce(const T &value, const BinaryOp &bop, const int tag=UPstream::msgType(), const label comm=UPstream::worldComm)
Perform reduction on a copy, using specified binary operation.
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:69
GeometricField< sphericalTensor, fvPatchField, volMesh > volSphericalTensorField
Definition: volFieldsFwd.H:84
Macros for easy insertion into run-time selection tables.
GeometricField< tensor, pointPatchField, pointMesh > pointTensorField
bool read(const char *buf, int32_t &val)
Same as readInt32.
Definition: int32.H:127
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
searchControls
Search/caching controls.
Definition: exprDriver.H:145
GeometricField< scalar, fvPatchField, volMesh > volScalarField
Definition: volFieldsFwd.H:81
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
Field< bool > boolField
Specialisation of Field<T> for bool.
Definition: boolField.H:47
#define doLocalCode(GeoField)
GeometricField< symmTensor, fvsPatchField, surfaceMesh > surfaceSymmTensorField
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for expressions::valueTypeCode::INVALID.
Definition: exprTraits.C:127
volumeExpr::parseDriver volumeExprDriver
Typedef for volumeExpr parseDriver.
Definition: volumeExprFwd.H:59
A class for handling words, derived from Foam::string.
Definition: word.H:63
GeometricField< tensor, fvsPatchField, surfaceMesh > surfaceTensorField
static const word null
An empty word.
Definition: word.H:84
GeometricField< scalar, pointPatchField, pointMesh > pointScalarField
#define DebugInfo
Report an information message using Foam::Info.
addToRunTimeSelectionTable(functionObject, ObukhovLength, dictionary)
Search disk (eg, standalone app)
Definition: exprDriver.H:149
bool loadAndStore(const IOobject &io)
Attempt load from io, store on database if successful.
GeometricField< sphericalTensor, pointPatchField, pointMesh > pointSphericalTensorField
List< word > wordList
List of word.
Definition: fileName.H:59
#define WarningInFunction
Report a warning using Foam::Warning.
Enum is a wrapper around a list of names/values that represent particular enumeration (or int) values...
Definition: error.H:64
Automatically write from objectRegistry::writeObject()
#define Log
Definition: PDRblock.C:28
void reduce(const List< UPstream::commsStruct > &comms, T &value, const BinaryOp &bop, const int tag, const label comm)
Reduce inplace (cf. MPI Allreduce) using specified communication schedule.
messageStream Info
Information stream (stdout output on master, null elsewhere)
virtual bool read(const dictionary &dict)
Read optional controls.
static Ostream & output(Ostream &os, const IntRange< T > &range)
Definition: IntRanges.C:44
Specialization of Foam::functionObject for an Foam::fvMesh, providing a reference to the Foam::fvMesh...
const Type * isA(const U &obj)
Check if dynamic_cast to Type is possible.
Definition: typeInfo.H:88
virtual bool read(const dictionary &dict)
Read the data.
IOobject io("surfaceFilmProperties", mesh.time().constant(), mesh, IOobject::READ_IF_PRESENT, IOobject::NO_WRITE, IOobject::NO_REGISTER)
GeometricField< scalar, fvsPatchField, surfaceMesh > surfaceScalarField
Defines the attributes of an object for which implicit objectRegistry management is supported...
Definition: IOobject.H:172
static const Enum< actionType > actionNames_
Action type names.
GeometricField< symmTensor, pointPatchField, pointMesh > pointSymmTensorField
fvExpressionField(const word &name, const Time &runTime, const dictionary &dict, const bool loadFromFiles=false)
Construct from Time and dictionary.
const fvMesh & mesh_
Reference to the fvMesh.
Namespace for OpenFOAM.