distanceSurfaceFilter.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) 2020-2022 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 \*---------------------------------------------------------------------------*/
27 
28 #include "distanceSurface.H"
29 #include "regionSplit.H"
30 #include "syncTools.H"
31 #include "ListOps.H"
32 #include "vtkSurfaceWriter.H"
33 
34 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
35 
37 (
38  bitSet& ignoreCells,
39  const isoSurfaceBase& isoCutter
40 ) const
41 {
42  // With the cell/point distance fields we can take a second pass at
43  // pre-filtering.
44  // This duplicates how cut detection is determined in the cell/topo
45  // algorithms but is fairly inexpensive (creates no geometry)
46 
47  bool changed = false;
48 
49  for (label celli = 0; celli < mesh_.nCells(); ++celli)
50  {
51  if (ignoreCells.test(celli))
52  {
53  continue;
54  }
55 
56  auto cut = isoCutter.getCellCutType(celli);
57  if (!(cut & isoSurfaceBase::ANYCUT))
58  {
59  ignoreCells.set(celli);
60  changed = true;
61  }
62  }
63 
64  return changed;
65 }
66 
67 
69 (
70  const bitSet& ignoreCells
71 ) const
72 {
73  // Prepare for region split
74 
75  bitSet blockedFaces(mesh_.nFaces());
76 
77  const labelList& faceOwn = mesh_.faceOwner();
78  const labelList& faceNei = mesh_.faceNeighbour();
79 
80  // Could be more efficient
81  for (label facei = 0; facei < mesh_.nInternalFaces(); ++facei)
82  {
83  // If only one cell is blocked, the face corresponds
84  // to an exposed subMesh face
85 
86  if
87  (
88  ignoreCells.test(faceOwn[facei])
89  != ignoreCells.test(faceNei[facei])
90  )
91  {
92  blockedFaces.set(facei);
93  }
94  }
95 
96  for (const polyPatch& patch : mesh_.boundaryMesh())
97  {
98  if (!patch.coupled())
99  {
100  continue;
101  }
102 
103  forAll(patch, patchFacei)
104  {
105  const label facei = patch.start() + patchFacei;
106  if (ignoreCells.test(faceOwn[facei]))
107  {
108  blockedFaces.set(facei);
109  }
110  }
111  }
112 
114 
115  return blockedFaces;
116 }
117 
118 
120 (
121  bitSet& ignoreCells
122 ) const
123 {
124  // For region split
125  bitSet blockedFaces(filterPrepareRegionSplit(ignoreCells));
126 
127  // Split region
128  regionSplit rs(mesh_, blockedFaces);
129  blockedFaces.clearStorage();
130 
131  const labelList& regionColour = rs;
132 
133  // Identical number of regions on all processors
134  labelList nCutsPerRegion(rs.nRegions(), Zero);
135 
136  // Count cut cells (ie, unblocked)
137  forAll(regionColour, celli)
138  {
139  if (!ignoreCells.test(celli))
140  {
141  ++nCutsPerRegion[regionColour[celli]];
142  }
143  }
144 
145  // Sum totals from all processors
146  Pstream::listCombineGather(nCutsPerRegion, plusEqOp<label>());
147 
148 
149  // Define which regions to keep
150  boolList keepRegion(rs.nRegions(), false);
151 
152  if (Pstream::master())
153  {
154  const label largest = findMax(nCutsPerRegion);
155 
156  if (largest == -1)
157  {
158  // Should not happen
159  keepRegion = true;
160  }
161  else
162  {
163  keepRegion[largest] = true;
164  }
165 
166  if (debug)
167  {
168  Info<< "Had " << sum(nCutsPerRegion) << " cuts, in "
169  << nCutsPerRegion.size() << " regions, largest is "
170  << largest << ": " << flatOutput(nCutsPerRegion) << nl;
171  }
172  }
173 
174  Pstream::broadcast(keepRegion);
175 
176  forAll(regionColour, celli)
177  {
178  if (!keepRegion.test(regionColour[celli]))
179  {
180  ignoreCells.set(celli);
181  }
182  }
183 }
184 
185 
187 (
188  bitSet& ignoreCells
189 ) const
190 {
191  if (nearestPoints_.empty())
192  {
194  << "Ignoring nearestPoints - no points provided" << nl
195  << endl;
196  return;
197  }
198 
199  // For region split
200  bitSet blockedFaces(filterPrepareRegionSplit(ignoreCells));
201 
202  // Split region
203  regionSplit rs(mesh_, blockedFaces);
204  blockedFaces.clearStorage();
205 
206  const labelList& regionColour = rs;
207 
208  const pointField& cc = mesh_.cellCentres();
209  const pointField& nearPts = nearestPoints_;
210 
211  // The magSqr distance and region
212  typedef Tuple2<scalar, label> nearInfo;
213  List<nearInfo> nearest(nearPts.size(), nearInfo(GREAT, -1));
214 
215  // Also collect cuts per region, may be useful for rejecting
216  // small regions. Code as per filterKeepLargestRegion
217  labelList nCutsPerRegion(rs.nRegions(), Zero);
218 
219  forAll(cc, celli)
220  {
221  if (ignoreCells.test(celli))
222  {
223  continue;
224  }
225 
226  const point& pt = cc[celli];
227  const label regioni = regionColour[celli];
228 
229  ++nCutsPerRegion[regioni];
230 
231  label pointi = 0;
232  for (nearInfo& near : nearest)
233  {
234  const scalar distSqr = magSqr(nearPts[pointi] - pt);
235  ++pointi;
236 
237  if (distSqr < near.first())
238  {
239  near.first() = distSqr;
240  near.second() = regioni;
241  }
242  }
243  }
244 
245  // Sum totals from all processors
246  Pstream::listCombineGather(nCutsPerRegion, plusEqOp<label>());
247 
248  // Get nearest
249  Pstream::listCombineGather(nearest, minFirstEqOp<scalar>());
250 
251 
252  // Define which regions to keep
253 
254  boolList keepRegion(rs.nRegions(), false);
255 
256  if (Pstream::master())
257  {
258  const label largest = findMax(nCutsPerRegion);
259 
260  for (const nearInfo& near : nearest)
261  {
262  const scalar distSqr = near.first();
263  const label regioni = near.second();
264 
265  if (regioni != -1 && distSqr < maxDistanceSqr_)
266  {
267  keepRegion[regioni] = true;
268  }
269  }
270 
271  if (debug)
272  {
273  Info<< "Had " << sum(nCutsPerRegion) << " cuts, in "
274  << nCutsPerRegion.size() << " regions, largest is "
275  << largest << ": " << flatOutput(nCutsPerRegion) << nl;
276 
277  Info<< "nearestPoints (max distance = "
278  << sqrt(maxDistanceSqr_) << ")" << nl;
279 
280  forAll(nearPts, pointi)
281  {
282  const scalar dist = sqrt(nearest[pointi].first());
283  const label regioni = nearest[pointi].second();
284 
285  Info<< " " << nearPts[pointi] << " region "
286  << regioni << " distance "
287  << dist;
288 
289  if (!keepRegion.test(regioni))
290  {
291  Info<< " too far";
292  }
293  Info<< nl;
294  }
295  }
296  }
297 
298  Pstream::broadcast(keepRegion);
299 
300  forAll(regionColour, celli)
301  {
302  if (!keepRegion.test(regionColour[celli]))
303  {
304  ignoreCells.set(celli);
305  }
306  }
307 }
308 
309 
311 (
312  bitSet& ignoreCells
313 ) const
314 {
315  const searchableSurface& geom = geometryPtr_();
316 
317  // For face distances
318  const pointField& fc = surface_.faceCentres();
319 
320  // For region split
321  bitSet blockedFaces(filterPrepareRegionSplit(ignoreCells));
322 
323  // Split region
324  regionSplit rs(mesh_, blockedFaces);
325  blockedFaces.clearStorage();
326 
327  const labelList& regionColour = rs;
328 
329  // For each face
330  scalarField faceDistance(fc.size(), GREAT);
331 
332  {
333  List<pointIndexHit> nearest;
334  geom.findNearest
335  (
336  fc,
337  // Use initialized field (GREAT) to limit search too
338  faceDistance,
339  nearest
340  );
341  calcAbsoluteDistance(faceDistance, fc, nearest);
342  }
343 
344  // Identical number of regions on all processors
345  scalarField areaRegion(rs.nRegions(), Zero);
346  scalarField distRegion(rs.nRegions(), Zero);
347 
348  forAll(meshCells_, facei)
349  {
350  const label celli = meshCells_[facei];
351  const label regioni = regionColour[celli];
352 
353  const scalar faceArea = surface_[facei].mag(surface_.points());
354  distRegion[regioni] += (faceDistance[facei] * faceArea);
355  areaRegion[regioni] += (faceArea);
356  }
357 
358  Pstream::listCombineGather(distRegion, plusEqOp<scalar>());
359  Pstream::listCombineGather(areaRegion, plusEqOp<scalar>());
360 
361  if (Pstream::master())
362  {
363  forAll(distRegion, regioni)
364  {
365  distRegion[regioni] /= (areaRegion[regioni] + VSMALL);
366  }
367  }
368 
369  Pstream::broadcast(distRegion);
370 
371 
372  // Define the per-face acceptance based on the region average distance
373 
374  bitSet acceptFaces(fc.size());
375  bool prune(false);
376 
377  forAll(meshCells_, facei)
378  {
379  const label celli = meshCells_[facei];
380  const label regioni = regionColour[celli];
381 
382  // NB: do not filter by individual faces as well since this
383  // has been reported to cause minor holes for surfaces with
384  // high curvature! (2021-06-10)
385 
386  if (absProximity_ < distRegion[regioni])
387  {
388  prune = true;
389  }
390  else
391  {
392  acceptFaces.set(facei);
393  }
394  }
395 
396  // Heavier debugging
397  if (debug & 4)
398  {
399  const fileName outputName(surfaceName() + "-region-proximity-filter");
400 
401  Info<< "Writing debug surface: " << outputName << nl;
402 
403  surfaceWriters::vtkWriter writer
404  (
405  surface_.points(),
406  surface_, // faces
407  outputName
408  );
409 
410  writer.write("absolute-distance", faceDistance);
411 
412  // Region segmentation
413  labelField faceRegion
414  (
415  ListOps::create<label>
416  (
417  meshCells_,
418  [&](const label celli){ return regionColour[celli]; }
419  )
420  );
421  writer.write("face-region", faceRegion);
422 
423  // Region-wise filter state
424  labelField faceFilterState
425  (
426  ListOps::createWithValue<label>(surface_.size(), acceptFaces, 1, 0)
427  );
428  writer.write("filter-state", faceFilterState);
429  }
430 
431 
432  // If filtering with region proximity results in zero faces,
433  // revert to face-only proximity filter
434  if (returnReduceAnd(acceptFaces.none()))
435  {
436  acceptFaces.reset();
437  prune = false;
438 
439  // Consider the absolute proximity of the face centres
440  forAll(faceDistance, facei)
441  {
442  if (absProximity_ < faceDistance[facei])
443  {
444  prune = true;
445  }
446  else
447  {
448  acceptFaces.set(facei);
449  }
450  }
451  }
452 
453  if (prune)
454  {
455  labelList pointMap, faceMap;
456  meshedSurface filtered
457  (
458  surface_.subsetMesh(acceptFaces, pointMap, faceMap)
459  );
460  surface_.transfer(filtered);
461 
462  meshCells_ = UIndirectList<label>(meshCells_, faceMap)();
463  }
464 }
465 
466 
468 {
469  const searchableSurface& geom = geometryPtr_();
470 
471  // For face distances
472  const pointField& fc = surface_.faceCentres();
473 
474  // For each face
475  scalarField faceDistance(fc.size(), GREAT);
476  scalarField faceNormalDistance; // Debugging only
477  {
478  List<pointIndexHit> nearest;
479  geom.findNearest
480  (
481  fc,
482  // Use initialized field (GREAT) to limit search too
483  faceDistance,
484  nearest
485  );
486  calcAbsoluteDistance(faceDistance, fc, nearest);
487 
488  // Heavier debugging
489  if (debug & 4)
490  {
491  vectorField norms;
492  geom.getNormal(nearest, norms);
493 
494  faceNormalDistance.resize(fc.size());
495 
496  forAll(nearest, i)
497  {
498  const vector diff(fc[i] - nearest[i].point());
499 
500  faceNormalDistance[i] = Foam::mag((diff & norms[i]));
501  }
502  }
503  }
504 
505  // Note
506  // Proximity checks using the face points (nearest hit) to establish
507  // a length scale are too fragile. Can easily have stretched faces
508  // where the centre is less than say 0.3-0.5 of the centre-point distance
509  // but they are still outside.
510 
511  // Using the absolute proximity of the face centres is more robust.
512 
513  bitSet acceptFaces(fc.size());
514  bool prune(false);
515 
516  // Consider the absolute proximity of the face centres
517  forAll(faceDistance, facei)
518  {
519  if (absProximity_ < faceDistance[facei])
520  {
521  prune = true;
522  if (debug & 2)
523  {
524  Pout<< "trim reject: "
525  << faceDistance[facei] << nl;
526  }
527  }
528  else
529  {
530  acceptFaces.set(facei);
531  }
532  }
533 
534 
535  // Heavier debugging
536  if (debug & 4)
537  {
538  const fileName outputName(surfaceName() + "-face-proximity-filter");
539 
540  Info<< "Writing debug surface: " << outputName << nl;
541 
542  surfaceWriters::vtkWriter writer
543  (
544  surface_.points(),
545  surface_, // faces
546  outputName
547  );
548 
549  writer.write("absolute-distance", faceDistance);
550  writer.write("normal-distance", faceNormalDistance);
551 
552  // Region-wise filter state
553  labelField faceFilterState
554  (
555  ListOps::createWithValue<label>(surface_.size(), acceptFaces, 1, 0)
556  );
557  writer.write("filter-state", faceFilterState);
558  }
559 
560 
561  if (prune)
562  {
563  labelList pointMap, faceMap;
564  meshedSurface filtered
565  (
566  surface_.subsetMesh(acceptFaces, pointMap, faceMap)
567  );
568  surface_.transfer(filtered);
569 
570  meshCells_ = UIndirectList<label>(meshCells_, faceMap)();
571  }
572 }
573 
574 
575 // ************************************************************************* //
This class separates the mesh into distinct unconnected regions, each of which is then given a label ...
Definition: regionSplit.H:136
label findMax(const ListType &input, label start=0)
Linear search for the index of the max element, similar to std::max_element but for lists and returns...
scalar diff(const triad &A, const triad &B)
Return a quantity of the difference between two triads.
Definition: triad.C:373
cutType getCellCutType(const label celli) const
Cell cut for an individual cell, with special handling for TETCUT and SPHERE cuts.
Field< label > labelField
Specialisation of Field<T> for label.
Definition: labelField.H:48
bitSet filterPrepareRegionSplit(const bitSet &ignoreCells) const
Prepare blockedFaces for region split.
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &f1)
void set(const bitSet &bitset)
Set specified bits from another bitset.
Definition: bitSetI.H:504
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
vtk::lineWriter writer(edgeCentres, edgeList::null(), fileName(aMesh.time().globalPath()/"finiteArea-edgesCentres"))
void filterKeepNearestRegions(bitSet &ignoreCells) const
Keep region(s) closest to the nearest points.
void resize(const label len)
Adjust allocated size of list.
Definition: ListI.H:160
std::enable_if< std::is_same< bool, TypeT >::value, bool >::type set(const label i, bool val=true)
A bitSet::set() method for a list of bool.
Definition: List.H:489
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
bool refineBlockedCells(bitSet &ignoreCells, const isoSurfaceBase &isoContext) const
Re-filter the blocked cells based on the anticipated cuts.
dimensionedScalar sqrt(const dimensionedScalar &ds)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
static void syncFaceList(const polyMesh &mesh, UList< T > &faceValues, const CombineOp &cop)
Synchronize values on all mesh faces.
Definition: syncTools.H:432
Any cut type (bitmask)
void write(const word &fieldName, const UList< Type > &field)
Write primitive field of CellData (Poly or Line) or PointData values.
Pair< int > faceMap(const label facePi, const face &faceP, const label faceNi, const face &faceN)
static void broadcast(Type &value, const label comm=UPstream::worldComm)
Broadcast content (contiguous or non-contiguous) to all communicator ranks. Does nothing in non-paral...
Various functions to operate on Lists.
Low-level components common to various iso-surface algorithms.
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
bool returnReduceAnd(const bool value, const label comm=UPstream::worldComm)
Perform logical (and) MPI Allreduce on a copy. Uses UPstream::reduceAnd.
word outputName("finiteArea-edges.obj")
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:38
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
MeshedSurface< face > meshedSurface
Vector< scalar > vector
Definition: vector.H:57
bool test(const label pos) const
Test for True value at specified position, never auto-vivify entries.
Definition: bitSet.H:326
int debug
Static debugging option.
void filterKeepLargestRegion(bitSet &ignoreCells) const
Keep region with the most cuts (after region split)
vector point
Point is a vector.
Definition: point.H:37
A bitSet stores bits (elements with only two states) in packed internal format and supports a variety...
Definition: bitSet.H:59
#define WarningInFunction
Report a warning using Foam::Warning.
label nCells() const noexcept
Number of mesh cells.
void filterFaceProximity()
Adjust extracted iso-surface to remove far faces.
A List with indirect addressing. Like IndirectList but does not store addressing. ...
Definition: faMatrix.H:56
static bool master(const label communicator=worldComm)
True if process corresponds to the master rank in the communicator.
Definition: UPstream.H:1082
const std::string patch
OpenFOAM patch number as a std::string.
void transfer(pointField &pointLst, List< Face > &faceLst)
Transfer the components.
messageStream Info
Information stream (stdout output on master, null elsewhere)
Field< vector > vectorField
Specialisation of Field<T> for vector.
static void listCombineGather(const List< commsStruct > &comms, List< T > &values, const CombineOp &cop, const int tag, const label comm)
List< label > labelList
A List of labels.
Definition: List.H:62
List< bool > boolList
A List of bools.
Definition: List.H:60
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
dimensioned< typename typeOfMag< Type >::type > magSqr(const dimensioned< Type > &dt)
FlatOutput::OutputAdaptor< Container, Delimiters > flatOutput(const Container &obj, Delimiters delim)
Global flatOutput() function with specified output delimiters.
Definition: FlatOutput.H:225
void filterRegionProximity(bitSet &ignoreCells) const
Remove region(s) that have far faces.
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127