cyclicTransform.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) 2020-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 
26 #include "cyclicTransform.H"
27 #include "IOmanip.H"
28 #include "stringOps.H"
29 #include "units.H"
30 
31 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
32 
33 namespace Foam
34 {
35 
36 template<class Type>
37 Type sum(const Type& x, const bool global)
38 {
39  return global ? returnReduce(x, sumOp<Type>()) : x;
40 }
41 
42 template<class Type>
43 Type sum(const Field<Type>& x, const bool global)
44 {
45  return global ? gSum(x) : sum(x);
46 }
47 
48 template<class Type>
49 Type sum(const tmp<Field<Type>>& x, const bool global)
50 {
51  const Type s = sum(x(), global);
52  x.clear();
53  return s;
54 }
55 
56 }
57 
58 
59 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
60 
61 namespace Foam
62 {
64 }
65 
68 {
69  "unspecified",
70  "none",
71  "rotational",
72  "translational"
73 };
74 
76 {
77  "transformType",
78  "transform",
79  "rotationAxis",
80  "rotationCentre",
81  "rotationAngle",
82  "separation",
83  "separationVector"
84 };
85 
86 
87 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
88 
89 void Foam::cyclicTransform::update()
90 {
91  if (!transformComplete_)
92  {
93  return;
94  }
95 
96  switch (transformType_)
97  {
98  case UNSPECIFIED:
99  break;
100 
101  case NONE:
102  transform_ = transformer();
103  break;
104 
105  case ROTATIONAL:
106  if (rotationAngle_ == 0)
107  {
108  transform_ = transformer();
109  }
110  else
111  {
112  const tensor R =
113  quaternion(rotationAxis_, rotationAngle_).R();
114 
115  if (mag(rotationCentre_) == 0)
116  {
117  transform_ = transformer::rotation(R);
118  }
119  else
120  {
121  transform_ =
122  transformer::translation(rotationCentre_)
124  & transformer::translation(- rotationCentre_);
125  }
126  }
127  break;
128 
129  case TRANSLATIONAL:
130  if (mag(separation_) == 0)
131  {
132  transform_ = transformer();
133  }
134  else
135  {
136  transform_ = transformer::translation(separation_);
137  }
138  break;
139  }
140 }
141 
142 
143 bool Foam::cyclicTransform::set
144 (
145  const cyclicTransform& t,
146  const scalar lengthScale,
147  const scalar matchTolerance
148 )
149 {
150  // If the supplied transform is unspecified then there is nothing to do
151  if (t.transformType_ == UNSPECIFIED)
152  {
153  return true;
154  }
155 
156  // If this transform is specified then we need to check that it is
157  // compatible with the supplied transform
158  if (transformType_ != UNSPECIFIED)
159  {
160  // If the transforms are of different types then they are incompatible
161  if (transformType_ != t.transformType_)
162  {
163  return false;
164  }
165 
166  // Length-tolerance
167  const scalar lengthTolerance = lengthScale*matchTolerance;
168 
169  // If the transforms are both rotational then the axes must be in the
170  // same direction, the centre points must lie on the same line, and the
171  // angles (if available) must be opposing.
172  if (transformType_ == ROTATIONAL)
173  {
174  const scalar dot = rotationAxis_ & t.rotationAxis_;
175 
176  if (mag(dot) < 1 - matchTolerance)
177  {
178  return false;
179  }
180 
181  if
182  (
183  (rotationAxis_ & (rotationCentre_ - t.rotationCentre_))
184  > lengthTolerance
185  )
186  {
187  return false;
188  }
189 
190  if (transformComplete_ && t.transformComplete_)
191  {
192  if
193  (
194  mag(rotationAngle_ - sign(dot)*t.rotationAngle_)
195  > matchTolerance
196  )
197  {
198  return false;
199  }
200  }
201  }
202 
203  // If the transforms are both translational then the separations must
204  // be opposing
205  if (transformType_ == TRANSLATIONAL)
206  {
207  if (transformComplete_ && t.transformComplete_)
208  {
209  if (mag(separation_ - t.separation_) > lengthTolerance)
210  {
211  return false;
212  }
213  }
214  }
215  }
216 
217  // If the supplied transform is more complete then overwrite this with it
218  if (!transformComplete_ && t.transformComplete_)
219  {
220  *this = t;
221  }
222 
223  return true;
224 }
225 
226 
227 // * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * * //
228 
230 :
231  cyclicTransform(true)
232 {}
233 
234 
236 (
237  const bool defaultIsNone
238 )
239 :
240  transformType_(defaultIsNone ? NONE : UNSPECIFIED),
241  rotationAxis_(vector::uniform(NaN)),
242  rotationCentre_(vector::uniform(NaN)),
243  rotationAngle_(NaN),
244  separation_(vector::uniform(NaN)),
245  transformComplete_(transformType_ == NONE),
246  transform_()
247 {}
248 
249 
251 (
252  const dictionary& dict,
253  const bool defaultIsNone
254 )
255 :
256  transformType_
257  (
258  transformTypeNames
259  [
260  dict.lookupOrDefaultBackwardsCompatible<word>
261  (
262  {"transformType", "transform"},
263  transformTypeNames[defaultIsNone ? NONE : UNSPECIFIED]
264  )
265  ]
266  ),
267  rotationAxis_
268  (
269  transformType_ == ROTATIONAL
270  ? normalised(dict.lookup<vector>("rotationAxis", dimless))
271  : vector::uniform(NaN)
272  ),
273  rotationCentre_
274  (
275  transformType_ == ROTATIONAL
276  ? dict.lookup<point>("rotationCentre", units::length)
277  : point::uniform(NaN)
278  ),
279  rotationAngle_
280  (
281  dict.lookupOrDefault<scalar>("rotationAngle", units::degrees, NaN)
282  ),
283  separation_
284  (
285  transformType_ == TRANSLATIONAL
286  ? (
288  (
289  {"separation", "separationVector"},
291  )
292  )
293  : vector::uniform(NaN)
294  ),
295  transformComplete_
296  (
297  transformType_ == NONE
298  || (
299  transformType_ == ROTATIONAL
300  && dict.found("rotationAngle")
301  )
302  || (
303  transformType_ == TRANSLATIONAL
304  && (dict.found("separation") || dict.found("separationVector"))
305  )
306  ),
307  transform_()
308 {
309  if (transformComplete_)
310  {
311  update();
312  }
313 }
314 
315 
317 (
318  const word& name,
319  const vectorField& areas,
320  const cyclicTransform& transform,
321  const word& nbrName,
322  const cyclicTransform& nbrTransform,
323  const scalar matchTolerance,
324  const bool global
325 )
326 :
328 {
329  // Calculate the total (vector) areas for the supplied patch data
330  const vector area = sum(areas, global);
331 
332  // Calculate patch length scales
333  const scalar lengthScale = sqrt(mag(area));
334 
335  // Copy as much data from the neighbour as possible
336  if (!transformComplete_ && nbrTransform.transformType_ != UNSPECIFIED)
337  {
338  if (!set(inv(nbrTransform), lengthScale, matchTolerance))
339  {
341  << "Patch " << name
342  << " and neighbour patch " << nbrName
343  << " have incompatible transforms:" << nl << nl << incrIndent;
344 
346  << indent << name << nl << indent << token::BEGIN_BLOCK << nl
347  << incrIndent;
348 
350 
352  << decrIndent << indent << token::END_BLOCK << nl << nl;
353 
355  << indent << nbrName << nl << indent << token::BEGIN_BLOCK << nl
356  << incrIndent;
357 
358  nbrTransform.write(FatalError);
359 
361  << decrIndent << indent << token::END_BLOCK << nl << nl;
362 
364  << decrIndent << exit(FatalError);
365  }
366  }
367 }
368 
369 
371 (
372  const word& name,
373  const pointField& ctrs,
374  const vectorField& areas,
375  const cyclicTransform& transform,
376  const word& nbrName,
377  const pointField& nbrCtrs,
378  const vectorField& nbrAreas,
379  const cyclicTransform& nbrTransform,
380  const scalar matchTolerance,
381  const bool global
382 )
383 :
385  (
386  name,
387  areas,
388  transform,
389  nbrName,
390  nbrTransform,
391  matchTolerance
392  )
393 {
394  // If there is no geometry from which to calculate the transform then
395  // nothing can be calculated
396  if (sum(areas.size(), global) == 0 || sum(nbrAreas.size(), global) == 0)
397  {
398  return;
399  }
400 
401  // Calculate the total (vector) areas for the supplied patch data
402  const vector area = sum(areas, global);
403  const vector nbrArea = sum(nbrAreas, global);
404 
405  // Calculate the centroids for the supplied patch data
406  const scalarField magAreas(mag(areas));
407  const scalarField magNbrAreas(mag(nbrAreas));
408  const scalar sumMagAreas = sum(magAreas, global);
409  const scalar sumMagNbrAreas = sum(magNbrAreas, global);
410  const point ctr = sum(ctrs*magAreas, global)/sumMagAreas;
411  const point nbrCtr = sum(nbrCtrs*magNbrAreas, global)/sumMagNbrAreas;
412 
413  // Calculate patch length scales
414  const scalar lengthScale = sqrt(sumMagAreas);
415 
416  // Calculate the transformation from the patch geometry
417  if (!transformComplete_)
418  {
419  // Store the old transformation type
420  const transformTypes oldTransformType = transformType_;
421 
422  // Calculate the average patch normals
423  const vector normal = normalised(area);
424  const vector negNbrNormal = - normalised(nbrArea);
425 
426  // Calculate the angle and distance separations
427  const scalar dot = normal & negNbrNormal;
428  const vector delta = ctr - nbrCtr;
429 
430  // Determine the type of transformation if it has not been specified
431  if (transformType_ == UNSPECIFIED)
432  {
433  transformType_ =
434  dot < 1 - rootSmall
435  ? ROTATIONAL
436  : mag(delta) > lengthScale*rootSmall
437  ? TRANSLATIONAL
438  : NONE;
439  }
440 
441  // If the transformation is known to be rotational, then we need to
442  // calculate the angle. If the transformation was previously
443  // unspecified then we also need to calculate the axis and the centre
444  // of rotation.
445  if (transformType_ == ROTATIONAL)
446  {
447  // Calculate the axis, if necessary
448  if (transformType_ != oldTransformType)
449  {
450  const vector midNormal = normalised(normal + negNbrNormal);
451  const vector axis =
452  (ctr - nbrCtr)
453  ^ (
454  normal*(negNbrNormal & midNormal)
455  - negNbrNormal*(normal & midNormal)
456  );
457  const vector axis180 =
458  (ctr - nbrCtr)
459  ^ (normal - negNbrNormal);
460 
461  rotationAxis_ =
462  normalised
463  (
464  mag(axis) > lengthScale*rootSmall
465  ? axis
466  : axis180
467  );
468  }
469 
470  const tensor PerpA = tensor::I - sqr(rotationAxis_);
471  const vector normalPerpA = normalised(PerpA & normal);
472  const vector negNbrNormalPerpA = normalised(PerpA & negNbrNormal);
473  const scalar theta =
474  acos(min(max(normalPerpA & negNbrNormalPerpA, -1), 1));
475  rotationAngle_ =
476  - sign((normalPerpA ^ negNbrNormalPerpA) & rotationAxis_)*theta;
477 
478  // Calculate the angle
479  // Calculate the centre of rotation, if necessary
480  if (transformType_ != oldTransformType)
481  {
482  const tensor R = quaternion(rotationAxis_, theta).R();
483  tensor A = tensor::I - R;
484  vector b = ctr - (R & nbrCtr);
485  const label i = findMax(cmptMag(rotationAxis_));
486  forAll(b, j)
487  {
488  A(i, j) = j == i;
489  }
490  b[i] = 0;
491  rotationCentre_ = inv(A) & b;
492  }
493  }
494 
495  // If the transformation is known to be translational then we just need
496  // to set the separation.
497  if (transformType_ == TRANSLATIONAL)
498  {
499  separation_ = delta;
500  }
501 
502  // Update the transform object
503  transformComplete_ = true;
504  update();
505 
506  // Print results of calculation
507  if (debug)
508  {
509  Info<< "Transformation calculated between patches " << name
510  << " and " << nbrName << ":" << nl << token::BEGIN_BLOCK << nl
511  << incrIndent;
512 
514 
516  }
517  }
518 
519  // Check the transformation is correct to within the matching tolerance
520  const point nbrCtrT =
521  transform_.transformPosition(nbrCtr);
522 
523  const scalar ctrNbrCtrTDistance = mag(ctr - nbrCtrT);
524 
525  if (ctrNbrCtrTDistance > lengthScale*matchTolerance)
526  {
528  str << "Patches " << name << " and " << nbrName << " are potentially "
529  << "not geometrically similar enough to be coupled." << nl << nl
530  << "The distance between the transformed centres of these patches "
531  << "is " << ctrNbrCtrTDistance << ", which is greater than the "
532  << "patch length scale (" << lengthScale << ") multiplied by the "
533  << "match tolerance (" << matchTolerance << ")." << nl << nl
534  << "Check that the patches are geometrically similar and that any "
535  << "transformations defined between them are correct." << nl << nl
536  << "If the patches and their transformations are defined correctly "
537  << "but small irregularities in the mesh mean this geometric test "
538  << "is failing, then it might be appropriate to relax the failure "
539  << "criteria by increasing the \"matchTolerance\" setting for "
540  << "these patches in the \"polyMesh/boundary\" file.";
542  << nl << stringOps::breakIntoIndentedLines(str.str()).c_str()
543  << exit(FatalError);
544  }
545 }
546 
547 
548 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
549 
551 {}
552 
553 
554 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
555 
557 {
558  const label oldPrecision = os.precision();
559 
560  os.precision(16);
561 
562  if (transformType_ != UNSPECIFIED)
563  {
564  writeEntry(os, "transformType", transformTypeNames[transformType_]);
565  }
566 
567  if (transformType_ == ROTATIONAL)
568  {
569  writeEntry(os, "rotationAxis", rotationAxis_);
570  writeEntry(os, "rotationCentre", rotationCentre_);
571 
572  if (transformComplete_)
573  {
574  writeEntry(os, "rotationAngle", units::degrees, rotationAngle_);
575  }
576  }
577 
578  if (transformType_ == TRANSLATIONAL)
579  {
580  if (transformComplete_)
581  {
582  writeEntry(os, "separation", separation_);
583  }
584  }
585 
586  os.precision(oldPrecision);
587 }
588 
589 
591 {
592  OStringStream oss;
593 
594  switch (transformType_)
595  {
596  case UNSPECIFIED:
597  break;
598 
599  case NONE:
600  break;
601 
602  case ROTATIONAL:
603  {
604  oss << "axis=" << rotationAxis_
605  << ", centre=" << rotationCentre_
606  << ", angle=";
607 
608  if (transformComplete_)
609  {
610  oss << units::degrees.toUser(rotationAngle_);
611  }
612  else
613  {
614  oss << '?';
615  }
616 
617  break;
618  }
619 
620  case TRANSLATIONAL:
621  {
622  oss << "separation=";
623 
624  if (transformComplete_)
625  {
626  oss << separation_;
627  }
628  else
629  {
630  oss << '?';
631  }
632 
633  break;
634  }
635  }
636 
637  return oss.str();
638 }
639 
640 
641 // * * * * * * * * * * * * * * * Global Operators * * * * * * * * * * * * * //
642 
643 Foam::cyclicTransform Foam::operator&
644 (
645  const transformer& t,
646  const cyclicTransform& c0
647 )
648 {
649  cyclicTransform c1(c0);
650 
651  if (c1.transformType_ == cyclicTransform::ROTATIONAL)
652  {
653  c1.rotationAxis_ = normalised(t.transform(c1.rotationAxis_));
654  c1.rotationCentre_ = t.transformPosition(c1.rotationCentre_);
655  }
656 
657  if (c1.transformType_ == cyclicTransform::TRANSLATIONAL)
658  {
659  if (c1.transformComplete_)
660  {
661  c1.separation_ = t.transform(c1.separation_);
662  }
663  }
664 
665  if (c1.transformComplete_)
666  {
667  c1.update();
668  }
669 
670  return c1;
671 }
672 
673 
675 {
676  cyclicTransform c1(c0);
677 
678  if (c1.transformType_ == cyclicTransform::ROTATIONAL)
679  {
680  if (c1.transformComplete_)
681  {
682  c1.rotationAngle_ = - c1.rotationAngle_;
683  }
684  }
685 
686  if (c1.transformType_ == cyclicTransform::TRANSLATIONAL)
687  {
688  if (c1.transformComplete_)
689  {
690  c1.separation_ = - c1.separation_;
691  }
692  }
693 
694  if (c1.transformComplete_)
695  {
696  c1.update();
697  }
698 
699  return c1;
700 }
701 
702 
703 // ************************************************************************* //
Istream and Ostream manipulators taking arguments.
scalar delta
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:449
Pre-declare SubField and related Field type.
Definition: Field.H:83
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
Initialise the NamedEnum HashTable from the static list of names.
Definition: NamedEnum.H:55
Output to memory buffer stream.
Definition: OStringStream.H:52
string str() const
Return the string.
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:57
virtual int precision() const =0
Get precision of output field.
static const Tensor I
Definition: Tensor.H:83
static Form uniform(const Cmpt &s)
Return a VectorSpace with all elements = s.
Definition: VectorSpaceI.H:168
Cyclic plane transformation.
string str() const
Generate a string representation of the transform.
static const NamedEnum< transformTypes, 4 > transformTypeNames
friend cyclicTransform inv(const cyclicTransform &c)
void write(Ostream &os) const
Write the data to a dictionary.
static const wordList keywords
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
T lookupOrDefault(const word &, const T &) const
Find and return a T, if not found return the given default.
ITstream & lookup(const word &, bool recursive=false, bool patternMatch=true) const
Find and return an entry data stream.
Definition: dictionary.C:669
ITstream & lookupBackwardsCompatible(const wordList &, bool recursive=false, bool patternMatch=true) const
Find and return an entry data stream, trying a list of keywords.
Definition: dictionary.C:680
bool found(const word &, bool recursive=false, bool patternMatch=true) const
Search dictionary for given keyword.
Definition: dictionary.C:468
Quaternion class used to perform rotations in 3D space.
Definition: quaternion.H:61
tensor R() const
The rotation tensor corresponding the quaternion.
Definition: quaternionI.H:323
A class for handling character strings derived from std::string.
Definition: string.H:79
A class for managing temporary objects.
Definition: tmp.H:55
@ BEGIN_BLOCK
Definition: token.H:114
@ END_BLOCK
Definition: token.H:115
Vector-tensor class used to perform translations, rotations and scaling operations in 3D space.
Definition: transformer.H:84
static transformer rotation(const tensor &T)
Construct a pure rotation transformer.
Definition: transformerI.H:43
static transformer translation(const vector &t)
Construct a pure translation transformer.
Definition: transformerI.H:31
vector transformPosition(const vector &v) const
Transform the given position.
Definition: transformerI.H:153
T toUser(const T &) const
Convert a value to user units.
A class for handling words, derived from string.
Definition: word.H:63
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:334
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))
volScalarField & b
Definition: createFields.H:27
const dimensionedScalar c1
First radiation constant: default SI units: [W/m^2].
const dimensionSet area
static const coefficient A("A", dimPressure, 611.21)
string breakIntoIndentedLines(const string &str, const string::size_type nLength=80, const string::size_type nIndent=0)
Break a string up into indented lines.
Definition: stringOps.C:963
const unitSet & length
Definition: units.C:137
const unitSet degrees
Namespace for OpenFOAM.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
const dimensionSet & dimless
Definition: dimensions.C:138
Ostream & decrIndent(Ostream &os)
Decrement the indent level.
Definition: Ostream.H:272
dimensionedScalar sign(const dimensionedScalar &ds)
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
Tensor< scalar > tensor
Tensor of scalars.
Definition: tensor.H:51
label findMax(const ListType &, const label start=0)
Find index of max element (and larger than given element).
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:288
void transform(GeometricField< Type, GeoMesh > &rtf, const GeometricField< tensor, GeoMesh > &trf, const GeometricField< Type, GeoMesh > &tf)
messageStream Info
Ostream & incrIndent(Ostream &os)
Increment the indent level.
Definition: Ostream.H:265
Type gSum(const UList< Type > &f, const label comm)
tmp< DimensionedField< typename outerProduct< Type, Type >::type, GeoMesh, Field >> sqr(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
void inv(pointPatchField< tensor > &, const pointPatchField< tensor > &)
T returnReduce(const T &Value, const BinaryOp &bop, const int tag=Pstream::msgType(), const label comm=UPstream::worldComm)
dimensionSet normalised(const dimensionSet &)
Definition: dimensionSet.C:509
dimensioned< Type > min(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
static scalar R(const scalar a, const scalar x)
Definition: invIncGamma.C:102
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
tmp< DimensionedField< scalar, GeoMesh, Field > > mag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
error FatalError
tmp< DimensionedField< Type, GeoMesh, Field > > cmptMag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
void sqrt(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
defineTypeNameAndDebug(atmosphericBoundaryLayer, 0)
Ostream & indent(Ostream &os)
Indent stream.
Definition: Ostream.H:243
void writeEntry(Ostream &os, const word &key, const DimensionedFieldFunction< DimensionedFieldType > &f)
static const char nl
Definition: Ostream.H:297
dimensionedScalar acos(const dimensionedScalar &ds)
dimensioned< Type > max(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
void dot(GeometricField< typename innerProduct< Type1, Type2 >::type, GeoMesh, PrimitiveField1 > &gf, const GeometricField< Type1, GeoMesh, PrimitiveField2 > &gf1, const GeometricField< Type2, GeoMesh, PrimitiveField3 > &gf2)
dictionary dict
Useful unit conversions.