boundSphere.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) 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 "boundSphere.H"
27 #include "DLPermutation.H"
28 #include "labelPair.H"
29 #include "triPointRef.H"
30 #include "tetPointRef.H"
31 
32 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
33 
34 template<class PointList>
35 Foam::boundSphere Foam::boundSphere::implementation::intersect
36 (
37  const PointList& ps,
38  const FixedList<label, 4>& pis,
39  const label nPs
40 )
41 {
42  switch (nPs)
43  {
44  case 0:
45  return boundSphere(point::uniform(NaN), -vGreat);
46 
47  case 1:
48  return boundSphere(ps[pis[0]], 0);
49 
50  case 2:
51  {
52  // Return a sphere centred on the mid-point between the two points
53  // and with radius half the distance between the points
54  const point c = (ps[pis[0]] + ps[pis[1]])/2;
55  return boundSphere
56  (
57  c,
58  max
59  (
60  magSqr(ps[pis[0]] - c),
61  magSqr(ps[pis[1]] - c)
62  )
63  );
64  }
65 
66  case 3:
67  {
68  // If the triangle is not degenerate then return the sphere for
69  // which a great circle is the triangles circum-circle
70  const triPointRef tri
71  (
72  ps[pis[0]],
73  ps[pis[1]],
74  ps[pis[2]]
75  );
76 
77  const boundSphere s = crSqr(tri.circumCircleSqr());
78 
79  // If the quality is less than small then return an invalid sphere
80  static const scalar ratio = small*3.0/4.0*sqrt(scalar(3));
81  if (!s.valid() || magSqr(tri.area()) < sqr(ratio)*sqr(s.rSqr_))
82  {
83  return boundSphere();
84  }
85 
86  // Re-calculate the radius to make sure that the sphere contains
87  // all the points. This makes Welzl's algorithm robust to the
88  // presence of coincident points.
89  return
90  boundSphere
91  (
92  s.c(),
93  max
94  (
95  magSqr(tri.a() - s.c()),
96  max
97  (
98  magSqr(tri.b() - s.c()),
99  magSqr(tri.c() - s.c())
100  )
101  )
102  );
103  }
104 
105  case 4:
106  {
107  // If the tetrahedron is not degenerate then return the
108  // cicrum-sphere
109  const tetPointRef tet
110  (
111  ps[pis[0]],
112  ps[pis[1]],
113  ps[pis[2]],
114  ps[pis[3]]
115  );
116 
117  const boundSphere s = crSqr(tet.circumSphereSqr());
118 
119  // If the quality is less than small then return an invalid sphere
120  static const scalar ratio = small*8.0/27.0*sqrt(scalar(3));
121  if (!s.valid() || sqr(tet.mag()) < sqr(ratio)*pow3(s.rSqr_))
122  {
123  return boundSphere();
124  }
125 
126  // Re-calculate the radius to make sure that the sphere contains
127  // all the points. This makes Welzl's algorithm robust to the
128  // presence of coincident points.
129  return
130  boundSphere
131  (
132  s.c(),
133  max
134  (
135  magSqr(tet.a() - s.c()),
136  max
137  (
138  magSqr(tet.b() - s.c()),
139  max
140  (
141  magSqr(tet.c() - s.c()),
142  magSqr(tet.d() - s.c())
143  )
144  )
145  )
146  );
147  }
148 
149  default:
151  << "Cannot compute the intersect bounding sphere of more than "
152  << "four points" << exit(FatalError);
153  return boundSphere(point::uniform(NaN), NaN);
154  }
155 }
156 
157 
158 template<class PointList>
159 Foam::boundSphere Foam::boundSphere::implementation::trivial
160 (
161  const PointList& ps,
162  const FixedList<label, 4>& pis,
163  const label nPs,
164  FixedList<label, 4>& boundaryPis
165 )
166 {
167  // Search for spheres that intersect sub-sets of the points that bound all
168  // the other points
169  switch (nPs)
170  {
171  case 0:
172  case 1:
173  case 2:
174  break;
175 
176  case 3:
177  {
178  // Test the spheres that intersect the edges
179  for (label i = 0; i < 3; ++ i)
180  {
181  const label pi0 = pis[i];
182  const label pi1 = pis[(i + 1) % 3];
183  const label piOpp = pis[(i + 2) % 3];
184 
185  const boundSphere s = intersect(ps, {pi0, pi1, -1, -1}, 2);
186 
187  if (s.contains(ps[piOpp]))
188  {
189  boundaryPis[0] = pis[i];
190  boundaryPis[1] = pis[(i + 1) % 3];
191  return s;
192  }
193  }
194 
195  break;
196  }
197 
198  case 4:
199  {
200  // Test the spheres that intersect the edges
201  static const FixedList<labelPair, 6> p01s =
202  {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}};
203  static const FixedList<labelPair, 6> pOpp01s =
204  {{2, 3}, {1, 3}, {1, 2}, {0, 3}, {0, 2}, {0, 1}};
205  for (label i = 0; i < 6; ++ i)
206  {
207  const label pi0 = pis[p01s[i].first()];
208  const label pi1 = pis[p01s[i].second()];
209  const label piOpp0 = pis[pOpp01s[i].first()];
210  const label piOpp1 = pis[pOpp01s[i].second()];
211 
212  const boundSphere s = intersect(ps, {pi0, pi1, -1, -1}, 2);
213 
214  if (s.contains(ps[piOpp0]) && s.contains(ps[piOpp1]))
215  {
216  boundaryPis[0] = pis[p01s[i].first()];
217  boundaryPis[1] = pis[p01s[i].second()];
218  return s;
219  }
220  }
221 
222  // Test the spheres that intersect the triangles
223  boundSphere minS(point::uniform(NaN), vGreat);
224  label minSi = -1;
225  for (label i = 0; i < 4; ++ i)
226  {
227  const label pi0 = pis[i];
228  const label pi1 = pis[(i + 1) % 4];
229  const label pi2 = pis[(i + 2) % 4];
230  const label piOpp = pis[(i + 3) % 4];
231 
232  const boundSphere s = intersect(ps, {pi0, pi1, pi2, -1}, 3);
233 
234  if (s.contains(ps[piOpp]) && s.rSqr_ < minS.rSqr_)
235  {
236  minS = s;
237  minSi = i;
238  }
239  }
240 
241  if (minSi != -1)
242  {
243  boundaryPis[0] = pis[minSi];
244  boundaryPis[1] = pis[(minSi + 1) % 4];
245  boundaryPis[2] = pis[(minSi + 2) % 4];
246  return minS;
247  }
248 
249  break;
250  }
251 
252  default:
254  << "Cannot compute the trivial bounding sphere of more than "
255  << "four points" << exit(FatalError);
256  return boundSphere();
257  }
258 
259  // Set all the points as on the boundary
260  for (label i = 0; i < nPs; ++ i)
261  {
262  boundaryPis[i] = pis[i];
263  }
264  sortBoundaryPis(boundaryPis);
265 
266  // Return the sphere that intersects all the points
267  return intersect(ps, pis, nPs);
268 }
269 
270 
271 template<class PointList>
272 Foam::boundSphere Foam::boundSphere::implementation::bruteForce
273 (
274  const PointList& ps,
275  FixedList<label, 4>& boundaryPis
276 )
277 {
278  // Update the currently stored bounding sphere if the supplied set of
279  // points defines a smaller bounding sphere
280  auto update = []
281  (
282  const PointList& ps,
283  const FixedList<label, 4>& pis,
284  const label nPs,
285  boundSphere& s,
286  FixedList<label, 4>& boundaryPis
287  )
288  {
289  // Construct the intersect sphere for these points
290  const boundSphere sStar = intersect(ps, pis, nPs);
291 
292  // Determine if the sphere bounds all the points, excluding any
293  // actually on the boundary so as to prevent round-off error issues
294  bool bounds = true;
295  for (label pi = 0; bounds && pi < ps.size(); ++ pi)
296  {
297  label i = 0;
298  for (; i < nPs; ++ i) if (pi == pis[i]) break;
299  if (i != nPs) continue;
300 
301  if (!sStar.contains(ps[pi])) bounds = false;
302  }
303 
304  // If this sphere bounds everything and is smaller than the current
305  // sphere then update
306  if (bounds && sStar.rSqr() < s.rSqr())
307  {
308  s = sStar;
309  boundaryPis = pis;
310  }
311  };
312 
313  // Return an invalid sphere if there are no points
314  if (ps.size() == 0) return boundSphere();
315 
316  // Initialise a very large sphere
317  boundSphere s = boundSphere::crSqr(point::uniform(NaN), vGreat);
318  boundaryPis = -1;
319 
320  // Search through all combinations of points to find the minimum sphere
321  FixedList<label, 4> pis({-1, -1, -1, -1});
322  for (pis[0] = 0; pis[0] < ps.size(); ++ pis[0])
323  {
324  update(ps, pis, 1, s, boundaryPis);
325  for (pis[1] = pis[0] + 1; pis[1] < ps.size(); ++ pis[1])
326  {
327  update(ps, pis, 2, s, boundaryPis);
328  for (pis[2] = pis[1] + 1; pis[2] < ps.size(); ++ pis[2])
329  {
330  update(ps, pis, 3, s, boundaryPis);
331  for (pis[3] = pis[2] + 1; pis[3] < ps.size(); ++ pis[3])
332  {
333  update(ps, pis, 4, s, boundaryPis);
334  }
335  pis[3] = -1;
336  }
337  pis[2] = -1;
338  }
339  pis[1] = -1;
340  }
341 
342  return s;
343 }
344 
345 
346 template<class PointList, class DLPermutation>
347 Foam::boundSphere Foam::boundSphere::implementation::Welzl
348 (
349  const PointList& ps,
350  DLPermutation& pis,
351  const typename DLPermutation::const_iterator& pisEnd,
352  FixedList<label, 4>& boundaryPis,
353  const label nBoundaryPs
354 )
355 {
356  boundSphere s = intersect(ps, boundaryPis, nBoundaryPs);
357 
358  if (pisEnd == pis.rend() || nBoundaryPs == 4) return s;
359 
360  for (label boundaryi = nBoundaryPs; boundaryi < 4; ++ boundaryi)
361  {
362  boundaryPis[boundaryi] = -1;
363  }
364 
365  for
366  (
367  typename DLPermutation::const_iterator piIter = pis.begin();
368  piIter != pisEnd;
369  ++ piIter
370  )
371  {
372  if (s.contains(ps[*piIter])) continue;
373 
374  boundaryPis[nBoundaryPs] = *piIter;
375 
376  s = Welzl(ps, pis, piIter, boundaryPis, nBoundaryPs + 1);
377 
378  // Move this point up to the start
379  if (piIter != pis.begin())
380  {
381  const label i = *piIter;
382  piIter = piIter.previous();
383  pis.raise(i);
384  }
385  }
386 
387  return s;
388 }
389 
390 
391 template<class PointList>
392 Foam::boundSphere Foam::boundSphere::implementation::local
393 (
394  const PointList& ps,
395  randomGenerator& rndGen,
396  FixedList<label, 4>& boundaryPis
397 )
398 {
399  if (ps.size() <= 4)
400  {
401  static const FixedList<label, 4> pis({0, 1, 2, 3});
402  boundaryPis = -1;
403  return trivial(ps, pis, ps.size(), boundaryPis);
404  }
405  else
406  {
407  dLPermutation pis(ps.size(), rndGen);
408  boundaryPis = -1;
409  boundSphere s = Welzl(ps, pis, pis.end(), boundaryPis, 0);
410  sortBoundaryPis(boundaryPis);
411  return s;
412  }
413 }
414 
415 
416 template<class PointList>
417 Foam::boundSphere Foam::boundSphere::implementation::global
418 (
419  const PointList& ps,
420  randomGenerator& rndGen,
421  FixedList<RemoteData<point>, 4>& boundaryPs,
422  const bool strict
423 )
424 {
425  // Run the local algorithm if serial
426  if (!Pstream::parRun())
427  {
428  FixedList<label, 4> boundaryPis({-1, -1, -1, -1});
429 
430  const boundSphere s = local(ps, rndGen, boundaryPis);
431 
432  forAll(boundaryPis, boundaryi)
433  {
434  if (boundaryPis[boundaryi] != -1)
435  {
436  boundaryPs[boundaryi] =
437  RemoteData<point>
438  (
439  Pstream::myProcNo(),
440  boundaryPis[boundaryi],
441  ps[boundaryPis[boundaryi]]
442  );
443  }
444  }
445 
446  return s;
447  }
448 
449  LocalAndRemotePoints<PointList> larps(ps);
450 
451  dynamicDLPermutation pis(ps.size(), rndGen);
452  FixedList<label, 4> boundaryPis({-1, -1, -1, -1});
453 
454  while (true)
455  {
456  // Calculate the sphere for the current set of points
457  const boundSphere s = Welzl(larps, pis, pis.end(), boundaryPis, 0);
458 
459  // Communicate the boundary points
460  List<FixedList<RemoteData<point>, 4>> procBoundaryPs(Pstream::nProcs());
461  {
462  label boundaryi = 0;
463  for (; boundaryi < 4 && boundaryPis[boundaryi] != -1; ++ boundaryi)
464  {
465  procBoundaryPs[Pstream::myProcNo()][boundaryi] =
466  larps(boundaryPis[boundaryi]);
467  }
468 
469  sortBoundaryPis(procBoundaryPs[Pstream::myProcNo()]);
470 
471  Pstream::gatherList(procBoundaryPs);
472  Pstream::scatterList(procBoundaryPs);
473  }
474 
475  // Determine if all boundary points are the same on all processors. If
476  // they are then the sphere is complete.
477  bool complete = true;
478  forAll(procBoundaryPs, proci)
479  {
480  if (proci == Pstream::myProcNo()) continue;
481 
482  for (label boundaryi = 0; boundaryi < 4; ++ boundaryi)
483  {
484  const remote& r = procBoundaryPs[proci][boundaryi];
485 
486  if (r != procBoundaryPs[Pstream::myProcNo()][boundaryi])
487  {
488  complete = false;
489  }
490 
491  if (!complete) break;
492  }
493 
494  if (!complete) break;
495  }
496 
497  if (complete)
498  {
499  boundaryPs = procBoundaryPs[Pstream::myProcNo()];
500  return s;
501  }
502 
503  // Add remote boundary points to the local lists and iterate
504  complete = true;
505  forAll(procBoundaryPs, proci)
506  {
507  if (proci == Pstream::myProcNo()) continue;
508 
509  for (label boundaryi = 0; boundaryi < 4; ++ boundaryi)
510  {
511  const RemoteData<point>& rp = procBoundaryPs[proci][boundaryi];
512 
513  if (rp.proci == -1) break;
514  if (larps.found(rp)) continue;
515 
516  if (s.contains(rp.data)) continue;
517 
518  larps.insertNoCheck(rp);
519  pis.prepend();
520 
521  complete = false;
522  }
523  }
524 
525  // If nothing was added on any processor then the state has stopped
526  // changing. If we are being strict, then this should never happen, so
527  // raise an error. However, this can also happen due to ambiguous cases
528  // for which multiple solutions are valid to within round-off error
529  // (e.g., a cube), in which case the different ordering on different
530  // processes can result in a different (but still valid) solution. So,
531  // if we are not being strict then just return the sphere and the
532  // boundary points from the master process.
533  if (returnReduce(complete, andOp<bool>()))
534  {
535  if (strict)
536  {
538  << "Global bound sphere iteration did not converge"
539  << exit(FatalError);
540  }
541 
542  boundaryPs = procBoundaryPs[Pstream::master()];
543  return s;
544  }
545  }
546 
547  return boundSphere();
548 }
549 
550 
551 #define InstantiateBoundSphere(PointList) \
552  \
553  template Foam::boundSphere Foam::boundSphere::implementation::intersect \
554  ( \
555  const PointList& ps, \
556  const FixedList<label, 4>& pis, \
557  const label nPs \
558  ); \
559  \
560  template Foam::boundSphere Foam::boundSphere::implementation::trivial \
561  ( \
562  const PointList& ps, \
563  const FixedList<label, 4>& pis, \
564  const label nPs, \
565  FixedList<label, 4>& boundaryPis \
566  ); \
567  \
568  template Foam::boundSphere Foam::boundSphere::implementation::bruteForce \
569  ( \
570  const PointList& ps, \
571  FixedList<label, 4>& boundaryPis \
572  ); \
573  \
574  template Foam::boundSphere Foam::boundSphere::implementation::local \
575  ( \
576  const PointList& ps, \
577  randomGenerator& rndGen, \
578  FixedList<label, 4>& boundaryPis \
579  ); \
580  \
581  template Foam::boundSphere Foam::boundSphere::implementation::global \
582  ( \
583  const PointList& ps, \
584  randomGenerator& rndGen, \
585  FixedList<RemoteData<point>, 4>& boundaryPis, \
586  const bool strict \
587  );
588 
589 
590 InstantiateBoundSphere(UList<point>);
591 InstantiateBoundSphere(UIndirectList<point>);
592 
593 
594 #undef InstantiateBoundSphere
595 
596 
597 // * * * * * * * * * * * * * * IOstream operators * * * * * * * * * * * * * //
598 
600 {
601  is.readBegin("boundSphere");
602  is >> s.c_ >> s.rSqr_;
603  is.readEnd("boundSphere");
604 
605  // Check state of Istream
606  is.check("operator>>(Istream&, boundSphere&)");
607 
608  if (is.format() == IOstream::ASCII && s.valid())
609  {
610  s.rSqr_ = sqr(s.rSqr_);
611  }
612 
613  return is;
614 }
615 
616 
618 {
619  os << s.c_ << token::SPACE;
620 
621  if (os.format() == IOstream::ASCII && s.valid())
622  {
623  os << sqrt(s.rSqr_);
624  }
625  else
626  {
627  os << s.rSqr_;
628  }
629 
630  return os;
631 }
632 
633 
634 // ************************************************************************* //
635 
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:449
#define InstantiateBoundSphere(PointList)
Definition: boundSphere.C:551
streamFormat format() const
Return current stream format.
Definition: IOstream.H:377
virtual bool check(const char *operation) const
Check IOstream status for given operation.
Definition: IOstream.C:108
An Istream is an abstract base class for all input systems (streams, files, token lists etc)....
Definition: Istream.H:60
Istream & readEnd(const char *funcName)
Definition: Istream.C:123
Istream & readBegin(const char *funcName)
Definition: Istream.C:106
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:57
The smallest sphere enclosing a given set of points.
Definition: boundSphere.H:63
Motion of the mesh specified as a list of pointMeshMovers.
#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))
const dimensionedScalar c
Speed of light in a vacuum.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
Istream & operator>>(Istream &, pointEdgeDist &)
Definition: pointEdgeDist.C:41
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
vector point
Point is a vector.
Definition: point.H:41
tmp< DimensionedField< typename outerProduct< Type, Type >::type, GeoMesh, Field >> sqr(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
T returnReduce(const T &Value, const BinaryOp &bop, const int tag=Pstream::msgType(), const label comm=UPstream::worldComm)
void pow3(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
DLPermutation< DynamicList< label > > dynamicDLPermutation
Define the type of a permutation based on a dynamic list.
triangle< point, const point & > triPointRef
Definition: triPointRef.H:44
Ostream & operator<<(Ostream &os, const fvConstraints &constraints)
error FatalError
void sqrt(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
tmp< DimensionedField< scalar, GeoMesh, Field > > magSqr(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dimensioned< Type > max(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
tetrahedron< point, const point & > tetPointRef
Definition: tetPointRef.H:44
DLPermutation< List< label > > dLPermutation
Define the type of a permutation based on a list.
randomGenerator rndGen(653213)