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-2023 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 "unitConversion.H"
28 #include "IOmanip.H"
29 #include "stringOps.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 {
63  template<>
65  {
66  "unspecified",
67  "none",
68  "rotational",
69  "translational"
70  };
71 
74 
76 }
77 
78 
79 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
80 
81 void Foam::cyclicTransform::update()
82 {
83  if (!transformComplete_)
84  {
85  return;
86  }
87 
88  switch (transformType_)
89  {
90  case UNSPECIFIED:
91  break;
92 
93  case NONE:
94  transform_ = transformer();
95  break;
96 
97  case ROTATIONAL:
98  if (rotationAngle_ == 0)
99  {
100  transform_ = transformer();
101  }
102  else
103  {
104  const tensor R =
105  quaternion(rotationAxis_, degToRad(rotationAngle_)).R();
106 
107  if (mag(rotationCentre_) == 0)
108  {
109  transform_ = transformer::rotation(R);
110  }
111  else
112  {
113  transform_ =
114  transformer::translation(rotationCentre_)
116  & transformer::translation(- rotationCentre_);
117  }
118  }
119  break;
120 
121  case TRANSLATIONAL:
122  if (mag(separation_) == 0)
123  {
124  transform_ = transformer();
125  }
126  else
127  {
128  transform_ = transformer::translation(separation_);
129  }
130  break;
131  }
132 }
133 
134 
135 bool Foam::cyclicTransform::set
136 (
137  const cyclicTransform& t,
138  const scalar lengthScale,
139  const scalar matchTolerance
140 )
141 {
142  // If the supplied transform is unspecified then there is nothing to do
143  if (t.transformType_ == UNSPECIFIED)
144  {
145  return true;
146  }
147 
148  // If this transform is specified then we need to check that it is
149  // compatible with the supplied transform
150  if (transformType_ != UNSPECIFIED)
151  {
152  // If the transforms are of different types then they are incompatible
153  if (transformType_ != t.transformType_)
154  {
155  return false;
156  }
157 
158  // Length-tolerance
159  const scalar lengthTolerance = lengthScale*matchTolerance;
160 
161  // If the transforms are both rotational then the axes must be in the
162  // same direction, the centre points must lie on the same line, and the
163  // angles (if available) must be opposing.
164  if (transformType_ == ROTATIONAL)
165  {
166  const scalar dot = rotationAxis_ & t.rotationAxis_;
167 
168  if (mag(dot) < 1 - matchTolerance)
169  {
170  return false;
171  }
172 
173  if
174  (
175  (rotationAxis_ & (rotationCentre_ - t.rotationCentre_))
176  > lengthTolerance
177  )
178  {
179  return false;
180  }
181 
182  if (transformComplete_ && t.transformComplete_)
183  {
184  if
185  (
186  mag(degToRad(rotationAngle_ - sign(dot)*t.rotationAngle_))
187  > matchTolerance
188  )
189  {
190  return false;
191  }
192  }
193  }
194 
195  // If the transforms are both translational then the separations must
196  // be opposing
197  if (transformType_ == TRANSLATIONAL)
198  {
199  if (transformComplete_ && t.transformComplete_)
200  {
201  if (mag(separation_ - t.separation_) > lengthTolerance)
202  {
203  return false;
204  }
205  }
206  }
207  }
208 
209  // If the supplied transform is more complete then overwrite this with it
210  if (!transformComplete_ && t.transformComplete_)
211  {
212  *this = t;
213  }
214 
215  return true;
216 }
217 
218 
219 // * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * * //
220 
222 :
223  cyclicTransform(true)
224 {}
225 
226 
228 (
229  const bool defaultIsNone
230 )
231 :
232  transformType_(defaultIsNone ? NONE : UNSPECIFIED),
233  rotationAxis_(vector::uniform(NaN)),
234  rotationCentre_(vector::uniform(NaN)),
235  rotationAngle_(NaN),
236  separation_(vector::uniform(NaN)),
237  transformComplete_(transformType_ == NONE),
238  transform_()
239 {}
240 
241 
243 (
244  const dictionary& dict,
245  const bool defaultIsNone
246 )
247 :
248  transformType_
249  (
250  transformTypeNames
251  [
252  dict.lookupOrDefaultBackwardsCompatible<word>
253  (
254  {"transformType", "transform"},
255  transformTypeNames[defaultIsNone ? NONE : UNSPECIFIED]
256  )
257  ]
258  ),
259  rotationAxis_
260  (
261  transformType_ == ROTATIONAL
262  ? normalised(dict.lookup<vector>("rotationAxis"))
264  ),
265  rotationCentre_
266  (
267  transformType_ == ROTATIONAL
268  ? dict.lookup<point>("rotationCentre")
270  ),
271  rotationAngle_(dict.lookupOrDefault<scalar>("rotationAngle", NaN)),
272  separation_
273  (
274  transformType_ == TRANSLATIONAL
275  ? (
277  (
278  {"separation", "separationVector"}
279  )
280  )
282  ),
283  transformComplete_
284  (
285  transformType_ == NONE
286  || (
287  transformType_ == ROTATIONAL
288  && dict.found("rotationAngle")
289  )
290  || (
291  transformType_ == TRANSLATIONAL
292  && (dict.found("separation") || dict.found("separationVector"))
293  )
294  ),
295  transform_()
296 {
297  if (transformComplete_)
298  {
299  update();
300  }
301 }
302 
303 
305 (
306  const word& name,
307  const vectorField& areas,
308  const cyclicTransform& transform,
309  const word& nbrName,
310  const cyclicTransform& nbrTransform,
311  const scalar matchTolerance,
312  const bool global
313 )
314 :
316 {
317  // Calculate the total (vector) areas for the supplied patch data
318  const vector area = sum(areas, global);
319 
320  // Calculate patch length scales
321  const scalar lengthScale = sqrt(mag(area));
322 
323  // Copy as much data from the neighbour as possible
324  if (!transformComplete_ && nbrTransform.transformType_ != UNSPECIFIED)
325  {
326  if (!set(inv(nbrTransform), lengthScale, matchTolerance))
327  {
329  << "Patch " << name
330  << " and neighbour patch " << nbrName
331  << " have incompatible transforms:" << nl << nl << incrIndent;
332 
334  << indent << name << nl << indent << token::BEGIN_BLOCK << nl
335  << incrIndent;
336 
338 
340  << decrIndent << indent << token::END_BLOCK << nl << nl;
341 
343  << indent << nbrName << nl << indent << token::BEGIN_BLOCK << nl
344  << incrIndent;
345 
346  nbrTransform.write(FatalError);
347 
349  << decrIndent << indent << token::END_BLOCK << nl << nl;
350 
352  << decrIndent << exit(FatalError);
353  }
354  }
355 }
356 
357 
359 (
360  const word& name,
361  const pointField& ctrs,
362  const vectorField& areas,
363  const cyclicTransform& transform,
364  const word& nbrName,
365  const pointField& nbrCtrs,
366  const vectorField& nbrAreas,
367  const cyclicTransform& nbrTransform,
368  const scalar matchTolerance,
369  const bool global
370 )
371 :
373  (
374  name,
375  areas,
376  transform,
377  nbrName,
378  nbrTransform,
379  matchTolerance
380  )
381 {
382  // If there is no geometry from which to calculate the transform then
383  // nothing can be calculated
384  if (sum(areas.size(), global) == 0 || sum(nbrAreas.size(), global) == 0)
385  {
386  return;
387  }
388 
389  // Calculate the total (vector) areas for the supplied patch data
390  const vector area = sum(areas, global);
391  const vector nbrArea = sum(nbrAreas, global);
392 
393  // Calculate the centroids for the supplied patch data
394  const scalarField magAreas(mag(areas));
395  const scalarField magNbrAreas(mag(nbrAreas));
396  const scalar sumMagAreas = sum(magAreas, global);
397  const scalar sumMagNbrAreas = sum(magNbrAreas, global);
398  const point ctr = sum(ctrs*magAreas, global)/sumMagAreas;
399  const point nbrCtr = sum(nbrCtrs*magNbrAreas, global)/sumMagNbrAreas;
400 
401  // Calculate patch length scales
402  const scalar lengthScale = sqrt(sumMagAreas);
403 
404  // Calculate the transformation from the patch geometry
405  if (!transformComplete_)
406  {
407  // Store the old transformation type
408  const transformTypes oldTransformType = transformType_;
409 
410  // Calculate the average patch normals
411  const vector normal = normalised(area);
412  const vector negNbrNormal = - normalised(nbrArea);
413 
414  // Calculate the angle and distance separations
415  const scalar dot = normal & negNbrNormal;
416  const vector delta = ctr - nbrCtr;
417 
418  // Determine the type of transformation if it has not been specified
419  if (transformType_ == UNSPECIFIED)
420  {
421  transformType_ =
422  dot < 1 - rootSmall
423  ? ROTATIONAL
424  : mag(delta) > lengthScale*rootSmall
425  ? TRANSLATIONAL
426  : NONE;
427  }
428 
429  // If the transformation is known to be rotational, then we need to
430  // calculate the angle. If the transformation was previously
431  // unspecified then we also need to calculate the axis and the centre
432  // of rotation.
433  if (transformType_ == ROTATIONAL)
434  {
435  // Calculate the axis, if necessary
436  if (transformType_ != oldTransformType)
437  {
438  const vector midNormal = normalised(normal + negNbrNormal);
439  const vector axis =
440  (ctr - nbrCtr)
441  ^ (
442  normal*(negNbrNormal & midNormal)
443  - negNbrNormal*(normal & midNormal)
444  );
445  const vector axis180 =
446  (ctr - nbrCtr)
447  ^ (normal - negNbrNormal);
448 
449  rotationAxis_ =
450  normalised
451  (
452  mag(axis) > lengthScale*rootSmall
453  ? axis
454  : axis180
455  );
456  }
457 
458  const tensor PerpA = tensor::I - sqr(rotationAxis_);
459  const vector normalPerpA = normalised(PerpA & normal);
460  const vector negNbrNormalPerpA = normalised(PerpA & negNbrNormal);
461  const scalar theta =
462  acos(min(max(normalPerpA & negNbrNormalPerpA, -1), 1));
463  rotationAngle_ =
464  - sign((normalPerpA ^ negNbrNormalPerpA) & rotationAxis_)
465  *radToDeg(theta);
466 
467  // Calculate the angle
468  // Calculate the centre of rotation, if necessary
469  if (transformType_ != oldTransformType)
470  {
471  const tensor R = quaternion(rotationAxis_, theta).R();
472  tensor A = tensor::I - R;
473  vector b = ctr - (R & nbrCtr);
474  const label i = findMax(cmptMag(rotationAxis_));
475  forAll(b, j)
476  {
477  A(i, j) = j == i;
478  }
479  b[i] = 0;
480  rotationCentre_ = inv(A) & b;
481  }
482  }
483 
484  // If the transformation is known to be translational then we just need
485  // to set the separation.
486  if (transformType_ == TRANSLATIONAL)
487  {
488  separation_ = delta;
489  }
490 
491  // Update the transform object
492  transformComplete_ = true;
493  update();
494 
495  // Print results of calculation
496  if (debug)
497  {
498  Info<< "Transformation calculated between patches " << name
499  << " and " << nbrName << ":" << nl << token::BEGIN_BLOCK << nl
500  << incrIndent;
501 
503 
505  }
506  }
507 
508  // Check the transformation is correct to within the matching tolerance
509  const point nbrCtrT =
510  transform_.transformPosition(nbrCtr);
511 
512  const scalar ctrNbrCtrTDistance = mag(ctr - nbrCtrT);
513 
514  if (ctrNbrCtrTDistance > lengthScale*matchTolerance)
515  {
516  OStringStream str;
517  str << "Patches " << name << " and " << nbrName << " are potentially "
518  << "not geometrically similar enough to be coupled." << nl << nl
519  << "The distance between the transformed centres of these patches "
520  << "is " << ctrNbrCtrTDistance << ", which is greater than the "
521  << "patch length scale (" << lengthScale << ") multiplied by the "
522  << "match tolerance (" << matchTolerance << ")." << nl << nl
523  << "Check that the patches are geometrically similar and that any "
524  << "transformations defined between them are correct." << nl << nl
525  << "If the patches and their transformations are defined correctly "
526  << "but small irregularities in the mesh mean this geometric test "
527  << "is failing, then it might be appropriate to relax the failure "
528  << "criteria by increasing the \"matchTolerance\" setting for "
529  << "these patches in the \"polyMesh/boundary\" file.";
531  << nl << stringOps::breakIntoIndentedLines(str.str()).c_str()
532  << exit(FatalError);
533  }
534 }
535 
536 
537 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
538 
540 {}
541 
542 
543 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
544 
546 {
547  const label oldPrecision = os.precision();
548 
549  os.precision(16);
550 
551  if (transformType_ != UNSPECIFIED)
552  {
553  writeEntry(os, "transformType", transformTypeNames[transformType_]);
554  }
555 
556  if (transformType_ == ROTATIONAL)
557  {
558  writeEntry(os, "rotationAxis", rotationAxis_);
559  writeEntry(os, "rotationCentre", rotationCentre_);
560 
561  if (transformComplete_)
562  {
563  writeEntry(os, "rotationAngle", rotationAngle_);
564  }
565  }
566 
567  if (transformType_ == TRANSLATIONAL)
568  {
569  if (transformComplete_)
570  {
571  writeEntry(os, "separation", separation_);
572  }
573  }
574 
575  os.precision(oldPrecision);
576 }
577 
578 
579 // * * * * * * * * * * * * * * * Global Operators * * * * * * * * * * * * * //
580 
581 Foam::cyclicTransform Foam::operator&
582 (
583  const transformer& t,
584  const cyclicTransform& c0
585 )
586 {
587  cyclicTransform c1(c0);
588 
589  if (c1.transformType_ == cyclicTransform::ROTATIONAL)
590  {
591  c1.rotationAxis_ = normalised(t.transform(c1.rotationAxis_));
592  c1.rotationCentre_ = t.transformPosition(c1.rotationCentre_);
593  }
594 
595  if (c1.transformType_ == cyclicTransform::TRANSLATIONAL)
596  {
597  if (c1.transformComplete_)
598  {
599  c1.separation_ = t.transform(c1.separation_);
600  }
601  }
602 
603  if (c1.transformComplete_)
604  {
605  c1.update();
606  }
607 
608  return c1;
609 }
610 
611 
613 {
614  cyclicTransform c1(c0);
615 
616  if (c1.transformType_ == cyclicTransform::ROTATIONAL)
617  {
618  if (c1.transformComplete_)
619  {
620  c1.rotationAngle_ = - c1.rotationAngle_;
621  }
622  }
623 
624  if (c1.transformType_ == cyclicTransform::TRANSLATIONAL)
625  {
626  if (c1.transformComplete_)
627  {
628  c1.separation_ = - c1.separation_;
629  }
630  }
631 
632  if (c1.transformComplete_)
633  {
634  c1.update();
635  }
636 
637  return c1;
638 }
639 
640 
641 // ************************************************************************* //
static const Foam::dimensionedScalar A("A", Foam::dimPressure, 611.21)
Istream and Ostream manipulators taking arguments.
scalar delta
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:434
Pre-declare SubField and related Field type.
Definition: Field.H:82
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:54
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.
static const NamedEnum< transformTypes, 4 > transformTypeNames
friend cyclicTransform inv(const cyclicTransform &c)
void write(Ostream &os) const
Write the data to a dictionary.
A list of keyword definitions, which are a keyword followed by any number of values (e....
Definition: dictionary.H:160
T lookupOrDefault(const word &, const T &, bool recursive=false, bool patternMatch=true) const
Find and return a T,.
ITstream & lookup(const word &, bool recursive=false, bool patternMatch=true) const
Find and return an entry data stream.
Definition: dictionary.C:860
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:871
bool found(const word &, bool recursive=false, bool patternMatch=true) const
Search dictionary for given keyword.
Definition: dictionary.C:659
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 managing temporary objects.
Definition: tmp.H:55
@ BEGIN_BLOCK
Definition: token.H:110
@ END_BLOCK
Definition: token.H:111
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
A class for handling words, derived from string.
Definition: word.H:62
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:306
gmvFile<< "tracers "<< particles.size()<< nl;forAllConstIter(Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().x()<< " ";}gmvFile<< nl;forAllConstIter(Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().y()<< " ";}gmvFile<< nl;forAllConstIter(Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.name(), 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].
static Type NaN()
Return a primitive with all components set to NaN.
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:950
Namespace for OpenFOAM.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
Type gSum(const FieldField< Field, Type > &f)
Ostream & decrIndent(Ostream &os)
Decrement the indent level.
Definition: Ostream.H:235
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
dimensionedSymmTensor sqr(const dimensionedVector &dv)
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:251
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
scalar radToDeg(const scalar rad)
Conversion from radians to degrees.
messageStream Info
Ostream & incrIndent(Ostream &os)
Increment the indent level.
Definition: Ostream.H:228
void dot(FieldField< Field1, typename innerProduct< Type1, Type2 >::type > &f, const FieldField< Field1, Type1 > &f1, const FieldField< Field2, Type2 > &f2)
layerAndWeight min(const layerAndWeight &a, const layerAndWeight &b)
void writeEntry(Ostream &os, const HashTable< T, Key, Hash > &ht)
Definition: HashTableIO.C:96
dimensionedScalar sqrt(const dimensionedScalar &ds)
T returnReduce(const T &Value, const BinaryOp &bop, const int tag=Pstream::msgType(), const label comm=UPstream::worldComm)
dimensionSet transform(const dimensionSet &)
Definition: dimensionSet.C:483
dimensioned< scalar > mag(const dimensioned< Type > &)
defineTypeNameAndDebug(combustionModel, 0)
layerAndWeight max(const layerAndWeight &a, const layerAndWeight &b)
Form normalised(const VectorSpace< Form, Cmpt, Ncmpts > &vs)
Definition: VectorSpaceI.H:413
static scalar R(const scalar a, const scalar x)
Definition: invIncGamma.C:102
dimensionSet cmptMag(const dimensionSet &)
Definition: dimensionSet.C:275
error FatalError
dimensionedSphericalTensor inv(const dimensionedSphericalTensor &dt)
word name(const complex &)
Return a string representation of a complex.
Definition: complex.C:47
Ostream & indent(Ostream &os)
Indent stream.
Definition: Ostream.H:221
static const char nl
Definition: Ostream.H:260
dimensionedScalar acos(const dimensionedScalar &ds)
scalar degToRad(const scalar deg)
Conversion from degrees to radians.
dictionary dict
Unit conversion functions.