advancingFrontAMI.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) 2013-2016 OpenFOAM Foundation
9  Copyright (C) 2015-2022,2024 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
29 #include "advancingFrontAMI.H"
30 #include "meshTools.H"
31 #include "mapDistribute.H"
32 #include "unitConversion.H"
33 
34 #include "findNearestMaskedOp.H"
35 
36 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
37 
38 namespace Foam
39 {
40  defineTypeNameAndDebug(advancingFrontAMI, 0);
41 }
42 
43 
46 {
47  { areaNormalisationMode::project, "project" },
48  { areaNormalisationMode::mag, "mag" },
49 };
50 
51 // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
52 
54 {
55  const auto& src = srcPatch();
56  const auto& tgt = tgtPatch();
57 
58  if (debug && (!src.size() || !tgt.size()))
59  {
60  Pout<< "AMI: Patches not on processor: Source faces = "
61  << src.size() << ", target faces = " << tgt.size()
62  << endl;
63  }
64 
65 
66  if (requireMatch_)
67  {
68  const scalar maxBoundsError = 0.05;
69 
70  // Check bounds of source and target
71  boundBox bbSrc(src.points(), src.meshPoints(), false);
73  (
74  bbSrc.min(),
75  minOp<point>(),
77  comm_
78  );
80  (
81  bbSrc.max(),
82  maxOp<point>(),
84  comm_
85  );
86  boundBox bbTgt(tgt.points(), tgt.meshPoints(), false);
88  (
89  bbTgt.min(),
90  minOp<point>(),
92  comm_
93  );
95  (
96  bbTgt.max(),
97  maxOp<point>(),
99  comm_
100  );
101 
102  boundBox bbTgtInf(bbTgt);
103  bbTgtInf.inflate(maxBoundsError);
104 
105  if (!bbTgtInf.contains(bbSrc))
106  {
108  << "Source and target patch bounding boxes are not similar"
109  << nl
110  << " source box span : " << bbSrc.span() << nl
111  << " target box span : " << bbTgt.span() << nl
112  << " source box : " << bbSrc << nl
113  << " target box : " << bbTgt << nl
114  << " inflated target box : " << bbTgtInf << endl;
115  }
116  }
117 }
118 
119 
121 (
122  const label srcFacei,
123  const label tgtFacei
124 ) const
125 {
126  const auto& srcPatch = this->srcPatch();
127  const auto& tgtPatch = this->tgtPatch();
128 
129  if
130  (
131  (srcMagSf_[srcFacei] < ROOTVSMALL)
132  || (tgtMagSf_[tgtFacei] < ROOTVSMALL)
133  )
134  {
135  return false;
136  }
137 
138  if (maxDistance2_ > 0)
139  {
140  const point& srcFc = srcPatch.faceCentres()[srcFacei];
141  const point& tgtFc = tgtPatch.faceCentres()[tgtFacei];
142  const vector& srcN = srcPatch.faceNormals()[srcFacei];
143 
144  const scalar normalDist((tgtFc-srcFc)&srcN);
145  //if (magSqr(srcFc-tgtFc) >= maxDistance2_)
146  if (sqr(normalDist) >= maxDistance2_)
147  {
148  return false;
149  }
150  }
151 
152  if (minCosAngle_ > -1)
153  {
154  const vector& srcN = srcPatch.faceNormals()[srcFacei];
155  vector tgtN = tgtPatch.faceNormals()[tgtFacei];
156  if (!reverseTarget_)
157  {
158  tgtN = -tgtN;
159  }
160 
161  if ((srcN & tgtN) <= minCosAngle_)
162  {
163  return false;
164  }
165  }
166 
167  return true;
168 }
169 
170 
172 {
173  // Create processor map of extended cells. This map gets (possibly
174  // remote) cells from the src mesh such that they (together) cover
175  // all of tgt
176  extendedTgtMapPtr_.reset(calcProcMap(srcPatch0(), tgtPatch0()));
177  const mapDistribute& map = extendedTgtMapPtr_();
178 
179  // Original faces from tgtPatch
180  // Note: in globalIndexing since might be remote
181  globalIndex globalTgtFaces(tgtPatch0().size(), comm_);
182  distributeAndMergePatches
183  (
184  map,
185  tgtPatch0(),
186  globalTgtFaces,
187  extendedTgtFaces_,
188  extendedTgtPoints_,
189  extendedTgtFaceIDs_
190  );
191 
192  // Create a representation of the tgt patch that is extended to overlap
193  // the src patch
194  extendedTgtPatchPtr_.reset
195  (
197  (
198  SubList<face>(extendedTgtFaces_),
199  extendedTgtPoints_
200  )
201  );
202 }
203 
204 
205 bool Foam::advancingFrontAMI::initialiseWalk(label& srcFacei, label& tgtFacei)
206 {
207  const auto& src = this->srcPatch();
208  const auto& tgt = this->tgtPatch();
209 
210  bool foundFace = false;
211 
212  // Check that patch sizes are valid
213  if (!src.size())
214  {
215  return foundFace;
216  }
217  else if (!tgt.size())
218  {
220  << src.size() << " source faces but no target faces" << endl;
221 
222  return foundFace;
223  }
224 
225  // Reset the octree
226  treePtr_.reset(createTree(tgt));
227 
228  // Find initial face match using brute force/octree search
229  if ((srcFacei == -1) || (tgtFacei == -1))
230  {
231  srcFacei = 0;
232  tgtFacei = 0;
233  forAll(src, facei)
234  {
235  tgtFacei = findTargetFace(facei);
236  if (tgtFacei >= 0)
237  {
238  srcFacei = facei;
239  foundFace = true;
240  break;
241  }
242  }
243 
244  if (!foundFace)
245  {
246  if (requireMatch_)
247  {
249  << "Unable to find initial target face"
250  << abort(FatalError);
251  }
252 
253  return foundFace;
254  }
255  }
256 
257  if (debug)
258  {
259  Pout<< "AMI: initial target face = " << tgtFacei << endl;
260  }
261 
262  return true;
263 }
264 
265 
267 (
268  const scalar area,
269  const face& f1,
270  const face& f2,
271  const pointField& f1Points,
272  const pointField& f2Points
273 ) const
274 {
275  static label count = 1;
276 
277  const pointField f1pts = f1.points(f1Points);
278  const pointField f2pts = f2.points(f2Points);
279 
280  Pout<< "Face intersection area (" << count << "):" << nl
281  << " f1 face = " << f1 << nl
282  << " f1 pts = " << f1pts << nl
283  << " f2 face = " << f2 << nl
284  << " f2 pts = " << f2pts << nl
285  << " area = " << area
286  << endl;
287 
288  OFstream os("areas" + name(count) + ".obj");
289 
290  for (const point& pt : f1pts)
291  {
292  meshTools::writeOBJ(os, pt);
293  }
294  os<< "l";
295  forAll(f1pts, i)
296  {
297  os<< " " << i + 1;
298  }
299  os<< " 1" << endl;
300 
301  for (const point& pt : f2pts)
302  {
303  meshTools::writeOBJ(os, pt);
304  }
305  os<< "l";
306  const label n = f1pts.size();
307  forAll(f2pts, i)
308  {
309  os<< " " << n + i + 1;
310  }
311  os<< " " << n + 1 << endl;
312 
313  ++count;
314 }
315 
316 
318 (
319  const label srcFacei,
320  const UList<label>& excludeFaces,
321  const label srcFacePti
322 ) const
323 {
324  const auto& src = srcPatch();
325 
326  label targetFacei = -1;
327 
328  const pointField& srcPts = src.points();
329  const face& srcFace = src[srcFacei];
330 
331  findNearestMaskedOp<primitivePatch> fnOp(*treePtr_, excludeFaces);
332 
333  const boundBox bb(srcPts, srcFace, false);
334 
335  const point srcPt =
336  srcFacePti == -1 ? bb.centre() : srcPts[srcFace[srcFacePti]];
337 
338  pointIndexHit sample =
339  treePtr_->findNearest(srcPt, 0.25*bb.magSqr(), fnOp);
340 
341  if (!sample.hit())
342  {
343  // Fall-back for extreme cases. Should only occur sparsely
344  sample = treePtr_->findNearest(srcPt, Foam::sqr(GREAT), fnOp);
345  }
346 
347  if (sample.hit() && isCandidate(srcFacei, sample.index()))
348  {
349  targetFacei = sample.index();
350 
351  if (debug)
352  {
353  Pout<< "Source point = " << srcPt << ", Sample point = "
354  << sample.point() << ", Sample index = " << sample.index()
355  << endl;
356  }
357  }
358 
359  return targetFacei;
360 }
361 
362 
364 (
365  const label facei,
366  const primitivePatch& patch,
367  const DynamicList<label>& visitedFaces,
368  DynamicList<label>& faceIDs
369 ) const
370 {
371  static const scalar thetaCos = Foam::cos(degToRad(89.0));
372 
373  const labelList& nbrFaces = patch.faceFaces()[facei];
374 
375  // Filter out faces already visited from face neighbours
376  for (const label nbrFacei : nbrFaces)
377  {
378  // Prevent addition of face if it is not on the same plane-ish
379  if (!visitedFaces.found(nbrFacei) && !faceIDs.found(nbrFacei))
380  {
381  const vector& n1 = patch.faceNormals()[facei];
382  const vector& n2 = patch.faceNormals()[nbrFacei];
383 
384  const scalar cosI = n1 & n2;
385 
386  if (cosI > thetaCos)
387  {
388  faceIDs.append(nbrFacei);
389  }
390  }
391  }
392 }
393 
394 
396 (
397  const primitivePatch& patch,
398  List<DynamicList<face>>& tris,
399  List<scalar>& magSf
400 ) const
401 {
402  const pointField& points = patch.points();
403  tris.setSize(patch.size());
404  magSf.setSize(patch.size());
405 
406  const auto& faceNormals = patch.faceNormals();
407 
408  // Using methods that index into existing points
409  forAll(patch, facei)
410  {
411  tris[facei].clear();
412 
413  switch (triMode_)
414  {
416  {
417  faceAreaIntersect::triangleFan(patch[facei], tris[facei]);
418  break;
419  }
421  {
422  patch[facei].triangles(points, tris[facei]);
423  break;
424  }
425  }
426 
427  const DynamicList<face>& triFaces = tris[facei];
428  magSf[facei] = 0;
429 
430  switch (areaNormalisationMode_)
431  {
432  case areaNormalisationMode::project:
433  {
434  for (const face& f : triFaces)
435  {
436  magSf[facei] +=
438  (
439  points[f[0]],
440  points[f[1]],
441  points[f[2]]
442  ).areaNormal()
443  & faceNormals[facei];
444  }
445  break;
446  }
448  {
449  for (const face& f : triFaces)
450  {
451  magSf[facei] +=
453  (
454  points[f[0]],
455  points[f[1]],
456  points[f[2]]
457  ).mag();
458  }
459  break;
460  }
461  }
462  }
463 }
464 
465 
467 {
468  if (!requireMatch_ && distributed())
469  {
470  scalarList newTgtMagSf(std::move(tgtMagSf_));
471 
472  // Assign default sizes. Override selected values with calculated
473  // values. This is to support ACMI where some of the target faces
474  // are never used (so never get sent over and hence never assigned
475  // to)
476  tgtMagSf_ = tgtPatch0().magFaceAreas();
477 
478  for (const labelList& smap : this->extendedTgtMapPtr_->subMap())
479  {
480  UIndirectList<scalar>(tgtMagSf_, smap) =
481  UIndirectList<scalar>(newTgtMagSf, smap);
482  }
483  }
484 }
485 
486 
487 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
488 
490 (
491  const dictionary& dict,
492  const bool reverseTarget
493 )
494 :
495  AMIInterpolation(dict, reverseTarget),
496  maxDistance2_(dict.getOrDefault<scalar>("maxDistance2", -1)),
497  minCosAngle_(dict.getOrDefault<scalar>("minCosAngle", -1)),
498  srcTris_(),
499  tgtTris_(),
500  extendedTgtPatchPtr_(nullptr),
501  extendedTgtFaces_(),
502  extendedTgtPoints_(),
503  extendedTgtFaceIDs_(),
504  extendedTgtMapPtr_(nullptr),
505  srcNonOverlap_(),
506  triMode_
507  (
508  faceAreaIntersect::triangulationModeNames_.getOrDefault
509  (
510  "triMode",
511  dict,
512  faceAreaIntersect::tmMesh
513  )
514  ),
515  areaNormalisationMode_
516  (
517  areaNormalisationModeNames_.getOrDefault
518  (
519  "areaNormalisationMode",
520  dict,
521  areaNormalisationMode::project
522  )
523  )
524 {
525  DebugInfo
526  << "AMI: maxDistance2:" << maxDistance2_
527  << " minCosAngle:" << minCosAngle_
529  << " areaNormalisationMode:"
531  << endl;
532 }
533 
534 
536 (
537  const bool requireMatch,
538  const bool reverseTarget,
539  const scalar lowWeightCorrection,
541 )
542 :
543  AMIInterpolation(requireMatch, reverseTarget, lowWeightCorrection),
544  maxDistance2_(-1),
545  minCosAngle_(-1),
546  srcTris_(),
547  tgtTris_(),
548  extendedTgtPatchPtr_(nullptr),
549  extendedTgtFaces_(),
550  extendedTgtPoints_(),
551  extendedTgtFaceIDs_(),
552  extendedTgtMapPtr_(nullptr),
553  srcNonOverlap_(),
554  triMode_(triMode),
555  areaNormalisationMode_(areaNormalisationMode::project)
556 {}
557 
558 
560 :
561  AMIInterpolation(ami),
562  maxDistance2_(ami.maxDistance2_),
563  minCosAngle_(ami.minCosAngle_),
564  srcTris_(),
565  tgtTris_(),
566  extendedTgtPatchPtr_(nullptr),
567  extendedTgtFaces_(),
568  extendedTgtPoints_(),
569  extendedTgtFaceIDs_(),
570  extendedTgtMapPtr_(nullptr),
571  srcNonOverlap_(),
572  triMode_(ami.triMode_),
573  areaNormalisationMode_(ami.areaNormalisationMode_)
574 {}
575 
576 
577 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
578 
580 (
581  const primitivePatch& srcPatch,
582  const primitivePatch& tgtPatch,
583  const autoPtr<searchableSurface>& surfPtr
584 )
585 {
586  if (AMIInterpolation::calculate(srcPatch, tgtPatch, surfPtr))
587  {
588  // Create a representation of the target patch that covers the source
589  // patch
590  if (distributed())
591  {
592  createExtendedTgtPatch();
593  }
594 
595  const auto& src = this->srcPatch();
596  const auto& tgt = this->tgtPatch();
597 
598 
599  if (maxDistance2_ > 0)
600  {
601  // Early trigger face centre calculation
602  (void)src.faceCentres();
603  (void)tgt.faceCentres();
604  // Early trigger face normals calculation
605  (void)src.faceNormals();
606  (void)tgt.faceNormals();
607  }
608  if (minCosAngle_ > -1)
609  {
610  // Early trigger face normals calculation
611  (void)src.faceNormals();
612  (void)tgt.faceNormals();
613  }
614 
615 
616  // Initialise area magnitudes
617  srcMagSf_.setSize(src.size(), 1.0);
618  tgtMagSf_.setSize(tgt.size(), 1.0);
619 
620  // Source and target patch triangulations
621  triangulatePatch(src, srcTris_, srcMagSf_);
622  triangulatePatch(tgt, tgtTris_, tgtMagSf_);
623 
624  checkPatches();
625 
626  // Set initial sizes for weights and addressing - must be done even if
627  // returns false below
628  srcAddress_.setSize(src.size());
629  srcWeights_.setSize(src.size());
630  tgtAddress_.setSize(tgt.size());
631  tgtWeights_.setSize(tgt.size());
632 
633  return true;
634  }
635 
636  return false;
637 }
638 
639 
640 void Foam::advancingFrontAMI::write(Ostream& os) const
641 {
643  os.writeEntryIfDifferent<scalar>("maxDistance2", -1, maxDistance2_);
644  os.writeEntryIfDifferent<scalar>("minCosAngle", -1, minCosAngle_);
646  (
647  "triMode",
650  );
652  (
653  "areaNormalisationMode",
654  areaNormalisationModeNames_[areaNormalisationMode::project],
655  areaNormalisationModeNames_[areaNormalisationMode_]
656  );
657 }
658 
659 
660 // ************************************************************************* //
List< scalar > scalarList
List of scalar.
Definition: scalarList.H:32
areaNormalisationMode
Area normalisation mode.
label findTargetFace(const label srcFacei, const UList< label > &excludeFaces=UList< label >::null(), const label srcFacePti=-1) const
dictionary dict
const faceAreaIntersect::triangulationMode triMode_
Face triangulation mode.
void writeIntersectionOBJ(const scalar area, const face &f1, const face &f2, const pointField &f1Points, const pointField &f2Points) const
Write triangle intersection to OBJ file.
A face is a list of labels corresponding to mesh vertices.
Definition: face.H:68
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:608
const scalar maxDistance2_
Maximum squared distance.
static const Enum< areaNormalisationMode > areaNormalisationModeNames_
dimensionedSymmTensor sqr(const dimensionedVector &dv)
Unit conversion functions.
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
areaNormalisationMode areaNormalisationMode_
Area normalisation mode; default = project.
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
bool initialiseWalk(label &srcFacei, label &tgtFacei)
Initialise walk and return true if all ok.
A bounding box defined in terms of min/max extrema points.
Definition: boundBox.H:63
static int & msgType() noexcept
Message tag of standard messages.
Definition: UPstream.H:1252
This class describes the interaction of an object (often a face) and a point. It carries the info of ...
Definition: pointIndexHit.H:44
void writeOBJ(Ostream &os, const point &pt)
Write obj representation of a point.
Definition: meshTools.C:196
void createExtendedTgtPatch()
Create a map that extends tgtPatch so that it covers srcPatch.
label comm_
Communicator to use for parallel operations.
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
const scalar minCosAngle_
Minimum (cos of) angle. 1 for perfectly matching.
virtual void write(Ostream &os) const
Write.
const primitivePatch & tgtPatch() const
Return const access to the target patch.
const Field< point_type > & faceNormals() const
Return face unit normals for patch.
scalar magSqr() const
The magnitude/length squared of bounding box diagonal.
Definition: boundBoxI.H:204
A list of faces which address into the list of points.
unsigned int count(const UList< bool > &bools, const bool val=true)
Count number of &#39;true&#39; entries.
Definition: BitOps.H:73
const point_type & point() const noexcept
Return point, no checks.
A List obtained as a section of another List.
Definition: SubList.H:50
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:38
virtual bool calculate(const primitivePatch &srcPatch, const primitivePatch &tgtPatch, const autoPtr< searchableSurface > &surfPtr=nullptr)
Update addressing, weights and (optional) centroids.
dimensionedScalar cos(const dimensionedScalar &ds)
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for expressions::valueTypeCode::INVALID.
Definition: exprTraits.C:127
const pointField & points
static autoPtr< indexedOctree< treeDataPoint > > createTree(const pointField &points)
Construct search tree for points.
const wordList area
Standard area field types (scalar, vector, tensor, etc)
const Field< point_type > & faceCentres() const
Return face centres for patch.
Vector< scalar > vector
Definition: vector.H:57
label index() const noexcept
Return the hit index.
errorManip< error > abort(error &err)
Definition: errorManip.H:139
Ostream & writeEntryIfDifferent(const word &key, const T &value1, const T &value2)
Write a keyword/value entry only when the two values differ.
Definition: Ostream.H:336
#define DebugInfo
Report an information message using Foam::Info.
A Vector of values with scalar precision, where scalar is float/double depending on the compilation f...
bool isCandidate(const label srcFacei, const label tgtFacei) const
Is source/target a valid pair (i.e. not too far/different.
int debug
Static debugging option.
OBJstream os(runTime.globalPath()/outputName)
defineTypeNameAndDebug(combustionModel, 0)
static void triangleFan(const face &f, DynamicList< face > &faces)
Decompose face into triangle fan.
labelList f(nPoints)
PrimitivePatch< SubList< face >, const pointField & > primitivePatch
A PrimitivePatch with a SubList addressing for the faces, const reference for the point field...
void checkPatches() const
Check AMI patch coupling.
void appendNbrFaces(const label facei, const primitivePatch &patch, const DynamicList< label > &visitedFaces, DynamicList< label > &faceIDs) const
Add faces neighbouring facei to the ID list.
virtual void nonConformalCorrection()
Correction for non-conformal interpolations, e.g. for ACMI.
virtual bool calculate(const primitivePatch &srcPatch, const primitivePatch &tgtPatch, const autoPtr< searchableSurface > &surfPtr=nullptr)
Update addressing, weights and (optional) centroids.
bool hit() const noexcept
Is there a hit?
bool requireMatch_
Flag to indicate that the two patches must be matched/an overlap exists between them.
vector point
Point is a vector.
Definition: point.H:37
#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
void triangulatePatch(const primitivePatch &patch, List< DynamicList< face >> &tris, List< scalar > &magSf) const
Helper function to decompose a patch.
Base class for Arbitrary Mesh Interface (AMI) methods.
Interpolation class dealing with transfer of data between two primitive patches with an arbitrary mes...
const std::string patch
OpenFOAM patch number as a std::string.
triangle< point, const point & > triPointRef
A triangle using referred points.
Definition: triangleFwd.H:39
label n
static const Enum< triangulationMode > triangulationModeNames_
Pointer management similar to std::unique_ptr, with some additional methods and type checking...
Definition: HashPtrTable.H:48
List< label > labelList
A List of labels.
Definition: List.H:62
static autoPtr< T > New(Args &&... args)
Construct autoPtr with forwarding arguments.
Definition: autoPtr.H:178
point centre() const
The centre (midpoint) of the bounding box.
Definition: boundBoxI.H:186
constexpr scalar degToRad(const scalar deg) noexcept
Conversion from degrees to radians.
const primitivePatch & srcPatch() const
Return const access to the source patch.
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
void reduce(T &value, const BinaryOp &bop, const int tag=UPstream::msgType(), const label comm=UPstream::worldComm)
Reduce inplace (cf. MPI Allreduce) using linear/tree communication schedule.
Namespace for OpenFOAM.
virtual void write(Ostream &os) const
Write AMI as a dictionary.
advancingFrontAMI(const dictionary &dict, const bool reverseTarget)
Construct from components.