From 56e9f7bf4bfa3a67e5661d65bae3d5f68046b95a Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Fri, 23 Sep 2022 10:01:39 +0200
Subject: [PATCH] BUG: blockMesh mergePatchPairs fails with edge-shared points
 (fixes #2589)

- remedy by performing the attach() action sequentially (as per
  stitchMesh changes). This ensures that the current point addressing
  is always used and avoids references to the already-merged points
  (which is what causes the failure).

ENH: improve handling of empty patch removal

- only remove empty *merged* patches, but leave any other empty
  patches untouched since they may intentional placeholders for other
  parts of a workflow.

- remove any empty point/face zones created for patch merging
---
 .../generation/blockMesh/mergePatchPairs.H    | 215 +++++++++++++++---
 .../mesh/manipulation/stitchMesh/stitchMesh.C |  10 +-
 tutorials/mesh/blockMesh/mergePairs/Allclean  |  10 +
 tutorials/mesh/blockMesh/mergePairs/Allrun    |   8 +
 .../blockMesh/mergePairs/system/blockMeshDict | 119 ++++++++++
 .../blockMesh/mergePairs/system/controlDict   |  48 ++++
 6 files changed, 371 insertions(+), 39 deletions(-)
 create mode 100755 tutorials/mesh/blockMesh/mergePairs/Allclean
 create mode 100755 tutorials/mesh/blockMesh/mergePairs/Allrun
 create mode 100644 tutorials/mesh/blockMesh/mergePairs/system/blockMeshDict
 create mode 100644 tutorials/mesh/blockMesh/mergePairs/system/controlDict

diff --git a/applications/utilities/mesh/generation/blockMesh/mergePatchPairs.H b/applications/utilities/mesh/generation/blockMesh/mergePatchPairs.H
index 8d8021b08f2..94c6c6c7797 100644
--- a/applications/utilities/mesh/generation/blockMesh/mergePatchPairs.H
+++ b/applications/utilities/mesh/generation/blockMesh/mergePatchPairs.H
@@ -1,30 +1,56 @@
-// Handle merging of patch pairs
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2009 OpenFOAM Foundation
+    Copyright (C) 2017-2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
+
+Description
+    Handle merging of patch pairs
+
+\*---------------------------------------------------------------------------*/
+
 {
     wordPairList mergePatchPairs;
 
-    // Read in a list of dictionaries for the merge patch pairs
+    // Read in a list of merge patch pairs
     if
     (
         meshDict.readIfPresent("mergePatchPairs", mergePatchPairs)
      && mergePatchPairs.size()
     )
     {
-        Info<< "Creating merge patch pairs" << nl << endl;
+        Info<< "Merging " << mergePatchPairs.size() << " patch pairs" << nl;
 
-        Info<< "Adding point and face zones" << endl;
+        // Cleanup
+        wordHashSet cleanupPatches(4*mergePatchPairs.size());
+        wordHashSet cleanupPointZones(2*mergePatchPairs.size());
+        wordHashSet cleanupFaceZones(2*mergePatchPairs.size());
+
+        Info<< "    Adding point and face zones" << endl;
         {
-            auto& pzs = mesh.pointZones();
-            pzs.clearAddressing();
-            auto& fzs = mesh.faceZones();
-            fzs.clearAddressing();
+            const auto& pbm = mesh.boundaryMesh();
+
+            auto& pzs = mesh.pointZones(); pzs.clearAddressing();
+            auto& fzs = mesh.faceZones();  fzs.clearAddressing();
 
             forAll(mergePatchPairs, pairi)
             {
+                // Patch pairs
+                const polyPatch& patch0 = pbm[mergePatchPairs[pairi].first()];
+                const polyPatch& patch1 = pbm[mergePatchPairs[pairi].second()];
+
                 const word mergeName
                 (
                     mergePatchPairs[pairi].first()
                   + mergePatchPairs[pairi].second()
-                  + name(pairi)
+                  + Foam::name(pairi)
                 );
 
                 // An empty zone for cut points
@@ -37,40 +63,35 @@
                         pzs
                     )
                 );
+                cleanupPointZones.insert(pzs.last().name());
 
-                // Master patch
-                const word masterPatchName(mergePatchPairs[pairi].first());
-                const polyPatch& masterPatch =
-                    mesh.boundaryMesh()[masterPatchName];
-
+                // Coupling side 0 (master)
                 fzs.append
                 (
                     new faceZone
                     (
-                        mergeName + "MasterZone",
-                        identity(masterPatch.range()),
+                        mergeName + "Side0Zone",
+                        identity(patch0.range()),
                         false, // none are flipped
                         fzs.size(),
                         fzs
                     )
                 );
+                cleanupFaceZones.insert(fzs.last().name());
 
-                // Slave patch
-                const word slavePatchName(mergePatchPairs[pairi].second());
-                const polyPatch& slavePatch =
-                    mesh.boundaryMesh()[slavePatchName];
-
+                // Coupling side 1 (slave)
                 fzs.append
                 (
                     new faceZone
                     (
-                        mergeName + "SlaveZone",
-                        identity(slavePatch.range()),
+                        mergeName + "Side1Zone",
+                        identity(patch1.range()),
                         false, // none are flipped
                         fzs.size(),
                         fzs
                     )
                 );
+                cleanupFaceZones.insert(fzs.last().name());
 
                 // An empty zone for cut faces
                 fzs.append
@@ -82,35 +103,38 @@
                         fzs
                     )
                 );
-            }  // end of all merge pairs
+                cleanupFaceZones.insert(fzs.last().name());
+            }
         }
 
 
