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-------------------------------------------------------------------------------
10License
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"
36
37// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
38
39namespace Foam
40{
41namespace functionObjects
42{
45}
46}
47
48
49const Foam::Enum
50<
52>
54({
55 { actionType::opNone, "none" },
56 { actionType::opNew, "new" },
57 { actionType::opModify, "modify" },
58});
59
60
61// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
62
63namespace 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
79template<class Type>
81(
82 bool correctBCs,
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 {
95 {
96 pf = pf.patchInternalField();
97 }
98 }
99 }
100}
101
102
103template<class Type>
105(
106 bool correctBCs,
108)
109{
110 if (correctBCs)
111 {
112 // Info<< "Correcting boundary conditions: " << field.name() << nl;
113 field.correctBoundaryConditions();
114 }
115}
116
117
118template<class Type>
120(
121 bool correctBCs,
123)
124{}
125
126} // End namespace Foam
127
128
129// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
130
131template<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
148template<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;
176 << "readFields : "
177 << ptr->name() << " (" << ptr->type()
178 << ") already in database" << endl;
179 continue;
180 }
181
182 // Load field as necessary
184 (
185 fieldName,
186 mesh_.time().timeName(),
187 mesh_,
190 );
191
192 const bool ok =
193 (
194 io.typeHeaderOk<regIOobject>(false) // Preload header info
195 && io.hasHeaderClass() // Extra safety
196 &&
197 (
198 loadField<scalar>(io)
199 || loadField<vector>(io)
200 || loadField<sphericalTensor>(io)
201 || loadField<symmTensor>(io)
202 || loadField<tensor>(io)
203 )
204 );
205
206 if (ok)
207 {
208 ++nLoaded;
209 }
210 else
211 {
213 << "readFields : failed to load " << fieldName << endl;
214 }
215 }
216
217 return nLoaded;
218}
219
220
221template<class GeoField>
223(
224 GeoField& output,
225 const GeoField& evaluated,
226 const boolField& fieldMask
227)
228{
229 label numValuesChanged = 0;
230
231 // Internal field
232 if (fieldMask.empty())
233 {
234 // No field-mask - set all
235 numValuesChanged = output.size();
236
237 output.primitiveFieldRef() = evaluated;
238 }
239 else
240 {
241 auto& internal = output.primitiveFieldRef();
242
243 forAll(internal, idx)
244 {
245 if (fieldMask[idx])
246 {
247 internal[idx] = evaluated[idx];
248 ++numValuesChanged;
249 }
250 }
251 }
252
253 // Boundary fields
254 forAll(evaluated.boundaryField(), patchi)
255 {
256 auto& pf = output.boundaryFieldRef()[patchi];
257
258 if (pf.patch().coupled())
259 {
260 pf == evaluated.boundaryField()[patchi];
261 }
262 }
263
265
266 if (action_ == actionType::opModify && log)
267 {
268 const label numTotal = returnReduce(output.size(), sumOp<label>());
269 reduce(numValuesChanged, sumOp<label>());
270
271 Info<< this->name() << ": set ";
272 if (numValuesChanged == numTotal)
273 {
274 Info<< "all ";
275 }
276 else
277 {
278 Info<< numValuesChanged << " of ";
279 }
280 Info<< numTotal << " values (field: "
281 << output.name() << ')' << nl << endl;
282 }
283
284 if (hasDimensions_)
285 {
286 // Log<< "Setting dimensions to " << dims << endl;
287 output.dimensions().reset(dimensions_);
288 }
289
290 return true;
291}
292
293
294// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
295
297(
298 const word& name,
299 const Time& runTime,
300 const dictionary& dict,
301 const bool loadFromFiles
302)
303:
305 dict_(dict), // Deep copy
306 fieldName_(),
307 preloadFields_(),
308 maskExpr_(),
309 valueExpr_(),
310 dimensions_(),
311 action_(actionType::opNew),
312 autowrite_(false),
313 store_(true),
314 hasDimensions_(false),
315 loadFromFiles_(loadFromFiles)
316{
317 read(dict);
318}
319
320
321// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
322
324{}
325
326
327// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
328
330{
331 switch (action_)
332 {
333 case actionType::opNone:
334 {
335 break; // No-op
336 }
337 case actionType::opNew:
338 {
339 return scopedName(fieldName_);
340 }
341 case actionType::opModify:
342 {
343 return fieldName_;
344 }
345 }
346
347 return word::null;
348}
349
350
352{
354
355 action_ = actionNames_.getOrDefault("action", dict, actionType::opNew);
356
357 fieldName_ = dict.get<word>("field");
358 const word fldName = fieldName();
359
360 Log << type() << ' ' << this->name() << ':' << nl
361 << " action = " << actionNames_[action_] << nl
362 << " field = " << fldName << nl;
363
364 maskExpr_.clear();
365 valueExpr_.clear();
366
367 preloadFields_.clear();
368 dict.readIfPresent("readFields", preloadFields_);
369
370 switch (action_)
371 {
372 case actionType::opNone:
373 {
374 // No-op
375 break;
376 }
377 case actionType::opModify:
378 {
379 // Optional <fieldMask> for modify
380 maskExpr_.readEntry("fieldMask", dict, false);
381 [[fallthrough]];
382 }
383 case actionType::opNew:
384 {
385 // Mandatory <expression> for new and modify
386 valueExpr_.readEntry("expression", dict);
387 break;
388 }
389 }
390
391 autowrite_ = dict.getOrDefault("autowrite", false);
392 store_ = dict.getOrDefault("autowrite", true);
393
394 // "dimensions" is optional
395 dimensions_.clear();
396 hasDimensions_ = dimensions_.readEntry("dimensions", dict, false);
397
398 if (action_ == actionType::opNew)
399 {
400 if (!hasDimensions_)
401 {
402 Log << " no 'dimensions' : treat '" << fldName
403 << "' as dimensionless" << endl;
404 }
405 }
406 else
407 {
408 // Ignore for none/modify
409 hasDimensions_ = false;
410 }
411
412
413 if (action_ == actionType::opNone)
414 {
415 driver_.reset(nullptr);
416 return true; // Done
417 }
418
419 driver_.reset
420 (
421 new expressions::volumeExprDriver(mesh_, dict_)
422 );
423
424 driver_->setSearchBehaviour
425 (
427 (
429 | (
430 loadFromFiles_
432 : int(0)
433 )
434 ),
435 false // No caching
436 );
437
438 driver_->readDict(dict_);
439
440 return true;
441}
442
443
445{
447
448 if (!driver_ || action_ == actionType::opNone)
449 {
450 // No-op
451 return true;
452 }
453
454 const word fldName = fieldName();
455
456 if (loadFromFiles_)
457 {
458 loadFields(preloadFields_);
459 }
460
461 if (action_ == actionType::opModify && loadFromFiles_)
462 {
463 loadFields(wordList({fldName}));
464 }
465
466 auto& driver = *driver_;
467
468
469 // Current availability
470 auto* regIOobjectPtr = mesh_.getObjectPtr<regIOobject>(fldName);
471
472 if (action_ == actionType::opModify && !regIOobjectPtr)
473 {
474 // Cannot continue
476 << type() << ' ' << this->name() << ':' << nl
477 << " missing-field: " << fldName << nl
478 << exit(FatalError);
479
480 return false;
481 }
482
483
484 // Handle "field-mask" evaluation
485 bool evalFieldMask
486 (
487 (action_ == actionType::opModify)
488 && maskExpr_.size() && maskExpr_ != "true" && maskExpr_ != "1"
489 );
490
491 boolField fieldMask;
492 FieldAssociation maskFieldAssoc(FieldAssociation::NO_DATA);
493
494 if (evalFieldMask)
495 {
496 driver.parse(maskExpr_);
497
498 if (driver.isLogical())
499 {
500 auto& result = driver.result();
501 if (result.is_bool())
502 {
503 fieldMask = result.getResult<bool>();
504 maskFieldAssoc = driver.fieldAssociation();
505 }
506 }
507
508 // Slightly pedantic...
509 driver.clearField();
510 driver.clearResult();
511
512 evalFieldMask = (maskFieldAssoc != FieldAssociation::NO_DATA);
513
514 if (!evalFieldMask)
515 {
517 << "field-mask: " << maskExpr_
518 << " does not evaluate to a logical expression: "
519 << driver.resultType() << nl
520 #ifdef FULLDEBUG
521 << "contents: " << fieldMask
522 #endif
523 << exit(FatalError);
524 }
525 }
526
527
528 // Start "expression" evaluation
529
530 bool applied = false;
531 autoPtr<regIOobject> toutputField;
532
533 {
534 driver.clearVariables();
535
536 driver.parse(valueExpr_);
537
538 if (evalFieldMask && maskFieldAssoc != driver.fieldAssociation())
539 {
541 << "Mismatch between field-mask geometric type ("
542 << fieldGeoType(maskFieldAssoc) << ") and" << nl
543 << "expression geometric type ("
544 << fieldGeoType(driver.fieldAssociation()) << ')' << nl
545 << nl
546 << "Expression: " << valueExpr_ << nl
547 << "Field-mask: " << maskExpr_ << nl
548 << nl
549 << exit(FatalError);
550 }
551
552 // The output field does not appear to exist
553 // - create a new 'blank slate'
554 if (!regIOobjectPtr)
555 {
556 toutputField.reset(driver.dupZeroField());
557
558 if (toutputField)
559 {
560 toutputField->rename(fldName);
561
562 if (autowrite_)
563 {
564 toutputField->writeOpt(IOobject::AUTO_WRITE);
565 }
566 }
567
568 if (!store_)
569 {
570 // Local (non-registered) field only
571 regIOobjectPtr = toutputField.get();
572 }
573 else
574 {
575 if (toutputField->checkIn() && toutputField->store())
576 {
577 // Register and transfer ownership to registry
578 toutputField.release();
579 }
580
581 regIOobjectPtr = mesh_.getObjectPtr<regIOobject>(fldName);
582 }
583 }
584
585
586 // Additional checks (TBD):
587
588 if (!regIOobjectPtr)
589 {
590 // Cannot continue
592 << type() << ' ' << this->name() << ':' << nl
593 << " missing-field: " << fldName << nl
594 << exit(FatalError);
595 }
596
597 // const word oldFieldType = regIOobjectPtr->type();
598
599 // if (driver.resultType() != oldFieldType)
600 // {
601 // FatalErrorInFunction
602 // << "Inconsistent types: " << fldName << " is "
603 // << oldFieldType
604 // << " but the expression evaluates to "
605 // << driver.resultType()
606 // << exit(FatalError);
607 // }
608
609 switch (driver.fieldAssociation())
610 {
611 #undef doLocalCode
612 #define doLocalCode(GeoField) \
613 { \
614 /* FieldType */ \
615 auto* outPtr = dynamic_cast<GeoField*>(regIOobjectPtr); \
616 const auto* ptr = driver.isResultType<GeoField>(); \
617 \
618 if (outPtr && ptr) \
619 { \
620 applied = setField(*outPtr, *ptr, fieldMask); \
621 if (doWrite) \
622 { \
623 outPtr->write(); \
624 } \
625 break; \
626 } \
627 }
628
629 case FieldAssociation::VOLUME_DATA:
630 {
636 break;
637 }
638 case FieldAssociation::FACE_DATA:
639 {
645 break;
646 }
647 case FieldAssociation::POINT_DATA:
648 {
654 break;
655 }
656
657 default: break;
658 #undef doLocalCode
659 }
660 }
661
662
663 // Clear out heavier data
664 driver.clearResult();
665 driver.clearField();
666
667 if (!applied)
668 {
669 // Or error?
671 << type() << ' ' << this->name() << ": Failed to apply "
672 << actionNames_[action_] << " for " << fldName
673 << nl;
674 }
675
676 return true;
677}
678
679
681{
682 return performAction(false);
683}
684
685
687{
688 return performAction(true);
689}
690
691
692// ************************************************************************* //
#define Log
Definition: PDRblock.C:35
Macros for easy insertion into run-time selection tables.
#define addToRunTimeSelectionTable(baseType, thisType, argNames)
Add to construction table with typeName as the key.
Enum is a wrapper around a list of names/values that represent particular enumeration (or int) values...
Definition: Enum.H:61
Generic GeometricField class.
Defines the attributes of an object for which implicit objectRegistry management is supported,...
Definition: IOobject.H:170
bool isHeaderClass() const
Check if headerClassName() equals Type::typeName.
Definition: IOobjectI.H:156
const word & name() const noexcept
Return the object name.
Definition: IOobjectI.H:65
virtual const fileName & name() const
Return the name of the stream.
Definition: IOstream.C:40
virtual bool read()
Re-read model coefficients if they have changed.
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:80
A 1D vector of objects of type <T>, where the size of the vector is known and can be used for subscri...
Definition: UList.H:94
bool empty() const noexcept
True if the UList is empty (ie, size() is zero)
Definition: UListI.H:427
Pointer management similar to std::unique_ptr, with some additional methods and type checking.
Definition: autoPtr.H:66
T * get() noexcept
Return pointer to managed object without nullptr checking.
Definition: autoPtr.H:152
T * release() noexcept
Release ownership and return the pointer.
Definition: autoPtrI.H:100
void reset(autoPtr< T > &&other) noexcept
Delete managed object and set to new given pointer.
Definition: autoPtrI.H:117
This boundary condition is not designed to be evaluated; it is assmued that the value is assigned via...
A list of keyword definitions, which are a keyword followed by a number of values (eg,...
Definition: dictionary.H:126
The field association for mesh (patch/volume) values.
searchControls
Search/caching controls.
Definition: exprDriver.H:148
@ SEARCH_REGISTRY
Search registry before disk.
Definition: exprDriver.H:150
@ SEARCH_FILES
Search disk (eg, standalone app)
Definition: exprDriver.H:151
Driver for volume, surface, point field expressions.
Abstract base-class for Time/database function objects.
Function object that generates or modifies a field based on expressions.
bool setField(GeoField &output, const GeoField &evaluated, const boolField &cond)
virtual bool read(const dictionary &dict)
Read the data.
bool loadAndStore(const IOobject &io)
Attempt load from io, store on database if successful.
static const Enum< actionType > actionNames_
Action type names.
label loadFields(const UList< word > &fieldSet_)
Attempt to load specified fields.
virtual word fieldName() const
Qualified/unqualified field name (depends on action)
bool loadField(const IOobject &io)
Forward to loadAndStore for supported types.
Specialization of Foam::functionObject for an Foam::fvMesh, providing a reference to the Foam::fvMesh...
const fvMesh & mesh_
Reference to the fvMesh.
Computes the natural logarithm of an input volScalarField.
Definition: log.H:230
regIOobject is an abstract class derived from IOobject to handle automatic object registration with t...
Definition: regIOobject.H:76
A class for handling words, derived from Foam::string.
Definition: word.H:68
static const word null
An empty word.
Definition: word.H:80
#define defineTypeNameAndDebug(Type, DebugSwitch)
Define the typeName and debug information.
Definition: className.H:121
rDeltaTY field()
engineTime & runTime
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:453
IOobject io("surfaceFilmProperties", mesh.time().constant(), mesh, IOobject::READ_IF_PRESENT, IOobject::NO_WRITE, false)
#define DebugInfo
Report an information message using Foam::Info.
#define WarningInFunction
Report a warning using Foam::Warning.
@ VOLUME_DATA
Volume data.
Namespace for OpenFOAM.
List< word > wordList
A List of words.
Definition: fileName.H:63
const TargetType * isA(const Type &t)
Check if dynamic_cast to TargetType is possible.
Definition: typeInfo.H:197
messageStream Info
Information stream (stdout output on master, null elsewhere)
fileName::Type type(const fileName &name, const bool followLink=true)
Return the file type: DIRECTORY or FILE, normally following symbolic links.
Definition: MSwindows.C:598
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:372
void reduce(const List< UPstream::commsStruct > &comms, T &value, const BinaryOp &bop, const int tag, const label comm)
static Ostream & output(Ostream &os, const IntRange< T > &range)
Definition: IntRanges.C:66
static void doCorrectBoundaryConditions(bool correctBCs, VolumeField< Type > &field)
word fieldGeoType(const expressions::FieldAssociation geoType)
error FatalError
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:59
T returnReduce(const T &value, const BinaryOp &bop, const int tag=UPstream::msgType(), const label comm=UPstream::worldComm)
Reduce (copy) and return value.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:130
constexpr char nl
The newline '\n' character (0x0a)
Definition: Ostream.H:53
dictionary dict
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:333
Foam::surfaceFields.
#define doLocalCode(GeoField)