decompositionMethod.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) 2011-2016 OpenFOAM Foundation
9  Copyright (C) 2015-2023 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 "decompositionMethod.H"
30 #include "globalIndex.H"
31 #include "syncTools.H"
32 #include "faceSet.H"
33 #include "regionSplit.H"
34 #include "localPointRegion.H"
35 #include "minData.H"
36 #include "BitOps.H"
37 #include "FaceCellWave.H"
38 
39 // Compatibility (MAY-2014)
44 
45 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
46 
47 namespace Foam
48 {
49  defineTypeNameAndDebug(decompositionMethod, 0);
50  defineRunTimeSelectionTable(decompositionMethod, dictionary);
51 
52 } // End namespace Foam
53 
54 
55 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
56 
57 namespace Foam
58 {
59 
60 // Find named coefficents dictionary, or use default "coeffs"
61 static inline const dictionary* cfindCoeffsDict
62 (
63  const dictionary& dict,
64  const word& coeffsName,
65  const bool allowDefault
66 )
67 {
68  const dictionary* dictptr = dict.findDict(coeffsName);
69  if (!dictptr && allowDefault)
70  {
71  dictptr = dict.findDict("coeffs");
72  }
73  return dictptr;
74 }
75 
76 } // End namespace Foam
77 
78 
79 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
80 
82 (
83  const dictionary& decompDict,
84  const word& regionName
85 )
86 {
87  label nDomainsRegion = 0;
88  label nDomainsGlobal = UPstream::nProcs();
89 
90  // Allow numberOfSubdomains to be optional in parallel, which allows
91  // for missing files on directories that have not yet been created.
92 
93  decompDict.readEntry<label>
94  (
95  "numberOfSubdomains",
96  nDomainsGlobal,
97  keyType::REGEX, // keyType::LITERAL?
99  );
100 
101  if (!regionName.empty())
102  {
103  const dictionary& regionDict =
104  optionalRegionDict(decompDict, regionName);
105 
106  if (regionDict.readIfPresent("numberOfSubdomains", nDomainsRegion))
107  {
108  if (nDomainsRegion >= 1 && nDomainsRegion <= nDomainsGlobal)
109  {
110  return nDomainsRegion;
111  }
112 
114  << "Ignoring region [" << regionName
115  << "] numberOfSubdomains: " << nDomainsRegion
116  << ", using global: " << nDomainsGlobal << nl
117  << endl;
118  }
119  }
120 
121  return nDomainsGlobal;
122 }
123 
124 
126 (
127  const dictionary& decompDict,
128  const word& regionName
129 )
130 {
131  const dictionary* dictptr = nullptr;
132  if
133  (
134  !regionName.empty()
135  && (dictptr = decompDict.findDict("regions")) != nullptr
136  )
137  {
138  dictptr = dictptr->findDict(regionName);
139  }
140  return (dictptr ? *dictptr : dictionary::null);
141 }
142 
143 
144 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
145 
146 bool Foam::decompositionMethod::constraintCompat(const word& modelType) const
147 {
148  bool usable = decompDict_.found(modelType);
149  if (!usable)
150  {
151  return false;
152  }
153 
154  for (const auto& item : constraints_)
155  {
156  if (modelType == item.type())
157  {
158  usable = false;
159  break;
160  }
161  }
162 
163  if (usable)
164  {
165  Warning
166  << nl << " Using '" << modelType
167  << "' constraint specification." << nl;
168  }
169  else
170  {
171  Warning
172  << nl << " Ignoring '" << modelType
173  << "' constraint specification - was already specified." << nl;
174  }
175 
176  // The syntax changed MAY-2014
177  error::warnAboutAge("constraint keyword", 1406);
178 
179  return usable;
180 }
181 
182 
183 void Foam::decompositionMethod::readConstraints()
184 {
185  constraints_.clear();
186 
187  const dictionary* dictptr = decompDict_.findDict("constraints");
188 
189  if (dictptr)
190  {
191  for (const entry& dEntry : *dictptr)
192  {
193  if (!dEntry.isDict()) // safety
194  {
195  // Ignore or warn
196  continue;
197  }
198 
199  const dictionary& dict = dEntry.dict();
200 
201  if (dict.getOrDefault("enabled", true))
202  {
203  constraints_.append(decompositionConstraint::New(dict));
204  }
205  }
206  }
207 
208  // Backwards compatibility (MAY-2014)
209  if (constraintCompat("preserveBaffles"))
210  {
211  constraints_.append
212  (
213  new decompositionConstraints::preserveBaffles()
214  );
215  }
216 
217  if (constraintCompat("preservePatches"))
218  {
219  constraints_.append
220  (
221  new decompositionConstraints::preservePatches
222  (
223  decompDict_.get<wordRes>("preservePatches")
224  )
225  );
226  }
227 
228  if (constraintCompat("preserveFaceZones"))
229  {
230  constraints_.append
231  (
232  new decompositionConstraints::preserveFaceZones
233  (
234  decompDict_.get<wordRes>("preserveFaceZones")
235  )
236  );
237  }
238 
239  if (constraintCompat("singleProcessorFaceSets"))
240  {
241  constraints_.append
242  (
243  new decompositionConstraints::singleProcessorFaceSets
244  (
245  decompDict_.lookup("singleProcessorFaceSets")
246  )
247  );
248  }
249 }
250 
251 // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
252 
254 (
255  const dictionary& dict,
256  const word& coeffsName,
257  int select
258 )
259 {
260  const bool allowDefault = !(select & selectionType::EXACT);
261 
262  const dictionary* dictptr =
263  cfindCoeffsDict(dict, coeffsName, allowDefault);
264 
265  if (dictptr)
266  {
267  return *dictptr;
268  }
269 
270  // Not found
271  if (select & selectionType::MANDATORY)
272  {
274  << "'" << coeffsName << "' dictionary not found in dictionary "
275  << dict.name() << endl
276  << abort(FatalIOError);
277  }
278 
279  if (select & selectionType::NULL_DICT)
280  {
281  return dictionary::null;
282  }
283 
284  return dict; // Return original dictionary
285 }
286 
287 
289 (
290  const word& coeffsName,
291  int select
292 ) const
293 {
294  const bool allowDefault = !(select & selectionType::EXACT);
295 
296  const dictionary* dictptr = nullptr;
297 
298  if (!decompRegionDict_.empty())
299  {
300  // Region-specific dictionary
301  dictptr = cfindCoeffsDict(decompRegionDict_, coeffsName, allowDefault);
302  }
303  if (!dictptr)
304  {
305  // General
306  dictptr = cfindCoeffsDict(decompDict_, coeffsName, allowDefault);
307  }
308 
309  if (dictptr)
310  {
311  return *dictptr;
312  }
313 
314  // Not found
315  if (select & selectionType::MANDATORY)
316  {
318  << "'" << coeffsName << "' dictionary not found in dictionary "
319  << decompDict_.name() << endl
320  << abort(FatalIOError);
321  }
322 
323  if (select & selectionType::NULL_DICT)
324  {
325  return dictionary::null;
326  }
327 
328  return decompDict_; // Return general dictionary
329 }
330 
331 
332 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
333 
334 Foam::decompositionMethod::decompositionMethod
335 (
336  const dictionary& decompDict,
337  const word& regionName
338 )
339 :
340  decompDict_(decompDict),
341  decompRegionDict_
342  (
343  optionalRegionDict(decompDict_, regionName)
344  ),
345  nDomains_(nDomains(decompDict, regionName))
346 {
347  readConstraints();
348 }
349 
350 
351 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
352 
354 (
355  const dictionary& decompDict,
356  const word& regionName
357 )
358 {
359  word methodType(decompDict.get<word>("method"));
360 
361  const dictionary& regionDict = optionalRegionDict(decompDict, regionName);
362  regionDict.readIfPresent("method", methodType);
363 
364  auto* ctorPtr = dictionaryConstructorTable(methodType);
365 
366  if (!ctorPtr)
367  {
369  (
370  decompDict,
371  "decompositionMethod",
372  methodType,
373  *dictionaryConstructorTablePtr_
374  ) << exit(FatalIOError);
375  }
376 
377  // verbose
378  {
379  Info<< "Decomposition method " << methodType
380  << " [" << (nDomains(decompDict, regionName)) << ']';
381 
382  if (!regionName.empty())
383  {
384  Info<< " (region " << regionName << ')';
385  }
386  Info<< endl;
387  }
388 
389  return autoPtr<decompositionMethod>(ctorPtr(decompDict, regionName));
390 }
391 
392 
394 (
395  const polyMesh& mesh,
396  const pointField& points
397 ) const
398 {
399  scalarField weights(points.size(), scalar(1));
400 
401  return decompose(mesh, points, weights);
402 }
403 
404 
406 (
407  const polyMesh& mesh,
408  const labelList& fineToCoarse,
409  const pointField& coarsePoints,
410  const scalarField& coarseWeights
411 ) const
412 {
413  CompactListList<label> coarseCellCells;
414  calcCellCells
415  (
416  mesh,
417  fineToCoarse,
418  coarsePoints.size(),
419  true, // use global cell labels
420  coarseCellCells
421  );
422 
423  // Decompose based on agglomerated points
424  labelList coarseDistribution
425  (
426  decompose
427  (
428  coarseCellCells.unpack(),
429  coarsePoints,
430  coarseWeights
431  )
432  );
433 
434  // Rework back into decomposition for original mesh_
435  labelList fineDistribution(fineToCoarse.size());
436 
437  forAll(fineDistribution, i)
438  {
439  fineDistribution[i] = coarseDistribution[fineToCoarse[i]];
440  }
441 
442  return fineDistribution;
443 }
444 
445 
447 (
448  const polyMesh& mesh,
449  const labelList& fineToCoarse,
450  const pointField& coarsePoints
451 ) const
452 {
453  scalarField weights(coarsePoints.size(), scalar(1));
454 
455  return decompose
456  (
457  mesh,
458  fineToCoarse,
459  coarsePoints,
460  weights
461  );
462 }
463 
464 
466 (
467  const labelListList& globalCellCells,
468  const pointField& cc
469 ) const
470 {
471  scalarField weights(cc.size(), scalar(1));
472 
473  return decompose(globalCellCells, cc, weights);
474 }
475 
476 
478 (
479  const polyMesh& mesh,
480  const labelList& agglom,
481  const label nLocalCoarse,
482  const bool parallel,
483  CompactListList<label>& cellCells
484 )
485 {
486  const labelList& faceOwner = mesh.faceOwner();
487  const labelList& faceNeighbour = mesh.faceNeighbour();
489 
490 
491  // Create global cell numbers
492  // ~~~~~~~~~~~~~~~~~~~~~~~~~~
493 
494  const globalIndex globalAgglom(nLocalCoarse, UPstream::worldComm, parallel);
495 
496 
497  // Get agglomerate owner on other side of coupled faces
498  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
499 
500  labelList globalNeighbour(mesh.nBoundaryFaces());
501 
502  for (const polyPatch& pp : patches)
503  {
504  if (pp.coupled() && (parallel || !isA<processorPolyPatch>(pp)))
505  {
506  label facei = pp.start();
507  label bFacei = pp.start() - mesh.nInternalFaces();
508 
509  forAll(pp, i)
510  {
511  globalNeighbour[bFacei] = globalAgglom.toGlobal
512  (
513  agglom[faceOwner[facei]]
514  );
515 
516  ++facei;
517  ++bFacei;
518  }
519  }
520  }
521 
522  // Get the cell on the other side of coupled patches
523  syncTools::swapBoundaryFaceList(mesh, globalNeighbour);
524 
525 
526  // Count number of faces (internal + coupled)
527  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
528 
529  // Number of faces per coarse cell
530  labelList nFacesPerCell(nLocalCoarse, Zero);
531 
532  for (label facei = 0; facei < mesh.nInternalFaces(); ++facei)
533  {
534  const label own = agglom[faceOwner[facei]];
535  const label nei = agglom[faceNeighbour[facei]];
536 
537  nFacesPerCell[own]++;
538  nFacesPerCell[nei]++;
539  }
540 
541  for (const polyPatch& pp : patches)
542  {
543  if (pp.coupled() && (parallel || !isA<processorPolyPatch>(pp)))
544  {
545  label facei = pp.start();
546  label bFacei = pp.start()-mesh.nInternalFaces();
547 
548  forAll(pp, i)
549  {
550  const label own = agglom[faceOwner[facei]];
551  const label globalNei = globalNeighbour[bFacei];
552 
553  if
554  (
555  !globalAgglom.isLocal(globalNei)
556  || globalAgglom.toLocal(globalNei) != own
557  )
558  {
559  nFacesPerCell[own]++;
560  }
561 
562  ++facei;
563  ++bFacei;
564  }
565  }
566  }
567 
568 
569  // Fill in offset and data
570  // ~~~~~~~~~~~~~~~~~~~~~~~
571 
572  cellCells.setSize(nFacesPerCell);
573 
574  nFacesPerCell = 0;
575 
576  labelList& m = cellCells.m();
577  const labelList& offsets = cellCells.offsets();
578 
579  // For internal faces is just offsetted owner and neighbour
580  for (label facei = 0; facei < mesh.nInternalFaces(); ++facei)
581  {
582  const label own = agglom[faceOwner[facei]];
583  const label nei = agglom[faceNeighbour[facei]];
584 
585  m[offsets[own] + nFacesPerCell[own]++] = globalAgglom.toGlobal(nei);
586  m[offsets[nei] + nFacesPerCell[nei]++] = globalAgglom.toGlobal(own);
587  }
588 
589  // For boundary faces is offsetted coupled neighbour
590  for (const polyPatch& pp : patches)
591  {
592  if (pp.coupled() && (parallel || !isA<processorPolyPatch>(pp)))
593  {
594  label facei = pp.start();
595  label bFacei = pp.start()-mesh.nInternalFaces();
596 
597  forAll(pp, i)
598  {
599  const label own = agglom[faceOwner[facei]];
600  const label globalNei = globalNeighbour[bFacei];
601 
602  if
603  (
604  !globalAgglom.isLocal(globalNei)
605  || globalAgglom.toLocal(globalNei) != own
606  )
607  {
608  m[offsets[own] + nFacesPerCell[own]++] = globalNei;
609  }
610 
611  ++facei;
612  ++bFacei;
613  }
614  }
615  }
616 
617 
618  // Check for duplicates connections between cells
619  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
620  // Done as postprocessing step since we now have cellCells.
621 
622  if (cellCells.size() == 0)
623  {
624  return;
625  }
626 
627  label newIndex = 0;
628  labelHashSet nbrCells;
629 
630  label startIndex = cellCells.offsets()[0];
631 
632  forAll(cellCells, celli)
633  {
634  nbrCells.clear();
635  nbrCells.insert(globalAgglom.toGlobal(celli));
636 
637  const label endIndex = cellCells.offsets()[celli+1];
638 
639  for (label i = startIndex; i < endIndex; ++i)
640  {
641  if (nbrCells.insert(cellCells.m()[i]))
642  {
643  cellCells.m()[newIndex++] = cellCells.m()[i];
644  }
645  }
646  startIndex = endIndex;
647  cellCells.offsets()[celli+1] = newIndex;
648  }
649 
650  cellCells.m().setSize(newIndex);
651 
652  //forAll(cellCells, celli)
653  //{
654  // Pout<< "Original: Coarse cell " << celli << endl;
655  // forAll(mesh.cellCells()[celli], i)
656  // {
657  // Pout<< " nbr:" << mesh.cellCells()[celli][i] << endl;
658  // }
659  // Pout<< "Compacted: Coarse cell " << celli << endl;
660  // const labelUList cCells = cellCells[celli];
661  // forAll(cCells, i)
662  // {
663  // Pout<< " nbr:" << cCells[i] << endl;
664  // }
665  //}
666 }
667 
668 
670 (
671  const polyMesh& mesh,
672  const labelList& agglom,
673  const label nLocalCoarse,
674  const bool parallel,
675  CompactListList<label>& cellCells,
676  CompactListList<scalar>& cellCellWeights
677 )
678 {
679  const labelList& faceOwner = mesh.faceOwner();
680  const labelList& faceNeighbour = mesh.faceNeighbour();
682 
683 
684  // Create global cell numbers
685  // ~~~~~~~~~~~~~~~~~~~~~~~~~~
686 
687  const globalIndex globalAgglom(nLocalCoarse, UPstream::worldComm, parallel);
688 
689 
690  // Get agglomerate owner on other side of coupled faces
691  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
692 
693  labelList globalNeighbour(mesh.nBoundaryFaces());
694 
695  for (const polyPatch& pp : patches)
696  {
697  if (pp.coupled() && (parallel || !isA<processorPolyPatch>(pp)))
698  {
699  label facei = pp.start();
700  label bFacei = pp.start() - mesh.nInternalFaces();
701 
702  forAll(pp, i)
703  {
704  globalNeighbour[bFacei] = globalAgglom.toGlobal
705  (
706  agglom[faceOwner[facei]]
707  );
708 
709  ++facei;
710  ++bFacei;
711  }
712  }
713  }
714 
715  // Get the cell on the other side of coupled patches
716  syncTools::swapBoundaryFaceList(mesh, globalNeighbour);
717 
718 
719  // Count number of faces (internal + coupled)
720  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
721 
722  // Number of faces per coarse cell
723  labelList nFacesPerCell(nLocalCoarse, Zero);
724 
725  for (label facei = 0; facei < mesh.nInternalFaces(); ++facei)
726  {
727  const label own = agglom[faceOwner[facei]];
728  const label nei = agglom[faceNeighbour[facei]];
729 
730  nFacesPerCell[own]++;
731  nFacesPerCell[nei]++;
732  }
733 
734  for (const polyPatch& pp : patches)
735  {
736  if (pp.coupled() && (parallel || !isA<processorPolyPatch>(pp)))
737  {
738  label facei = pp.start();
739  label bFacei = pp.start() - mesh.nInternalFaces();
740 
741  forAll(pp, i)
742  {
743  const label own = agglom[faceOwner[facei]];
744  const label globalNei = globalNeighbour[bFacei];
745 
746  if
747  (
748  !globalAgglom.isLocal(globalNei)
749  || globalAgglom.toLocal(globalNei) != own
750  )
751  {
752  nFacesPerCell[own]++;
753  }
754 
755  ++facei;
756  ++bFacei;
757  }
758  }
759  }
760 
761 
762  // Fill in offset and data
763  // ~~~~~~~~~~~~~~~~~~~~~~~
764 
765  cellCells.setSize(nFacesPerCell);
766  cellCellWeights.setSize(nFacesPerCell);
767 
768  nFacesPerCell = 0;
769 
770  labelList& m = cellCells.m();
771  scalarList& w = cellCellWeights.m();
772  const labelList& offsets = cellCells.offsets();
773 
774  // For internal faces is just offsetted owner and neighbour
775  for (label facei = 0; facei < mesh.nInternalFaces(); ++facei)
776  {
777  const label own = agglom[faceOwner[facei]];
778  const label nei = agglom[faceNeighbour[facei]];
779 
780  const label ownIndex = offsets[own] + nFacesPerCell[own]++;
781  const label neiIndex = offsets[nei] + nFacesPerCell[nei]++;
782 
783  m[ownIndex] = globalAgglom.toGlobal(nei);
784  w[ownIndex] = mag(mesh.faceAreas()[facei]);
785  m[neiIndex] = globalAgglom.toGlobal(own);
786  w[ownIndex] = mag(mesh.faceAreas()[facei]);
787  }
788 
789  // For boundary faces is offsetted coupled neighbour
790  for (const polyPatch& pp : patches)
791  {
792  if (pp.coupled() && (parallel || !isA<processorPolyPatch>(pp)))
793  {
794  label facei = pp.start();
795  label bFacei = pp.start()-mesh.nInternalFaces();
796 
797  forAll(pp, i)
798  {
799  const label own = agglom[faceOwner[facei]];
800  const label globalNei = globalNeighbour[bFacei];
801 
802  if
803  (
804  !globalAgglom.isLocal(globalNei)
805  || globalAgglom.toLocal(globalNei) != own
806  )
807  {
808  const label ownIndex = offsets[own] + nFacesPerCell[own]++;
809  m[ownIndex] = globalNei;
810  w[ownIndex] = mag(mesh.faceAreas()[facei]);
811  }
812 
813  ++facei;
814  ++bFacei;
815  }
816  }
817  }
818 
819 
820  // Check for duplicates connections between cells
821  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
822  // Done as postprocessing step since we now have cellCells.
823 
824  if (cellCells.size() == 0)
825  {
826  return;
827  }
828 
829  label newIndex = 0;
830  labelHashSet nbrCells;
831 
832  label startIndex = cellCells.offsets()[0];
833 
834  forAll(cellCells, celli)
835  {
836  nbrCells.clear();
837  nbrCells.insert(globalAgglom.toGlobal(celli));
838 
839  const label endIndex = cellCells.offsets()[celli+1];
840 
841  for (label i = startIndex; i < endIndex; ++i)
842  {
843  if (nbrCells.insert(cellCells.m()[i]))
844  {
845  cellCells.m()[newIndex] = cellCells.m()[i];
846  cellCellWeights.m()[newIndex] = cellCellWeights.m()[i];
847  newIndex++;
848  }
849  }
850  startIndex = endIndex;
851  cellCells.offsets()[celli+1] = newIndex;
852  cellCellWeights.offsets()[celli+1] = newIndex;
853  }
854 
855  cellCells.m().setSize(newIndex);
856  cellCellWeights.m().setSize(newIndex);
857 }
859 
860 // NOTE:
861 // - alternative calcCellCells that handled explicitConnections was
862 // deactivated (2014 or earlier) and finally removed APR-2018.
863 
865 (
866  const polyMesh& mesh,
867  const scalarField& cellWeights,
868 
869  //- Whether owner and neighbour should be on same processor
870  // (takes priority over explicitConnections)
871  const boolList& blockedFace,
872 
873  //- Whether whole sets of faces (and point neighbours) need to be kept
874  // on single processor
875  const PtrList<labelList>& specifiedProcessorFaces,
876  const labelList& specifiedProcessor,
877 
878  //- Additional connections between boundary faces
879  const List<labelPair>& explicitConnections
880 ) const
881 {
882  // Any weights specified?
883  const bool hasWeights = returnReduceOr(cellWeights.size());
884 
885  if (hasWeights && cellWeights.size() != mesh.nCells())
886  {
888  << "Number of weights " << cellWeights.size()
889  << " differs from number of cells " << mesh.nCells()
890  << exit(FatalError);
891  }
892 
893  // Any faces not blocked?
894  const bool hasUnblocked =
896  (
897  !blockedFace.empty() && !BitOps::all(blockedFace)
898  );
899 
900 
901  // Any non-mesh connections?
902  const label nConnections = returnReduce
903  (
904  explicitConnections.size(),
905  sumOp<label>()
906  );
907 
908 
909  // Any processor sets?
910  label nProcSets = 0;
911  for (const labelList& procset : specifiedProcessorFaces)
912  {
913  nProcSets += procset.size();
914  }
915  reduce(nProcSets, sumOp<label>());
916 
917 
918  // Either do decomposition on cell centres or on agglomeration
919 
920  if (!hasUnblocked && !nConnections && !nProcSets)
921  {
922  // No constraints, possibly weights
923 
924  return
925  (
926  hasWeights
927  ? decompose(mesh, mesh.cellCentres(), cellWeights)
928  : decompose(mesh, mesh.cellCentres())
929  );
930  }
931 
932 
933  // The harder work.
934  // When we have processor sets, connections, or blocked faces.
935 
936 
937  // Determine local regions, separated by blockedFaces
938  regionSplit localRegion(mesh, blockedFace, explicitConnections, false);
939 
940  if (debug)
941  {
942  // Only need to count unblocked faces for debugging
943  const label nUnblocked =
944  (
945  hasUnblocked
946  ? returnReduce
947  (
948  label(BitOps::count(blockedFace, false)),
949  sumOp<label>()
950  )
951  : 0
952  );
953 
954  Info<< "Constrained decomposition:" << nl
955  << " faces with same owner and neighbour processor : "
956  << nUnblocked << nl
957  << " baffle faces with same owner processor : "
958  << nConnections << nl
959  << " faces all on same processor : "
960  << nProcSets << nl
961  << " split into " << localRegion.nLocalRegions()
962  << " regions."
963  << endl;
964  }
965 
966 
967  // Gather region weights and determine region cell centres
968  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
969 
970  // For the region centre, just take the first cell in the region.
971  // If we average the region centre instead, cyclics could cause
972  // the average domain centre to be outside of domain.
973 
974  scalarField regionWeights(localRegion.nLocalRegions(), Zero);
975 
976  pointField regionCentres(localRegion.nLocalRegions(), point::max);
977 
978  if (hasWeights)
979  {
980  forAll(localRegion, celli)
981  {
982  const label regioni = localRegion[celli];
983 
984  regionWeights[regioni] += cellWeights[celli];
985 
986  if (regionCentres[regioni] == point::max)
987  {
988  regionCentres[regioni] = mesh.cellCentres()[celli];
989  }
990  }
991  }
992  else
993  {
994  forAll(localRegion, celli)
995  {
996  const label regioni = localRegion[celli];
997 
998  regionWeights[regioni] += 1.0;
999 
1000  if (regionCentres[regioni] == point::max)
1001  {
1002  regionCentres[regioni] = mesh.cellCentres()[celli];
1003  }
1004  }
1005  }
1006 
1007  // Do decomposition on agglomeration
1008  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1009 
1010  labelList finalDecomp =
1011  decompose
1012  (
1013  mesh,
1014  localRegion,
1015  regionCentres,
1016  regionWeights
1017  );
1018 
1019 
1020  // Apply explicitConnections since decompose did not know about them
1021  for (const labelPair& baffle : explicitConnections)
1022  {
1023  const label f0 = baffle.first();
1024  const label f1 = baffle.second();
1025 
1026  if (!blockedFace[f0] && !blockedFace[f1])
1027  {
1028  // Note: what if internal faces and owner and neighbour on
1029  // different processor?
1030  // So for now just push owner side proc
1031 
1032  const label proci = finalDecomp[mesh.faceOwner()[f0]];
1033 
1034  finalDecomp[mesh.faceOwner()[f1]] = proci;
1035  if (mesh.isInternalFace(f1))
1036  {
1037  finalDecomp[mesh.faceNeighbour()[f1]] = proci;
1038  }
1039  }
1040  else if (blockedFace[f0] != blockedFace[f1])
1041  {
1043  << "On explicit connection between faces " << f0
1044  << " and " << f1
1045  << " the two blockedFace status are not equal : "
1046  << blockedFace[f0] << " and " << blockedFace[f1]
1047  << exit(FatalError);
1048  }
1049  }
1050 
1051 
1052  // blockedFaces corresponding to processor faces need to be handled
1053  // separately since not handled by local regionSplit. We need to
1054  // walk now across coupled faces and make sure to move a whole
1055  // global region across
1056 
1057  // This additionally consolidates/compacts the regions numbers globally,
1058  // since that was skipped in the previous regionSplit.
1059  if (Pstream::parRun())
1060  {
1061  // Re-do regionSplit
1062 
1063  // Field on cells and faces.
1064  List<minData> cellData(mesh.nCells());
1065  List<minData> faceData(mesh.nFaces());
1066 
1067  // Take over blockedFaces by seeding a negative number
1068  // (so is always less than the decomposition)
1069  label nUnblocked = 0;
1070  forAll(blockedFace, facei)
1071  {
1072  if (blockedFace[facei])
1073  {
1074  faceData[facei] = minData(-123);
1075  }
1076  else
1077  {
1078  ++nUnblocked;
1079  }
1080  }
1081 
1082  // Seed unblocked faces with destination processor
1083  labelList seedFaces(nUnblocked);
1084  List<minData> seedData(nUnblocked);
1085  nUnblocked = 0;
1086 
1087  forAll(blockedFace, facei)
1088  {
1089  if (!blockedFace[facei])
1090  {
1091  const label own = mesh.faceOwner()[facei];
1092  seedFaces[nUnblocked] = facei;
1093  seedData[nUnblocked] = minData(finalDecomp[own]);
1094  nUnblocked++;
1095  }
1096  }
1097 
1098 
1099  // Propagate information inwards
1100  FaceCellWave<minData> deltaCalc
1101  (
1102  mesh,
1103  seedFaces,
1104  seedData,
1105  faceData,
1106  cellData,
1108  );
1109 
1110  // And extract
1111  forAll(finalDecomp, celli)
1112  {
1113  if (cellData[celli].valid(deltaCalc.data()))
1114  {
1115  finalDecomp[celli] = cellData[celli].data();
1116  }
1117  }
1118  }
1119 
1120 
1121  // For specifiedProcessorFaces rework the cellToProc to enforce
1122  // all on one processor since we can't guarantee that the input
1123  // to regionSplit was a single region.
1124  // E.g. faceSet 'a' with the cells split into two regions
1125  // by a notch formed by two walls
1126  //
1127  // \ /
1128  // \ /
1129  // ---a----+-----a-----
1130  //
1131  //
1132  // Note that reworking the cellToProc might make the decomposition
1133  // unbalanced.
1134  forAll(specifiedProcessorFaces, seti)
1135  {
1136  const labelList& set = specifiedProcessorFaces[seti];
1137 
1138  label proci = specifiedProcessor[seti];
1139  if (proci == -1)
1140  {
1141  // If no processor specified - use the one from the 0th element
1142  if (set.size())
1143  {
1144  proci = finalDecomp[mesh.faceOwner()[set[0]]];
1145  }
1146  else
1147  {
1148  // Zero-sized processor (e.g. from redistributePar)
1149  proci = 0;
1150  }
1151  }
1152 
1153  for (const label facei : set)
1154  {
1155  const face& f = mesh.faces()[facei];
1156  for (const label pointi : f)
1157  {
1158  const labelList& pFaces = mesh.pointFaces()[pointi];
1159  for (const label pFacei : pFaces)
1160  {
1161  finalDecomp[mesh.faceOwner()[pFacei]] = proci;
1162  if (mesh.isInternalFace(pFacei))
1163  {
1164  finalDecomp[mesh.faceNeighbour()[pFacei]] = proci;
1165  }
1166  }
1167  }
1168  }
1169  }
1170 
1171 
1172  if (debug && Pstream::parRun())
1173  {
1174  labelList nbrDecomp;
1175  syncTools::swapBoundaryCellList(mesh, finalDecomp, nbrDecomp);
1176 
1178  for (const polyPatch& pp : patches)
1179  {
1180  if (pp.coupled())
1181  {
1182  forAll(pp, i)
1183  {
1184  const label facei = pp.start()+i;
1185  const label own = mesh.faceOwner()[facei];
1186  const label bFacei = facei-mesh.nInternalFaces();
1187 
1188  if (!blockedFace[facei])
1189  {
1190  const label ownProc = finalDecomp[own];
1191  const label nbrProc = nbrDecomp[bFacei];
1192 
1193  if (ownProc != nbrProc)
1194  {
1196  << "patch:" << pp.name()
1197  << " face:" << facei
1198  << " at:" << mesh.faceCentres()[facei]
1199  << " ownProc:" << ownProc
1200  << " nbrProc:" << nbrProc
1201  << exit(FatalError);
1202  }
1203  }
1204  }
1205  }
1206  }
1207  }
1208 
1209  return finalDecomp;
1210 }
1211 
1212 
1214 (
1215  const polyMesh& mesh,
1216  boolList& blockedFace,
1217  PtrList<labelList>& specifiedProcessorFaces,
1218  labelList& specifiedProcessor,
1219  List<labelPair>& explicitConnections
1220 ) const
1221 {
1222  blockedFace.setSize(mesh.nFaces());
1223  blockedFace = true;
1224 
1225  specifiedProcessorFaces.clear();
1226  explicitConnections.clear();
1227 
1228  for (const decompositionConstraint& decompConstraint : constraints_)
1229  {
1230  decompConstraint.add
1231  (
1232  mesh,
1233  blockedFace,
1234  specifiedProcessorFaces,
1235  specifiedProcessor,
1236  explicitConnections
1237  );
1238  }
1239 }
1240 
1241 
1243 (
1244  const polyMesh& mesh,
1245  const boolList& blockedFace,
1246  const PtrList<labelList>& specifiedProcessorFaces,
1247  const labelList& specifiedProcessor,
1248  const List<labelPair>& explicitConnections,
1249  labelList& decomposition
1250 ) const
1251 {
1252  for (const decompositionConstraint& decompConstraint : constraints_)
1253  {
1254  decompConstraint.apply
1255  (
1256  mesh,
1257  blockedFace,
1258  specifiedProcessorFaces,
1259  specifiedProcessor,
1260  explicitConnections,
1261  decomposition
1262  );
1263  }
1264 }
1265 
1266 
1268 (
1269  const polyMesh& mesh,
1270  const scalarField& cellWeights
1271 ) const
1272 {
1273  // Collect all constraints
1274 
1275  boolList blockedFace;
1276  PtrList<labelList> specifiedProcessorFaces;
1277  labelList specifiedProcessor;
1278  List<labelPair> explicitConnections;
1279  setConstraints
1280  (
1281  mesh,
1282  blockedFace,
1283  specifiedProcessorFaces,
1284  specifiedProcessor,
1285  explicitConnections
1286  );
1287 
1288 
1289  // Construct decomposition method and either do decomposition on
1290  // cell centres or on agglomeration
1291 
1292  labelList finalDecomp = decompose
1293  (
1294  mesh,
1295  cellWeights, // optional weights
1296  blockedFace, // any cells to be combined
1297  specifiedProcessorFaces,// any whole cluster of cells to be kept
1298  specifiedProcessor,
1299  explicitConnections // baffles
1300  );
1301 
1302 
1303  // Give any constraint the option of modifying the decomposition
1304 
1305  applyConstraints
1306  (
1307  mesh,
1308  blockedFace,
1309  specifiedProcessorFaces,
1310  specifiedProcessor,
1311  explicitConnections,
1312  finalDecomp
1313  );
1314 
1315  return finalDecomp;
1316 }
1317 
1318 
1319 // * * * * * * * * * * * * * * * Stub Functions * * * * * * * * * * * * * * //
1320 
1322 (
1324  const scalarField& pointWeights
1325 ) const
1326 {
1328  return labelList();
1329 }
1330 
1331 
1333 (
1335 ) const
1336 {
1338  return labelList();
1339 }
1340 
1341 
1342 // ************************************************************************* //
List< scalar > scalarList
List of scalar.
Definition: scalarList.H:32
bool isLocal(const label i) const
Is on local processor.
Definition: globalIndexI.H:261
This class separates the mesh into distinct unconnected regions, each of which is then given a label ...
Definition: regionSplit.H:136
Abstract class for handling decomposition constraints.
dictionary dict
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
label nLocalRegions() const
Return local number of regions.
Definition: regionSplit.H:320
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
label size() const noexcept
The primary size (the number of rows/sublists)
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)
const labelList & offsets() const noexcept
Return the offset table (= size()+1)
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:120
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:578
virtual const labelList & faceNeighbour() const
Return face neighbour.
Definition: polyMesh.C:1117
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
List< bool > select(const label n, const labelUList &locations)
Construct a selection list of bools (all false) with the given pre-size, subsequently add specified l...
Definition: BitOps.C:134
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:49
bool empty() const noexcept
True if List is empty (ie, size() is zero)
Definition: UList.H:632
T * data() noexcept
Return pointer to the underlying array serving as data storage.
Definition: UListI.H:229
virtual labelList decompose(const pointField &points, const scalarField &pointWeights) const
Return the wanted processor number for every coordinate.
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:487
Wave propagation of information through grid. Every iteration information goes through one layer of c...
Definition: FaceCellWave.H:206
void setConstraints(const polyMesh &mesh, boolList &blockedFace, PtrList< labelList > &specifiedProcessorFaces, labelList &specifiedProcessor, List< labelPair > &explicitConnections) const
Helper: extract constraints:
static bool & parRun() noexcept
Test if this a parallel run.
Definition: UPstream.H:1004
T get(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a T. FatalIOError if not found, or if the number of tokens is incorrect.
static label worldComm
Communicator for all ranks. May differ from commGlobal() if local worlds are in use.
Definition: UPstream.H:411
label nFaces() const noexcept
Number of mesh faces.
T returnReduce(const T &value, const BinaryOp &bop, const int tag=UPstream::msgType(), const label comm=UPstream::worldComm)
Perform reduction on a copy, using specified binary operation.
const fileName & name() const noexcept
The dictionary name.
Definition: dictionaryI.H:41
bool isInternalFace(const label faceIndex) const noexcept
Return true if given face label is internal to the mesh.
static bool warnAboutAge(const int version) noexcept
Test if an age warning should be emitted.
Definition: error.C:51
static void calcCellCells(const polyMesh &mesh, const labelList &agglom, const label nLocalCoarse, const bool global, CompactListList< label > &cellCells)
Helper: determine (local or global) cellCells from mesh.
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:414
static autoPtr< decompositionConstraint > New(const dictionary &constraintDict)
Return a reference to the selected decompositionConstraint.
void applyConstraints(const polyMesh &mesh, const boolList &blockedFace, const PtrList< labelList > &specifiedProcessorFaces, const labelList &specifiedProcessor, const List< labelPair > &explicitConnections, labelList &finalDecomp) const
Helper: apply constraints to a decomposition.
HashSet< label, Hash< label > > labelHashSet
A HashSet of labels, uses label hasher.
Definition: HashSet.H:85
bool readEntry(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX, IOobjectOption::readOption readOpt=IOobjectOption::MUST_READ) const
Find entry and assign to T val. FatalIOError if it is found and the number of tokens is incorrect...
Foam::word regionName(Foam::polyMesh::defaultRegion)
unsigned int count(const UList< bool > &bools, const bool val=true)
Count number of &#39;true&#39; entries.
Definition: BitOps.H:73
Calculates a unique integer (label so might not have enough room - 2G max) for processor + local inde...
Definition: globalIndex.H:62
static label nProcs(const label communicator=worldComm)
Number of ranks in parallel run (for given communicator). It is 1 for serial run. ...
Definition: UPstream.H:1020
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:38
void setSize(const label n)
Alias for resize()
Definition: List.H:289
dynamicFvMesh & mesh
const pointField & points
List< SubListType > unpack() const
Return non-compact list of lists.
void clear()
Clear the list, i.e. set size to zero.
Definition: ListI.H:109
const polyBoundaryMesh & boundaryMesh() const noexcept
Return boundary mesh.
Definition: polyMesh.H:584
A class for handling words, derived from Foam::string.
Definition: word.H:63
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
virtual const labelList & faceOwner() const
Return face owner.
Definition: polyMesh.C:1111
static const dictionary null
An empty dictionary, which is also the parent for all dictionaries.
Definition: dictionary.H:465
const globalMeshData & globalData() const
Return parallel info.
Definition: polyMesh.C:1300
label toLocal(const label i) const
From global to local on current processor.
Definition: globalIndexI.H:342
label nInternalFaces() const noexcept
Number of internal faces.
virtual const faceList & faces() const
Return raw faces.
Definition: polyMesh.C:1098
const vectorField & cellCentres() const
errorManip< error > abort(error &err)
Definition: errorManip.H:139
static const dictionary & findCoeffsDict(const dictionary &dict, const word &coeffsName, int select=selectionType::DEFAULT)
Locate coeffsName dictionary or the fallback "coeffs" dictionary within an enclosing dictionary...
label nDomains() const noexcept
Number of domains.
A polyBoundaryMesh is a polyPatch list with additional search methods and registered IO...
A packed storage unstructured matrix of objects of type <T> using an offset table for access...
void setSize(const label mRows)
Redimension - same as resize()
defineRunTimeSelectionTable(reactionRateFlameArea, dictionary)
int debug
Static debugging option.
bool readIfPresent(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX) const
Find an entry if present, and assign to T val. FatalIOError if it is found and the number of tokens i...
defineTypeNameAndDebug(combustionModel, 0)
labelList f(nPoints)
label nTotalCells() const noexcept
Return total number of cells in decomposed mesh.
Info<< "Finished reading KIVA file"<< endl;cellShapeList cellShapes(nPoints);labelList cellZoning(nPoints, -1);const cellModel &hex=cellModel::ref(cellModel::HEX);labelList hexLabels(8);label activeCells=0;labelList pointMap(nPoints);forAll(pointMap, i){ pointMap[i]=i;}for(label i=0;i< nPoints;i++){ if(f[i] > 0.0) { hexLabels[0]=i;hexLabels[1]=i1tab[i];hexLabels[2]=i3tab[i1tab[i]];hexLabels[3]=i3tab[i];hexLabels[4]=i8tab[i];hexLabels[5]=i1tab[i8tab[i]];hexLabels[6]=i3tab[i1tab[i8tab[i]]];hexLabels[7]=i3tab[i8tab[i]];cellShapes[activeCells].reset(hex, hexLabels);edgeList edges=cellShapes[activeCells].edges();forAll(edges, ei) { if(edges[ei].mag(points)< SMALL) { label start=pointMap[edges[ei].start()];while(start !=pointMap[start]) { start=pointMap[start];} label end=pointMap[edges[ei].end()];while(end !=pointMap[end]) { end=pointMap[end];} label minLabel=min(start, end);pointMap[start]=pointMap[end]=minLabel;} } cellZoning[activeCells]=idreg[i];activeCells++;}}cellShapes.setSize(activeCells);cellZoning.setSize(activeCells);forAll(cellShapes, celli){ cellShape &cs=cellShapes[celli];forAll(cs, i) { cs[i]=pointMap[cs[i]];} cs.collapse();}label bcIDs[11]={-1, 0, 2, 4, -1, 5, -1, 6, 7, 8, 9};const label nBCs=12;const word *kivaPatchTypes[nBCs]={ &wallPolyPatch::typeName, &wallPolyPatch::typeName, &wallPolyPatch::typeName, &wallPolyPatch::typeName, &symmetryPolyPatch::typeName, &wedgePolyPatch::typeName, &polyPatch::typeName, &polyPatch::typeName, &polyPatch::typeName, &polyPatch::typeName, &symmetryPolyPatch::typeName, &oldCyclicPolyPatch::typeName};enum patchTypeNames{ PISTON, VALVE, LINER, CYLINDERHEAD, AXIS, WEDGE, INFLOW, OUTFLOW, PRESIN, PRESOUT, SYMMETRYPLANE, CYCLIC};const char *kivaPatchNames[nBCs]={ "piston", "valve", "liner", "cylinderHead", "axis", "wedge", "inflow", "outflow", "presin", "presout", "symmetryPlane", "cyclic"};List< SLList< face > > pFaces[nBCs]
Definition: readKivaGrid.H:235
messageStream Warning
Warning stream (stdout output on master, null elsewhere), with additional &#39;FOAM Warning&#39; header text...
static void swapBoundaryCellList(const polyMesh &mesh, const UList< T > &cellData, List< T > &neighbourCellData)
Swap to obtain neighbour cell values for all boundary faces.
const vectorField & faceCentres() const
static autoPtr< decompositionMethod > New(const dictionary &decompDict, const word &regionName="")
Return a reference to the selected decomposition method, optionally region-specific.
label toGlobal(const label i) const
From local to global index.
Definition: globalIndexI.H:277
#define WarningInFunction
Report a warning using Foam::Warning.
label nCells() const noexcept
Number of mesh cells.
A list of pointers to objects of type <T>, with allocation/deallocation management of the pointers...
Definition: List.H:55
const vectorField & faceAreas() const
const polyBoundaryMesh & patches
static const dictionary & optionalRegionDict(const dictionary &decompDict, const word &regionName)
Return an optional region-specific dictionary from "regions" sub-dictionary, or dictionary::null on f...
void reduce(const List< UPstream::commsStruct > &comms, T &value, const BinaryOp &bop, const int tag, const label comm)
Reduce inplace (cf. MPI Allreduce) using specified communication schedule.
bool all(const UList< bool > &bools)
True if all entries are &#39;true&#39; or if the set is empty.
Definition: BitOps.H:83
void clear()
Clear the PtrList. Delete allocated entries and set size to zero.
Definition: PtrListI.H:81
messageStream Info
Information stream (stdout output on master, null elsewhere)
const labelListList & pointFaces() const
static const dictionary * cfindCoeffsDict(const dictionary &dict, const word &coeffsName, const bool allowDefault)
Reading is optional [identical to READ_IF_PRESENT].
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:73
T getOrDefault(const word &keyword, const T &deflt, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a T, or return the given default value. FatalIOError if it is found and the number of...
List< label > labelList
A List of labels.
Definition: List.H:62
A patch is a list of labels that address the faces in the global face list.
Definition: polyPatch.H:69
label nBoundaryFaces() const noexcept
Number of boundary faces (== nFaces - nInternalFaces)
bool returnReduceOr(const bool value, const label comm=UPstream::worldComm)
Perform logical (or) MPI Allreduce on a copy. Uses UPstream::reduceOr.
For use with FaceCellWave. Transports minimum passive data.
Definition: minData.H:56
#define NotImplemented
Issue a FatalErrorIn for a function not currently implemented.
Definition: error.H:666
#define FatalIOErrorInLookup(ios, lookupTag, lookupName, lookupTable)
Report an error message using Foam::FatalIOError.
Definition: error.H:615
const List< T > & m() const noexcept
Const access to the packed matrix of values.
Regular expression.
Definition: keyType.H:83
uindirectPrimitivePatch pp(UIndirectList< face >(mesh.faces(), faceLabels), mesh.points())
Namespace for OpenFOAM.
static void swapBoundaryFaceList(const polyMesh &mesh, UList< T > &faceValues)
Swap coupled boundary face values. Uses eqOp.
Definition: syncTools.H:485
const dictionary * findDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a sub-dictionary pointer if present (and a sub-dictionary) otherwise return nullptr...
Definition: dictionaryI.H:120
IOerror FatalIOError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL IO ERROR&#39; header text and ...
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:133