-
-        Info<< "Creating attachPolyTopoChanger" << endl;
+        Info<< "    Merging with attachPolyTopoChanger" << endl;
         attachPolyTopoChanger polyMeshAttacher(mesh);
-        polyMeshAttacher.setSize(mergePatchPairs.size());
+        polyMeshAttacher.resize(1);
 
         forAll(mergePatchPairs, pairi)
         {
+            cleanupPatches.insert(mergePatchPairs[pairi].first());
+            cleanupPatches.insert(mergePatchPairs[pairi].second());
+
             const word mergeName
             (
                 mergePatchPairs[pairi].first()
               + mergePatchPairs[pairi].second()
-              + name(pairi)
+              + Foam::name(pairi)
             );
 
             // Add the sliding interface mesh modifier
             polyMeshAttacher.set
             (
-                pairi,
+                0,
                 new slidingInterface
                 (
-                    "couple" + name(pairi),
+                    "couple" + Foam::name(pairi),
                     pairi,
                     polyMeshAttacher,
-                    mergeName + "MasterZone",
-                    mergeName + "SlaveZone",
+                    mergeName + "Side0Zone",
+                    mergeName + "Side1Zone",
                     mergeName + "CutPointZone",
                     mergeName + "CutFaceZone",
                     mergePatchPairs[pairi].first(),
@@ -120,12 +144,135 @@
                     intersection::VISIBLE
                 )
             );
+
+            polyMeshAttacher.attach(false);  // Do not yet remove empty patches
         }
 
