timeVaryingMappedFvPatchField.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2011-2026 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
28 #include "AverageField.H"
29 #include "IFstream.H"
30 #include "OSspecific.H"
31 
32 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
33 
34 template<class Type>
37 (
38  const word& timeName
39 ) const
40 {
41  const fileName fieldFileName
42  (
43  dataDir_/timeName/sampleName_/fieldTableName_
44  );
45 
46  const fileName typeFieldFileName
47  (
48  dataDir_/timeName/sampleName_
50  /fieldTableName_
51  );
52 
53  if (exists(fieldFileName))
54  {
55  return fieldFileName;
56  }
57  else if (exists(typeFieldFileName))
58  {
59  return typeFieldFileName;
60  }
61  else
62  {
64  << "Cannot find field file "
65  << fieldFileName << " " << typeFieldFileName
66  << exit(FatalError);
67 
68  return fileName::null;
69  }
70 }
71 
72 
73 template<class Type>
76 (
77  const word& timeName
78 ) const
79 {
80  // Points file specific to this sample time, provided if the surface
81  // from which the data was obtained changes in time
82  const fileName timePointsFile
83  (
84  dataDir_/timeName/sampleName_/pointsName_.name()
85  );
86 
87  // Static points file name used for all sample times
88  const fileName pointsFile(dataDir_/pointsName_);
89 
90  if (exists(timePointsFile))
91  {
92  return timePointsFile;
93  }
94  else if (exists(pointsFile))
95  {
96  return pointsFile;
97  }
98  else
99  {
101  << "Cannot find points file "
102  << timePointsFile << " " << pointsFile
103  << exit(FatalError);
104 
105  return fileName::null;
106  }
107 }
108 
109 
110 template<class Type>
113 {
114  const fileName pointsFile(findPointsFile(timeName));
115 
116  // If the points file has changed update the mapper
117  if (mapperPtr_.empty() || mapperPointsFile_ != pointsFile)
118  {
119  // Reread the sample points
120  const pointField samplePoints((IFstream(pointsFile)()));
121 
122  if (debug)
123  {
124  Info<< "timeVaryingMappedFvPatchField :"
125  << " Read " << samplePoints.size() << " sample points from "
126  << pointsFile << endl;
127  }
128 
129  // tbd: run-time selection
130  const bool nearestOnly
131  (
132  !mapMethod_.empty()
133  && mapMethod_ != "planarInterpolation"
134  );
135 
136  // Allocate the interpolator
137  mapperPtr_.reset
138  (
139  new pointToPointPlanarInterpolation
140  (
141  samplePoints,
142  patch_.poly().faceCentres(),
143  perturb_,
144  nearestOnly
145  )
146  );
147 
148  mapperPointsFile_ = pointsFile;
149  }
150 
151  return mapperPtr_();
152 }
153 
154 
155 template<class Type>
157 {
158  // Initialise
159  if (startSampleTime_ == -1 && endSampleTime_ == -1)
160  {
161  // Read the times for which data is available
162  sampleTimes_ = patch_.time().findTimes(dataDir_);
163 
164  if (debug)
165  {
166  Info<< "timeVaryingMappedFvPatchField : In directory "
167  << dataDir_ << " found times "
168  << pointToPointPlanarInterpolation::timeNames(sampleTimes_)
169  << endl;
170  }
171  }
172 
173 
174  // Find current time in sampleTimes
175  label lo = -1;
176  label hi = -1;
177 
178  const bool foundTime = pointToPointPlanarInterpolation::findTime
179  (
180  sampleTimes_,
181  startSampleTime_,
182  time().userTimeValue(),
183  lo,
184  hi
185  );
186 
187  if (!foundTime)
188  {
190  << "Cannot find starting sampling values for current time "
191  << time().userTimeValue() << nl
192  << "Have sampling values for times "
193  << pointToPointPlanarInterpolation::timeNames(sampleTimes_) << nl
194  << "In directory " << dataDir_ << " of field " << fieldTableName_
195  << exit(FatalError);
196  }
197 
198 
199  // Update sampled data fields.
200 
201  if (lo != startSampleTime_)
202  {
203  startSampleTime_ = lo;
204 
205  if (startSampleTime_ == endSampleTime_)
206  {
207  // No need to reread since are end values
208  if (debug)
209  {
210  Pout<< "checkTable : Setting startValues to (already read) "
211  << dataDir_/sampleTimes_[startSampleTime_].name()
212  << endl;
213  }
214  startSampledValues_ = endSampledValues_;
215  startAverage_ = endAverage_;
216  }
217  else
218  {
219  if (debug)
220  {
221  Pout<< "checkTable : Reading startValues from "
222  << dataDir_/sampleTimes_[lo].name()
223  << endl;
224  }
225 
226  // Update the mapper, re-mapping if the points have changed
227  const pointToPointPlanarInterpolation& mapper =
228  this->mapper(sampleTimes_[startSampleTime_].name());
229 
230  // Reread values and interpolate
231  const fileName valsFile
232  (
233  findFieldFile(sampleTimes_[startSampleTime_].name())
234  );
235 
236  Field<Type> vals;
237 
238  if (setAverage_)
239  {
240  AverageField<Type> avals((IFstream(valsFile)()));
241  vals = avals;
242  startAverage_ = avals.average();
243  }
244  else
245  {
246  (IFstream(valsFile)()) >> vals;
247  }
248 
249  if (vals.size() != mapper.sourceSize())
250  {
252  << "Number of values (" << vals.size()
253  << ") differs from the number of points ("
254  << mapper.sourceSize()
255  << ") in file " << valsFile << exit(FatalError);
256  }
257 
258  startSampledValues_ = mapper.interpolate(vals);
259  }
260  }
261 
262  if (hi != endSampleTime_)
263  {
264  endSampleTime_ = hi;
265 
266  if (endSampleTime_ == -1)
267  {
268  // endTime no longer valid. Might as well clear endValues.
269  if (debug)
270  {
271  Pout<< "checkTable : Clearing endValues" << endl;
272  }
273  endSampledValues_.clear();
274  }
275  else
276  {
277  if (debug)
278  {
279  Pout<< "checkTable : Reading endValues from "
280  << dataDir_/sampleTimes_[endSampleTime_].name()
281  << endl;
282  }
283 
284  // Update the mapper, re-mapping if the points have changed
285  const pointToPointPlanarInterpolation& mapper =
286  this->mapper(sampleTimes_[endSampleTime_].name());
287 
288  // Reread values and interpolate
289  const fileName valsFile
290  (
291  findFieldFile(sampleTimes_[endSampleTime_].name())
292  );
293 
294  Field<Type> vals;
295 
296  if (setAverage_)
297  {
298  AverageField<Type> avals((IFstream(valsFile)()));
299  vals = avals;
300  endAverage_ = avals.average();
301  }
302  else
303  {
304  (IFstream(valsFile)()) >> vals;
305  }
306 
307  if (vals.size() != mapper.sourceSize())
308  {
310  << "Number of values (" << vals.size()
311  << ") differs from the number of points ("
312  << mapper.sourceSize()
313  << ") in file " << valsFile << exit(FatalError);
314  }
315 
316  endSampledValues_ = mapper.interpolate(vals);
317  }
318  }
319 }
320 
321 
322 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
323 
324 template<class Type>
326 (
327  const fvPatch& p,
329  const dictionary& dict
330 )
331 :
332  patch_(p),
333  internalField_(iF),
334  fieldTableName_(dict.lookupOrDefault("fieldTable", iF.name())),
335  dataDir_
336  (
337  dict.lookupOrDefault
338  (
339  "dataDir",
340  time().constant()/"boundaryData"/p.name()
341  )
342  ),
343  pointsName_(dict.lookupOrDefault<fileName>("points", "points")),
344  sampleName_(dict.lookupOrDefault("sample", word::null)),
345  setAverage_(dict.lookupOrDefault("setAverage", false)),
346  perturb_(dict.lookupOrDefault("perturb", 1e-5)),
347  mapMethod_
348  (
349  dict.lookupOrDefault<word>
350  (
351  "mapMethod",
352  "planarInterpolation"
353  )
354  ),
355  mapperPtr_(nullptr),
356  sampleTimes_(0),
357  startSampleTime_(-1),
358  startSampledValues_(0),
359  startAverage_(Zero),
360  endSampleTime_(-1),
361  endSampledValues_(0),
362  endAverage_(Zero),
363  offset_()
364 {
365  dataDir_.expand();
366  pointsName_.expand();
367  sampleName_.expand();
368 
369  if (dict.found("offset"))
370  {
371  offset_ = Function1<Type>::New
372  (
373  "offset",
374  time().userUnits(),
375  iF.dimensions(),
376  dict
377  );
378  }
379 
380  if
381  (
382  mapMethod_ != "planarInterpolation"
383  && mapMethod_ != "nearest"
384  )
385  {
387  << "mapMethod should be one of 'planarInterpolation'"
388  << ", 'nearest'" << exit(FatalIOError);
389  }
390 }
391 
392 
393 template<class Type>
396 (
398  const fvPatch& p,
400  const fieldMapper& mapper
401 )
402 :
403  patch_(p),
404  internalField_(iF),
405  fieldTableName_(ptf.fieldTableName_),
406  dataDir_(ptf.dataDir_),
407  pointsName_(ptf.pointsName_),
408  sampleName_(ptf.sampleName_),
409  setAverage_(ptf.setAverage_),
410  perturb_(ptf.perturb_),
411  mapMethod_(ptf.mapMethod_),
412  mapperPtr_(nullptr),
413  sampleTimes_(ptf.sampleTimes_),
414  startSampleTime_(ptf.startSampleTime_),
415  startAverage_(ptf.startAverage_),
416  endSampleTime_(ptf.endSampleTime_),
417  endAverage_(ptf.endAverage_),
418  offset_(ptf.offset_, false)
419 {
420  map(ptf, mapper);
421 }
422 
423 
424 template<class Type>
427 (
429 )
430 :
431  patch_(ptf.patch_),
432  internalField_(ptf.internalField_),
433  fieldTableName_(ptf.fieldTableName_),
434  dataDir_(ptf.dataDir_),
435  pointsName_(ptf.pointsName_),
436  sampleName_(ptf.sampleName_),
437  setAverage_(ptf.setAverage_),
438  perturb_(ptf.perturb_),
439  mapMethod_(ptf.mapMethod_),
440  mapperPtr_(nullptr),
441  sampleTimes_(ptf.sampleTimes_),
442  startSampleTime_(ptf.startSampleTime_),
443  startSampledValues_(ptf.startSampledValues_),
444  startAverage_(ptf.startAverage_),
445  endSampleTime_(ptf.endSampleTime_),
446  endSampledValues_(ptf.endSampledValues_),
447  endAverage_(ptf.endAverage_),
448  offset_(ptf.offset_, false)
449 {}
450 
451 
452 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
453 
454 template<class Type>
456 (
458  const fieldMapper& mapper
459 )
460 {
461  reset(tiptf);
462 }
463 
464 
465 template<class Type>
467 (
469 )
470 {
471  if (this != &tiptf)
472  {
473  startSampledValues_.reset(tiptf.startSampledValues_);
474  endSampledValues_.reset(tiptf.endSampledValues_);
475  }
476 
477  // Clear interpolator
478  mapperPtr_.clear();
479  startSampleTime_ = -1;
480  endSampleTime_ = -1;
481 }
482 
483 
484 template<class Type>
486 {
487  checkTable();
488 
489  // Interpolate between the sampled data
490 
491  tmp<Field<Type>> tfld(new Field<Type>(patch_.size()));
492  Field<Type>& fld = tfld.ref();
493 
494  Type wantedAverage;
495 
496  if (endSampleTime_ == -1)
497  {
498  // Only start value
499  if (debug)
500  {
501  Pout<< "updateCoeffs : Sampled, non-interpolated values"
502  << " from start time:"
503  << sampleTimes_[startSampleTime_].name() << nl;
504  }
505 
506  fld = startSampledValues_;
507  wantedAverage = startAverage_;
508  }
509  else
510  {
511  const scalar start = sampleTimes_[startSampleTime_].value();
512  const scalar end = sampleTimes_[endSampleTime_].value();
513 
514  const scalar s = (time().userTimeValue() - start)/(end - start);
515 
516  if (debug)
517  {
518  Pout<< "updateCoeffs : Sampled, interpolated values"
519  << " between start time:"
520  << sampleTimes_[startSampleTime_].name()
521  << " and end time:" << sampleTimes_[endSampleTime_].name()
522  << " with weight:" << s << endl;
523  }
524 
525  fld = (1 - s)*startSampledValues_ + s*endSampledValues_;
526  wantedAverage = (1 - s)*startAverage_ + s*endAverage_;
527  }
528 
529  // Enforce average. Either by scaling (if scaling factor > 0.5) or by
530  // offsetting.
531  if (setAverage_)
532  {
533  Type averagePsi = gSum(patch_.magSf()*fld)/gSum(patch_.magSf());
534 
535  if (debug)
536  {
537  Pout<< "updateCoeffs :"
538  << " actual average:" << averagePsi
539  << " wanted average:" << wantedAverage
540  << endl;
541  }
542 
543  if (mag(averagePsi) < vSmall)
544  {
545  // Field too small to scale. Offset instead.
546  const Type offset = wantedAverage - averagePsi;
547  if (debug)
548  {
549  Pout<< "updateCoeffs :"
550  << " offsetting with:" << offset << endl;
551  }
552  fld += offset;
553  }
554  else
555  {
556  const scalar scale = mag(wantedAverage)/mag(averagePsi);
557 
558  if (debug)
559  {
560  Pout<< "updateCoeffs :"
561  << " scaling with:" << scale << endl;
562  }
563  fld *= scale;
564  }
565  }
566 
567  // Apply offset to mapped values
568  if (offset_.valid())
569  {
570  fld += offset_->value(time().value());
571  }
572 
573  if (debug)
574  {
575  Pout<< "updateCoeffs : set fixedValue to min:" << gMin(fld)
576  << " max:" << gMax(fld)
577  << " avg:" << gAverage(fld) << endl;
578  }
579 
580  return tfld;
581 }
582 
583 
584 template<class Type>
586 (
587  Ostream& os
588 ) const
589 {
591  (
592  os,
593  "dataDir",
594  time().constant()/"boundaryData"/patch_.name(),
595  dataDir_
596  );
597 
598  writeEntryIfDifferent(os, "points", fileName("points"), pointsName_);
599  writeEntryIfDifferent(os, "sample", fileName::null, sampleName_);
600  writeEntryIfDifferent(os, "setAverage", Switch(false), setAverage_);
601  writeEntryIfDifferent(os, "perturb", scalar(1e-5), perturb_);
602 
603  writeEntry(os, "fieldTable", fieldTableName_);
604 
606  (
607  os,
608  "mapMethod",
609  word("planarInterpolation"),
610  mapMethod_
611  );
612 
613  if (offset_.valid())
614  {
615  writeEntry
616  (
617  os,
618  time().userUnits(),
619  internalField_.dimensions(),
620  offset_()
621  );
622  }
623 }
624 
625 
626 // ************************************************************************* //
Functions used by OpenFOAM that are specific to POSIX compliant operating systems and need to be repl...
Field with dimensions and associated with geometry type GeoMesh which is used to size the field and a...
const dimensionSet & dimensions() const
Return dimensions.
Pre-declare SubField and related Field type.
Definition: Field.H:83
static autoPtr< Function1< Type > > New(const word &name, const Function1s::unitSets &units, const dictionary &dict)
Select from dictionary.
Definition: Function1New.C:32
virtual const fileName & name() const
Return the name of the stream.
Definition: OSstream.H:85
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:57
A simple wrapper around bool so that it can be read as a word: true/false, on/off,...
Definition: Switch.H:61
instantList findTimes(const fileName &, const word &constantName="constant") const
Search a given directory for valid time directories.
Definition: findTimes.C:36
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
Abstract base class for field mapping.
Definition: fieldMapper.H:48
A class for handling file names.
Definition: fileName.H:82
word name() const
Return file name (part beyond last /)
Definition: fileName.C:195
static const fileName null
An empty fileName.
Definition: fileName.H:97
A finiteVolume patch using a polyPatch and a fvBoundaryMesh.
Definition: fvPatch.H:58
Interpolates between two sets of unstructured points using 2D Delaunay triangulation....
string & expand(const bool allowEmpty=false)
Expand initial tildes and all occurrences of environment variables.
Definition: string.C:125
Patch field mapper which interpolates the values from a set of supplied points in space and time.
const Time & time() const
Return reference to time.
void reset(const timeVaryingMappedFvPatchField &)
Reset the fvPatchField to the given fvPatchField.
timeVaryingMappedFvPatchField(const fvPatch &, const DimensionedField< Type, fvMesh > &, const dictionary &)
Construct from patch, internal field and dictionary.
tmp< Field< Type > > map()
Return the current mapped patch field.
A class for managing temporary objects.
Definition: tmp.H:55
T & ref() const
Return non-const reference or generate a fatal error.
Definition: tmpI.H:197
A class for handling words, derived from string.
Definition: word.H:63
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:346
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:334
gmvFile<< "tracers "<< particles.size()<< nl;{ pointField positions(particles.size());label particlei=0;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter) { positions[particlei++]=iter().position(mesh);} for(i=0;i< pTraits< point >::nComponents;i++) { forAll(positions, particlei) { gmvFile<< component(positions[particlei], i)<< ' ';} gmvFile<< nl;}}forAll(lagrangianScalarNames, i){ const word &name=lagrangianScalarNames[i];IOField< scalar > fld(IOobject(name, runTime.name(), lagrangian::cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
gmvFile<< "tracers "<< particles.size()<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().x()<< " ";}gmvFile<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().y()<< " ";}gmvFile<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.name(), lagrangian::cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
word timeName
Definition: getTimeIndex.H:3
const dimensionedScalar e
Elementary charge.
const dimensionSet time
Type gMin(const UList< Type > &f, const label comm)
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
static const zero Zero
Definition: zero.H:97
const doubleScalar e
Definition: doubleScalar.H:106
void writeEntryIfDifferent(Ostream &os, const word &entryName, const EntryType &value1, const EntryType &value2)
Helper function to write the keyword and entry only if the.
Type gAverage(const UList< Type > &f, const label comm)
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:288
String typeName(const std::type_info &info)
Return the un-mangled name given the standard type info.
bool exists(const fileName &, const bool checkVariants=true, const bool followLink=true)
Does the name exist (as directory or file) in the file system?
Definition: POSIX.C:520
messageStream Info
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:42
Type gSum(const UList< Type > &f, const label comm)
IOerror FatalIOError
Type gMax(const UList< Type > &f, const label comm)
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
prefixOSstream Pout(cout, "Pout")
Definition: IOstreams.H:53
tmp< DimensionedField< scalar, GeoMesh, Field > > mag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
error FatalError
void offset(label &lst, const label o)
void writeEntry(Ostream &os, const word &key, const DimensionedFieldFunction< DimensionedFieldType > &f)
static const char nl
Definition: Ostream.H:297
dictionary dict
volScalarField & p