checkTopology.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) 2011-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 "meshCheck.H"
27 #include "polyMeshCheck.H"
28 #include "Time.H"
29 #include "regionSplit.H"
30 #include "cellSet.H"
31 #include "faceSet.H"
32 #include "pointSet.H"
33 #include "IOmanip.H"
34 #include "emptyPolyPatch.H"
35 #include "processorPolyPatch.H"
36 #include "surfaceWriter.H"
37 #include "mergeAndWrite.H"
38 
39 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
40 
42 (
43  const polyMesh& mesh,
44  const bool allTopology,
45  const autoPtr<surfaceWriter>& surfWriter,
47 )
48 {
49  label noFailedChecks = 0;
50 
51  Info<< "Checking topology..." << endl;
52 
53  // Check if the boundary definition is unique
54  mesh.boundaryMesh().checkDefinition(true);
55 
56  // Check that empty patches cover all sides of the mesh
57  {
58  label nEmpty = 0;
59  forAll(mesh.boundaryMesh(), patchi)
60  {
61  if (isA<emptyPolyPatch>(mesh.boundaryMesh()[patchi]))
62  {
63  nEmpty += mesh.boundaryMesh()[patchi].size();
64  }
65  }
66  reduce(nEmpty, sumOp<label>());
67  label nTotCells = returnReduce(mesh.cells().size(), sumOp<label>());
68 
69  // These are actually warnings, not errors.
70  if (nTotCells && (nEmpty % nTotCells))
71  {
72  Info<< " ***Total number of faces on empty patches"
73  << " is not divisible by the number of cells in the mesh."
74  << " Hence this mesh is not 1D or 2D."
75  << endl;
76  }
77  }
78 
79  // Check if the boundary processor patches are correct
80  mesh.boundaryMesh().checkParallelSync(true);
81 
82  // Check names of zones are equal
83  mesh.cellZones().checkDefinition(true);
84  if (mesh.cellZones().checkParallelSync(true))
85  {
86  noFailedChecks++;
87  }
88  mesh.faceZones().checkDefinition(true);
89  if (mesh.faceZones().checkParallelSync(true))
90  {
91  noFailedChecks++;
92  }
93  mesh.pointZones().checkDefinition(true);
94  if (mesh.pointZones().checkParallelSync(true))
95  {
96  noFailedChecks++;
97  }
98 
99 
100  {
101  cellSet cells(mesh, "illegalCells", mesh.nCells()/100);
102 
103  forAll(mesh.cells(), celli)
104  {
105  const cell& cFaces = mesh.cells()[celli];
106 
107  if (cFaces.size() <= 3)
108  {
109  cells.insert(celli);
110  }
111  forAll(cFaces, i)
112  {
113  if (cFaces[i] < 0 || cFaces[i] >= mesh.nFaces())
114  {
115  cells.insert(celli);
116  break;
117  }
118  }
119  }
120  label nCells = returnReduce(cells.size(), sumOp<label>());
121 
122  if (nCells > 0)
123  {
124  Info<< " Illegal cells (less than 4 faces or out of range faces)"
125  << " found, number of cells: " << nCells << endl;
126  noFailedChecks++;
127 
128  if (setWriter.valid())
129  {
130  Info<< " <<Writing " << nCells
131  << " illegal cells to set " << cells.name() << endl;
132  cells.instance() = mesh.pointsInstance();
133  cells.write();
134  if (surfWriter.valid())
135  {
136  meshCheck::mergeAndWrite(surfWriter(), cells);
137  }
138  }
139  }
140  else
141  {
142  Info<< " Cell to face addressing OK." << endl;
143  }
144  }
145 
146 
147  {
148  pointSet points(mesh, "unusedPoints", mesh.nPoints()/100);
149  if (meshCheck::checkPoints(mesh, true, &points))
150  {
151  noFailedChecks++;
152 
153  if (setWriter.valid())
154  {
156 
157  Info<< " <<Writing " << nPoints
158  << " unused points to set " << points.name() << endl;
159  points.instance() = mesh.pointsInstance();
160  points.write();
161  if (setWriter.valid())
162  {
164  }
165  }
166  }
167  }
168 
169  {
170  faceSet faces(mesh, "upperTriangularFace", mesh.nFaces()/100);
171  if (meshCheck::checkUpperTriangular(mesh, true, &faces))
172  {
173  noFailedChecks++;
174  }
175 
176  label nFaces = returnReduce(faces.size(), sumOp<label>());
177 
178  if (nFaces > 0)
179  {
180  if (setWriter.valid())
181  {
182  Info<< " <<Writing " << nFaces
183  << " unordered faces to set " << faces.name() << endl;
184  faces.instance() = mesh.pointsInstance();
185  faces.write();
186  if (surfWriter.valid())
187  {
188  meshCheck::mergeAndWrite(surfWriter(), faces);
189  }
190  }
191  }
192  }
193 
194  {
195  faceSet faces(mesh, "outOfRangeFaces", mesh.nFaces()/100);
196  if (meshCheck::checkFaceVertices(mesh, true, &faces))
197  {
198  noFailedChecks++;
199 
200  if (setWriter.valid())
201  {
202  label nFaces = returnReduce(faces.size(), sumOp<label>());
203 
204  Info<< " <<Writing " << nFaces
205  << " faces with out-of-range or duplicate vertices to set "
206  << faces.name() << endl;
207  faces.instance() = mesh.pointsInstance();
208  faces.write();
209  if (surfWriter.valid())
210  {
211  meshCheck::mergeAndWrite(surfWriter(), faces);
212  }
213  }
214  }
215  }
216 
217  if (allTopology)
218  {
219  cellSet cells(mesh, "zipUpCells", mesh.nCells()/100);
220  if (meshCheck::checkCellsZipUp(mesh, true, &cells))
221  {
222  noFailedChecks++;
223 
224  if (setWriter.valid())
225  {
226  label nCells = returnReduce(cells.size(), sumOp<label>());
227 
228  Info<< " <<Writing " << nCells
229  << " cells with over used edges to set " << cells.name()
230  << endl;
231  cells.instance() = mesh.pointsInstance();
232  cells.write();
233  if (surfWriter.valid())
234  {
235  meshCheck::mergeAndWrite(surfWriter(), cells);
236  }
237  }
238  }
239  }
240 
241  if (allTopology)
242  {
243  faceSet faces(mesh, "edgeFaces", mesh.nFaces()/100);
244  if (meshCheck::checkFaceFaces(mesh, true, &faces))
245  {
246  noFailedChecks++;
247  }
248 
249  label nFaces = returnReduce(faces.size(), sumOp<label>());
250  if (nFaces > 0)
251  {
252  if (setWriter.valid())
253  {
254  Info<< " <<Writing " << nFaces
255  << " faces with non-standard edge connectivity to set "
256  << faces.name() << endl;
257  faces.instance() = mesh.pointsInstance();
258  faces.write();
259  if (surfWriter.valid())
260  {
261  meshCheck::mergeAndWrite(surfWriter(), faces);
262  }
263  }
264  }
265  }
266 
267  if (allTopology)
268  {
269  labelList nInternalFaces(mesh.nCells(), 0);
270 
271  for (label facei = 0; facei < mesh.nInternalFaces(); facei++)
272  {
273  nInternalFaces[mesh.faceOwner()[facei]]++;
274  nInternalFaces[mesh.faceNeighbour()[facei]]++;
275  }
276  const polyBoundaryMesh& patches = mesh.boundaryMesh();
278  {
279  if (patches[patchi].coupled())
280  {
281  const labelUList& owners = patches[patchi].faceCells();
282 
283  forAll(owners, i)
284  {
285  nInternalFaces[owners[i]]++;
286  }
287  }
288  }
289 
290  cellSet oneCells(mesh, "oneInternalFaceCells", mesh.nCells()/100);
291  cellSet twoCells(mesh, "twoInternalFacesCells", mesh.nCells()/100);
292 
293  forAll(nInternalFaces, celli)
294  {
295  if (nInternalFaces[celli] <= 1)
296  {
297  oneCells.insert(celli);
298  }
299  else if (nInternalFaces[celli] == 2)
300  {
301  twoCells.insert(celli);
302  }
303  }
304 
305  label nOneCells = returnReduce(oneCells.size(), sumOp<label>());
306 
307  if (nOneCells > 0)
308  {
309  if (setWriter.valid())
310  {
311  Info<< " <<Writing " << nOneCells
312  << " cells with zero or one non-boundary face to set "
313  << oneCells.name()
314  << endl;
315  oneCells.instance() = mesh.pointsInstance();
316  oneCells.write();
317  if (surfWriter.valid())
318  {
319  meshCheck::mergeAndWrite(surfWriter(), oneCells);
320  }
321  }
322  }
323 
324  label nTwoCells = returnReduce(twoCells.size(), sumOp<label>());
325 
326  if (nTwoCells > 0)
327  {
328  if (setWriter.valid())
329  {
330  Info<< " <<Writing " << nTwoCells
331  << " cells with two non-boundary faces to set "
332  << twoCells.name()
333  << endl;
334  twoCells.instance() = mesh.pointsInstance();
335  twoCells.write();
336  if (surfWriter.valid())
337  {
338  meshCheck::mergeAndWrite(surfWriter(), twoCells);
339  }
340  }
341  }
342  }
343 
344  {
345  regionSplit rs(mesh);
346 
347  if (rs.nRegions() <= 1)
348  {
349  Info<< " Number of regions: " << rs.nRegions() << " (OK)."
350  << endl;
351 
352  }
353  else
354  {
355  Info<< " *Number of regions: " << rs.nRegions() << endl;
356 
357  Info<< " The mesh has multiple regions which are not connected "
358  "by any face." << endl;
359 
360  if (allTopology && setWriter.valid())
361  {
362  Info<< " <<Writing region information to "
363  << mesh.time().name()/"cellToRegion"
364  << endl;
365 
366  labelIOList ctr
367  (
368  IOobject
369  (
370  "cellToRegion",
371  mesh.time().name(),
372  mesh,
373  IOobject::NO_READ,
374  IOobject::NO_WRITE
375  ),
376  rs
377  );
378  ctr.write();
379  }
380 
381 
382  // Write region sets for allTopology
383  if (allTopology && setWriter.valid())
384  {
385  // Points in multiple regions
387  (
388  mesh,
389  "multiRegionPoints",
390  mesh.nPoints()/1000
391  );
392 
393  // Is region disconnected
394  boolList regionDisconnected(rs.nRegions(), true);
395  // -1 : not assigned
396  // -2 : multiple regions
397  // >= 0 : single region
398  labelList pointToRegion(mesh.nPoints(), -1);
399 
400  for
401  (
402  label faceI = mesh.nInternalFaces();
403  faceI < mesh.nFaces();
404  faceI++
405  )
406  {
407  label regionI = rs[mesh.faceOwner()[faceI]];
408  const face& f = mesh.faces()[faceI];
409  forAll(f, fp)
410  {
411  label& pRegion = pointToRegion[f[fp]];
412  if (pRegion == -1)
413  {
414  pRegion = regionI;
415  }
416  else if (pRegion == -2)
417  {
418  // Already marked
419  regionDisconnected[regionI] = false;
420  }
421  else if (pRegion != regionI)
422  {
423  // Multiple regions
424  regionDisconnected[regionI] = false;
425  regionDisconnected[pRegion] = false;
426  pRegion = -2;
427  points.insert(f[fp]);
428  }
429  }
430  }
431 
432  Pstream::listCombineGather
433  (
434  regionDisconnected,
435  andEqOp<bool>()
436  );
437  Pstream::listCombineScatter(regionDisconnected);
438 
439  // write cellSet for each region
440  PtrList<cellSet> cellRegions(rs.nRegions());
441  for (label i = 0; i < rs.nRegions(); i++)
442  {
443  cellRegions.set
444  (
445  i,
446  new cellSet
447  (
448  mesh,
449  "region" + Foam::name(i),
450  mesh.nCells()/100
451  )
452  );
453  }
454 
455  forAll(rs, i)
456  {
457  cellRegions[rs[i]].insert(i);
458  }
459 
460  for (label i = 0; i < rs.nRegions(); i++)
461  {
462  Info<< " <<Writing region " << i;
463 
464  if (regionDisconnected[i])
465  {
466  Info<< " (fully disconnected)";
467  }
468  else
469  {
470  Info<< " (point connected)";
471  }
472 
473  Info<< " with "
474  << returnReduce(cellRegions[i].size(), sumOp<scalar>())
475  << " cells to cellSet " << cellRegions[i].name()
476  << endl;
477 
478  cellRegions[i].instance() = mesh.pointsInstance();
479  cellRegions[i].write();
480  }
481 
483  if (nPoints)
484  {
485  Info<< " <<Writing " << nPoints
486  << " points that are in multiple regions to set "
487  << points.name() << endl;
488 
489  points.instance() = mesh.pointsInstance();
490  points.write();
491  if (setWriter.valid())
492  {
494  }
495  }
496  }
497  }
498  }
499 
500 
501  {
502  if (!Pstream::parRun())
503  {
504  Info<< "\nChecking patch topology for multiply connected"
505  << " surfaces..." << endl;
506  }
507  else
508  {
509  Info<< "\nChecking basic patch addressing..." << endl;
510  }
511 
512 
513  const polyBoundaryMesh& patches = mesh.boundaryMesh();
514 
515  // Non-manifold points
517  (
518  mesh,
519  "nonManifoldPoints",
520  mesh.nPoints()/1000
521  );
522 
523  Pout.setf(ios_base::left);
524 
525  Info<< " "
526  << setw(20) << "Patch"
527  << setw(9) << "Faces"
528  << setw(9) << "Points";
529  if (!Pstream::parRun())
530  {
531  Info<< setw(34) << "Surface topology";
532  }
533  Info<< endl;
534 
536  {
537  const polyPatch& pp = patches[patchi];
538 
539  if (!isA<processorPolyPatch>(pp))
540  {
541  Info<< " "
542  << setw(20) << pp.name()
543  << setw(9) << returnReduce(pp.size(), sumOp<label>())
544  << setw(9) << returnReduce(pp.nPoints(), sumOp<label>());
545 
546  if (!Pstream::parRun())
547  {
549 
550  if (pp.empty())
551  {
552  Info<< setw(34) << "ok (empty)";
553  }
554  else if (pTyp == primitivePatch::MANIFOLD)
555  {
556  if (pp.checkPointManifold(true, &points))
557  {
558  Info<< setw(34)
559  << "multiply connected (shared point)";
560  }
561  else
562  {
563  Info<< setw(34) << "ok (closed singly connected)";
564  }
565 
566  // Add points on non-manifold edges to make set complete
567  pp.checkTopology(false, &points);
568  }
569  else
570  {
571  pp.checkTopology(false, &points);
572 
573  if (pTyp == primitivePatch::OPEN)
574  {
575  Info<< setw(34)
576  << "ok (non-closed singly connected)";
577  }
578  else
579  {
580  Info<< setw(34)
581  << "multiply connected (shared edge)";
582  }
583  }
584  }
585  Info<< endl;
586  }
587  }
588 
589  if (points.size() && setWriter.valid())
590  {
591  Info<< " <<Writing " << returnReduce(points.size(), sumOp<label>())
592  << " conflicting points to set "
593  << points.name() << endl;
594 
595  points.instance() = mesh.pointsInstance();
596  points.write();
597  }
598 
599  // Info.setf(ios_base::right);
600  }
601 
602  // Force creation of all addressing if requested.
603  // Errors will be reported as required
604  if (allTopology)
605  {
606  mesh.cells();
607  mesh.faces();
608  mesh.edges();
609  mesh.points();
610  mesh.faceOwner();
611  mesh.faceNeighbour();
612  mesh.cellCells();
613  mesh.edgeCells();
614  mesh.pointCells();
615  mesh.edgeFaces();
616  mesh.pointFaces();
617  mesh.cellEdges();
618  mesh.faceEdges();
619  mesh.pointEdges();
620  }
621 
622  return noFailedChecks;
623 }
624 
625 
626 // ************************************************************************* //
Istream and Ostream manipulators taking arguments.
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:434
bool insert(const Key &key)
Insert a new entry.
Definition: HashSet.H:109
label size() const
Return number of elements in table.
Definition: HashTableI.H:65
IOobject defines the attributes of an object for which implicit objectRegistry management is supporte...
Definition: IOobject.H:99
fileName & instance() const
Return the instance directory, constant, system, <time> etc.
Definition: IOobject.C:355
const word & name() const
Return name.
Definition: IOobject.H:310
ios_base::fmtflags setf(const ios_base::fmtflags f)
Set flags of stream.
Definition: IOstream.H:493
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
label nPoints() const
Return number of points supporting patch faces.
bool checkPointManifold(const bool report=false, labelHashSet *setPtr=nullptr) const
Checks primitivePatch for faces sharing point but not edge.
surfaceTopo surfaceType() const
Calculate surface type formed by patch.
bool checkTopology(const bool report=false, labelHashSet *setPtr=nullptr) const
Check surface formed by patch for manifoldness (see above).
surfaceTopo
Enumeration defining the surface type. Used in check routines.
A templated 1D list of pointers to objects of type <T>, where the size of the array is known and used...
Definition: PtrList.H:75
bool set(const label) const
Is element set.
Definition: PtrListI.H:62
label size() const
Return the number of elements in the UList.
Definition: UListI.H:311
bool empty() const
Return true if the UList is empty (ie, size() is zero)
Definition: UListI.H:325
label size() const
Return the number of elements in the UPtrList.
Definition: UPtrListI.H:29
bool checkDefinition(const bool report=false) const
Check zone definition. Return true if in error.
Definition: ZoneList.C:245
bool checkParallelSync(const bool report=false) const
Check whether all procs have all zones and in same order. Return.
Definition: ZoneList.C:263
An auto-pointer similar to the STL auto_ptr but with automatic casting to a reference to the type and...
Definition: autoPtr.H:51
bool valid() const
Return true if the autoPtr valid (ie, the pointer is set)
Definition: autoPtrI.H:83
A collection of cell labels.
Definition: cellSet.H:51
A cell is defined as a list of faces with extra functionality.
Definition: cell.H:60
const word & name() const
Return const reference to name.
A list of face labels.
Definition: faceSet.H:51
A face is a list of labels corresponding to mesh vertices.
Definition: face.H:76
const Time & time() const
Return time.
const word & name() const
Return name.
A set of point labels.
Definition: pointSet.H:51
Foam::polyBoundaryMesh.
bool checkDefinition(const bool report=false) const
Check boundary definition. Return true if in error.
bool checkParallelSync(const bool report=false) const
Check whether all procs have all patches and in same order. Return.
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:80
const pointZoneList & pointZones() const
Return point zones.
Definition: polyMesh.H:440
const cellZoneList & cellZones() const
Return cell zones.
Definition: polyMesh.H:452
virtual const faceList & faces() const
Return raw faces.
Definition: polyMesh.C:1326
virtual const labelList & faceOwner() const
Return face owner.
Definition: polyMesh.C:1339
const fileName & pointsInstance() const
Return the current instance directory for points.
Definition: polyMesh.C:965
const faceZoneList & faceZones() const
Return face zones.
Definition: polyMesh.H:446
const polyBoundaryMesh & boundaryMesh() const
Return boundary mesh.
Definition: polyMesh.H:404
virtual const labelList & faceNeighbour() const
Return face neighbour.
Definition: polyMesh.C:1345
virtual const pointField & points() const
Return raw points.
Definition: polyMesh.C:1313
A patch is a list of labels that address the faces in the global face list.
Definition: polyPatch.H:70
label nCells() const
label nPoints() const
const labelListList & pointEdges() const
const edgeList & edges() const
Return mesh edges. Uses calcEdges.
const labelListList & cellEdges() const
const labelListList & pointCells() const
const labelListList & cellCells() const
label nInternalFaces() const
label nFaces() const
const labelListList & pointFaces() const
const labelListList & edgeFaces() const
const labelListList & faceEdges() const
const labelListList & edgeCells() const
const cellList & cells() const
virtual bool write(const bool write=true) const
Write using setting from DB.
This class separates the mesh into distinct unconnected regions, each of which is then given a label ...
Definition: regionSplit.H:118
label nRegions() const
Return total number of regions.
Definition: regionSplit.H:208
Base class for writing coordinate sets with data.
Definition: setWriter.H:64
label patchi
const pointField & points
label nPoints
const cellShapeList & cells
const fvPatchList & patches
Tools for checking the mesh.
Functions for checking mesh topology and geometry.
bool checkFaceFaces(const primitiveMesh &mesh, const bool report=false, labelHashSet *setPtr=nullptr)
Check face-face connectivity.
bool checkUpperTriangular(const primitiveMesh &mesh, const bool report=false, labelHashSet *setPtr=nullptr)
Check face ordering.
bool checkFaceVertices(const primitiveMesh &mesh, const bool report=false, labelHashSet *setPtr=nullptr)
Check uniqueness of face vertices.
void mergeAndWrite(const polyMesh &mesh, const surfaceWriter &setWriter, const word &name, const indirectPrimitivePatch setPatch, const fileName &outputDir)
Generate merged surface on master and write. Needs input patch.
label checkTopology(const polyMesh &mesh, const bool allTopology, const autoPtr< surfaceWriter > &surfWriter, const autoPtr< setWriter > &setWriter)
Check the topology.
Definition: checkTopology.C:42
bool checkPoints(const primitiveMesh &mesh, const bool report=false, labelHashSet *setPtr=nullptr)
Check for unused points.
bool checkCellsZipUp(const primitiveMesh &mesh, const bool report=false, labelHashSet *setPtr=nullptr)
Check cell zip-up.
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
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:257
word name(const bool)
Return a word representation of a bool.
Definition: boolIO.C:39
Omanip< int > setw(const int i)
Definition: IOmanip.H:199
messageStream Info
void reduce(const List< UPstream::commsStruct > &comms, T &Value, const BinaryOp &bop, const int tag, const label comm)
T returnReduce(const T &Value, const BinaryOp &bop, const int tag=Pstream::msgType(), const label comm=UPstream::worldComm)
prefixOSstream Pout(cout, "Pout")
Definition: IOstreams.H:53
labelList f(nPoints)