-        polyMeshAttacher.attach(true);
+        // Re-do the boundary patches, removing empty merge patches
+        // but keeping any other empty patches
+        {
+            const polyBoundaryMesh& oldPatches = mesh.boundaryMesh();
+
+            polyPatchList newPatches(oldPatches.size());
+            label nNewPatches = 0;
+
+            wordHashSet removedPatches(cleanupPatches.capacity());
+
+            forAll(oldPatches, patchi)
+            {
+                const word& patchName = oldPatches[patchi].name();
+
+                if
+                (
+                    !cleanupPatches.found(patchName)
+                 || returnReduceOr(oldPatches[patchi].size())
+                )
+                {
+                    newPatches.set
+                    (
+                        nNewPatches,
+                        oldPatches[patchi].clone
+                        (
+                            mesh.boundaryMesh(),
+                            nNewPatches,
+                            oldPatches[patchi].size(),
+                            oldPatches[patchi].start()
+                        )
+                    );
+
+                    ++nNewPatches;
+                }
+                else
+                {
+                    removedPatches.insert(patchName);
+                }
+            }
+
+            newPatches.resize(nNewPatches);
+
+            mesh.removeBoundary();
+            mesh.addPatches(newPatches);
+
+            Info<< "Removed " << removedPatches.size()
+                << " empty merged patches:" << nl
+                << "    " << flatOutput(removedPatches.sortedToc()) << endl;
+        }
+
+        // Cleanup empty merged point zones
+        {
+            PtrList<pointZone>& zones = mesh.pointZones();
+            mesh.pointZones().clearAddressing();
+
+            wordHashSet removedZones(2*zones.size());
+
+            label nZones = 0;
+            forAll(zones, zonei)
+            {
+                if
+                (
+                    !cleanupPointZones.found(zones[zonei].name())
+                 || returnReduceOr(zones[zonei].size())
+                )
+                {
+                    zones.set(nZones, zones.release(zonei));
+                    zones[nZones].index() = nZones;  // re-index
+                    ++nZones;
+                }
+                else
+                {
+                    removedZones.insert(zones[zonei].name());
+                }
+            }
+            zones.resize(nZones);
+
+            if (removedZones.size())
+            {
+                Info<< "Removed " << removedZones.size()
+                    << " empty point zones:" << nl
+                    << "    " << flatOutput(removedZones.sortedToc()) << endl;
+            }
+        }
+
+        // Cleanup empty merged face zones
+        {
+            PtrList<faceZone>& zones = mesh.faceZones();
+            mesh.faceZones().clearAddressing();
+
+            wordHashSet removedZones(2*zones.size());
+
+            label nZones = 0;
+            forAll(zones, zonei)
+            {
+                if
+                (
+                    !cleanupFaceZones.found(zones[zonei].name())
+                 || returnReduceOr(zones[zonei].size())
+                )
+                {
+                    zones.set(nZones, zones.release(zonei));
+                    zones[nZones].index() = nZones;  // re-index
+                    ++nZones;
+                }
+                else
+                {
+                    removedZones.insert(zones[zonei].name());
+                }
+            }
+            zones.resize(nZones);
+
+            if (removedZones.size())
+            {
+                Info<< "Removed " << removedZones.size()
+                    << " empty merged face zones:" << nl
+                    << "    " << flatOutput(removedZones.sortedToc()) << endl;
+            }
+        }
     }
     else
     {
-        Info<< nl << "There are no merge patch pairs" << endl;
+        Info<< "No patch pairs to merge" << endl;
     }
 }
+
+
+// ************************************************************************* //
diff --git a/applications/utilities/mesh/manipulation/stitchMesh/stitchMesh.C b/applications/utilities/mesh/manipulation/stitchMesh/stitchMesh.C
index 2cfe9fe00ad..57812d4ec06 100644
--- a/applications/utilities/mesh/manipulation/stitchMesh/stitchMesh.C
+++ b/applications/utilities/mesh/manipulation/stitchMesh/stitchMesh.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -564,7 +564,7 @@ int main(int argc, char *argv[])
 
             mesh.faceZones()
             (
-                mergePatchName + "MasterZone",
+                mergePatchName + "Side0Zone",
                 true // verbose
             ).resetAddressing(std::move(faceIds), false);
 
@@ -574,7 +574,7 @@ int main(int argc, char *argv[])
 
             mesh.faceZones()
             (
-                mergePatchName + "SlaveZone",
+                mergePatchName + "Side1Zone",
                 true // verbose
             ).resetAddressing(std::move(faceIds), false);
 
@@ -595,8 +595,8 @@ int main(int argc, char *argv[])
                     "couple" + Foam::name(actioni),
                     0,
                     stitcher,
-                    mergePatchName + "MasterZone",
-                    mergePatchName + "SlaveZone",
+                    mergePatchName + "Side0Zone",
+                    mergePatchName + "Side1Zone",
                     mergePatchName + "CutPointZone",
                     cutZoneName,
                     masterPatchName,
diff --git a/tutorials/mesh/blockMesh/mergePairs/Allclean b/tutorials/mesh/blockMesh/mergePairs/Allclean
new file mode 100755
index 00000000000..d4f5975bc81
--- /dev/null
+++ b/tutorials/mesh/blockMesh/mergePairs/Allclean
@@ -0,0 +1,10 @@
+#!/bin/sh
+cd "${0%/*}" || exit                                # Run from this directory
+. ${WM_PROJECT_DIR:?}/bin/tools/CleanFunctions      # Tutorial clean functions
+#------------------------------------------------------------------------------
+
+cleanCase0
+
+rm -rf constant
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/mesh/blockMesh/mergePairs/Allrun b/tutorials/mesh/blockMesh/mergePairs/Allrun
new file mode 100755
index 00000000000..c0ee92beedf
--- /dev/null
+++ b/tutorials/mesh/blockMesh/mergePairs/Allrun
@@ -0,0 +1,8 @@
+#!/bin/sh
+cd "${0%/*}" || exit                                # Run from this directory
+. ${WM_PROJECT_DIR:?}/bin/tools/RunFunctions        # Tutorial run functions
+#------------------------------------------------------------------------------
+
+runApplication blockMesh
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/mesh/blockMesh/mergePairs/system/blockMeshDict b/tutorials/mesh/blockMesh/mergePairs/system/blockMeshDict
new file mode 100644
index 00000000000..aaa0d5c7053
--- /dev/null
+++ b/tutorials/mesh/blockMesh/mergePairs/system/blockMeshDict
@@ -0,0 +1,119 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2212                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      blockMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+scale   0.001;
+
+vertices
+(
+    // Block d
+    ( 50 -17 -12.5)
+    (100 -17 -12.5)
+    (100  -2 -12.5)
+    ( 50  -2 -12.5)
+    ( 50 -17  12.5)
+    (100 -17  12.5)
+    (100  -2  12.5)
+    ( 50  -2  12.5)
+
+    // Block e
+    (  0 -17  -12.5)
+    ( 50 -17  -12.5)
+    ( 50  -2  -12.5)
+    (  0  -2  -12.5)
+    (  0 -17   12.5)
+    ( 50 -17   12.5)
+    ( 50  -2   12.5)
+    (  0  -2   12.5)
+
+    // Block f
+    (  0 -19 -12.5)
+    ( 50 -19 -12.5)
+    ( 50 -17 -12.5)
+    (  0 -17 -12.5)
+    (  0 -19  12.5)
+    ( 50 -19  12.5)
+    ( 50 -17  12.5)
+    (  0 -17  12.5)
+);
+
+blocks
+(
+    // Block d
+    hex (0 1 2 3 4 5 6 7) block-d (4 5 4) grading (1 1 1)
+
+    // Block e
+    hex (8 9 10 11 12 13 14 15) block-e (4 4 4) grading (1 1 1)
+
+    // Block f
+    hex (16 17 18 19 20 21 22 23) block-f (5 4 4) grading (1 1 1)
+);
+
+
+edges
+(
+);
+
+
+boundary
+(
+    mid6
+    {
+        type patch;
+        faces
+        (
+            (0 0) // Block d
+        );
+    }
+    mid7
+    {
+        type patch;
+        faces
+        (
+            (1 1) // Block e
+        );
+    }
+    mid8
+    {
+        type patch;
+        faces
+        (
+            (1 2) // Block e
+        );
+    }
+    mid9
+    {
+        type patch;
+        faces
+        (
+            (2 3) // Block e
+        );
+    }
+
+    other
+    {
+        type  patch;
+        faces ();
+    }
+);
+
+
+mergePatchPairs
+(
+    (mid6 mid7)
+    (mid8 mid9)
+);
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/blockMesh/mergePairs/system/controlDict b/tutorials/mesh/blockMesh/mergePairs/system/controlDict
new file mode 100644
index 00000000000..c2e46fdfc91
--- /dev/null
+++ b/tutorials/mesh/blockMesh/mergePairs/system/controlDict
@@ -0,0 +1,48 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2206                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      controlDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+application     blockMesh;
+
+startFrom       startTime;
+
+startTime       0;
+
+stopAt          endTime;
+
+endTime         0;
+
+deltaT          0;
+
+writeControl    timeStep;
+
+writeInterval   1;
+
+purgeWrite      0;
+
+writeFormat     ascii;
+
+writePrecision  6;
+
+writeCompression off;
+
+timeFormat      general;
+
+timePrecision   6;
+
+runTimeModifiable true;
+
+
+// ************************************************************************* //
-- 
GitLab