From 610711ac471d0f9193125e319f27a70f5a806d09 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Tue, 31 Aug 2021 15:36:05 +0200
Subject: [PATCH] ENH: inGroups support for mesh zones

- provisions for alternative groupings of zones

- extend indices() for boundary meshes to handle wordRes as well
---
 .../createBaffles/createBaffles.C             |   8 +-
 src/OpenFOAM/Make/files                       |   1 +
 .../Identifiers/patch/coupleGroupIdentifier.C |  10 +-
 .../Identifiers/patch/coupleGroupIdentifier.H |  28 +-
 .../patch/coupleGroupIdentifierI.H            |  54 +---
 .../Identifiers/patch/patchIdentifier.C       |  20 +-
 .../Identifiers/patch/patchIdentifier.H       |  55 ++--
 .../surface/geometricSurfacePatch.C           |  56 +---
 .../surface/geometricSurfacePatch.H           |  60 ++--
 .../Identifiers/surface/surfZoneIdentifier.C  |  43 +--
 .../Identifiers/surface/surfZoneIdentifier.H  |  18 +-
 .../Identifiers/{zones => zone}/ZoneIDs.H     |   0
 .../meshes/Identifiers/zone/zoneIdentifier.C  | 121 +++++++
 .../meshes/Identifiers/zone/zoneIdentifier.H  | 198 ++++++++++++
 .../pointBoundaryMesh/pointBoundaryMesh.C     |  10 +
 .../pointBoundaryMesh/pointBoundaryMesh.H     |   9 +-
 .../polyBoundaryMesh/polyBoundaryMesh.C       | 302 +++++++++++-------
 .../polyBoundaryMesh/polyBoundaryMesh.H       |  25 +-
 .../polyPatches/polyPatch/polyPatch.H         |  25 +-
 .../meshes/polyMesh/zones/ZoneMesh/ZoneMesh.C | 252 +++++++++++++--
 .../meshes/polyMesh/zones/ZoneMesh/ZoneMesh.H |  51 ++-
 .../meshes/polyMesh/zones/cellZone/cellZone.C |  16 +-
 .../meshes/polyMesh/zones/cellZone/cellZone.H |  29 +-
 .../meshes/polyMesh/zones/faceZone/faceZone.C |  56 ++--
 .../meshes/polyMesh/zones/faceZone/faceZone.H |  64 ++--
 .../polyMesh/zones/pointZone/pointZone.C      |  27 +-
 .../polyMesh/zones/pointZone/pointZone.H      |  29 +-
 .../meshes/polyMesh/zones/zone/zone.C         | 104 +++---
 .../meshes/polyMesh/zones/zone/zone.H         |  66 +---
 .../primitives/strings/lists/stringListOps.H  |  10 +
 .../strings/lists/stringListOpsTemplates.C    |  22 ++
 .../faMesh/faBoundaryMesh/faBoundaryMesh.C    |  46 ++-
 .../faMesh/faBoundaryMesh/faBoundaryMesh.H    |  32 +-
 src/finiteArea/faMesh/faMesh.C                |   3 -
 .../faMesh/faPatches/faPatch/faPatch.C        |   6 +-
 .../faMesh/faPatches/faPatch/faPatch.H        |   4 +-
 .../fvMesh/fvBoundaryMesh/fvBoundaryMesh.C    |  10 +
 .../fvMesh/fvBoundaryMesh/fvBoundaryMesh.H    |   9 +-
 .../triSurface/patches/surfacePatch.C         |  21 +-
 .../triSurface/patches/surfacePatch.H         |  15 +-
 40 files changed, 1263 insertions(+), 652 deletions(-)
 rename src/OpenFOAM/meshes/Identifiers/{zones => zone}/ZoneIDs.H (100%)
 create mode 100644 src/OpenFOAM/meshes/Identifiers/zone/zoneIdentifier.C
 create mode 100644 src/OpenFOAM/meshes/Identifiers/zone/zoneIdentifier.H

diff --git a/applications/utilities/mesh/manipulation/createBaffles/createBaffles.C b/applications/utilities/mesh/manipulation/createBaffles/createBaffles.C
index 0fa4f2cd7b0..269317bc3bb 100644
--- a/applications/utilities/mesh/manipulation/createBaffles/createBaffles.C
+++ b/applications/utilities/mesh/manipulation/createBaffles/createBaffles.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -85,11 +85,11 @@ label addPatch
                 pbm
             )
         );
-        polyPatch& pp = ppPtr();
+        auto& pp = *ppPtr;
 
-        if (!groupName.empty() && !pp.inGroup(groupName))
+        if (!groupName.empty())
         {
-            pp.inGroups().append(groupName);
+            pp.inGroups().appendUniq(groupName);
         }
 
 
diff --git a/src/OpenFOAM/Make/files b/src/OpenFOAM/Make/files
index 1af3509c2f7..6b2f6146074 100644
--- a/src/OpenFOAM/Make/files
+++ b/src/OpenFOAM/Make/files
@@ -549,6 +549,7 @@ meshes/Identifiers/patch/patchIdentifier.C
 meshes/Identifiers/patch/coupleGroupIdentifier.C
 meshes/Identifiers/surface/geometricSurfacePatch.C
 meshes/Identifiers/surface/surfZoneIdentifier.C
+meshes/Identifiers/zone/zoneIdentifier.C
 
 meshes/MeshObject/meshObject.C
 
diff --git a/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifier.C b/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifier.C
index d099fe18f7e..0cbb2f251ab 100644
--- a/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifier.C
+++ b/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifier.C
@@ -131,8 +131,6 @@ Foam::label Foam::coupleGroupIdentifier::findOtherPatchID
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::coupleGroupIdentifier::coupleGroupIdentifier(const dictionary& dict)
-:
-    name_()
 {
     dict.readIfPresent("coupleGroup", name_);
 }
@@ -212,18 +210,18 @@ Foam::label Foam::coupleGroupIdentifier::findOtherPatchID
 
 void Foam::coupleGroupIdentifier::write(Ostream& os) const
 {
-    if (valid())
+    if (!name_.empty())
     {
-        os.writeEntry("coupleGroup", name());
+        os.writeEntry("coupleGroup", name_);
     }
 }
 
 
 // * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
 
-Foam::Ostream& Foam::operator<<(Ostream& os, const coupleGroupIdentifier& p)
+Foam::Ostream& Foam::operator<<(Ostream& os, const coupleGroupIdentifier& ident)
 {
-    p.write(os);
+    ident.write(os);
     os.check(FUNCTION_NAME);
     return os;
 }
diff --git a/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifier.H b/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifier.H
index fd95e43185e..9d7bed8ed26 100644
--- a/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifier.H
+++ b/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifier.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2013 OpenFOAM Foundation
-    Copyright (C) 2020 OpenCFD Ltd.
+    Copyright (C) 2020-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,7 +31,6 @@ Description
     Encapsulates using "patchGroups" to specify coupled patch
 
 SourceFiles
-    coupleGroupIdentifierI.H
     coupleGroupIdentifier.C
 
 \*---------------------------------------------------------------------------*/
@@ -85,8 +84,11 @@ public:
 
     // Constructors
 
-        //- Construct from components
-        inline explicit coupleGroupIdentifier(const word& patchGroupName);
+        //- Construct from patchGroup name
+        explicit coupleGroupIdentifier(const word& patchGroupName)
+        :
+            name_(patchGroupName)
+        {}
 
         //- Construct from dictionary
         explicit coupleGroupIdentifier(const dictionary& dict);
@@ -95,10 +97,16 @@ public:
     // Member Functions
 
         //- Name of patchGroup
-        inline const word& name() const;
+        const word& name() const noexcept
+        {
+            return name_;
+        }
 
-        //- Is a valid patchGroup
-        inline bool valid() const;
+        //- Is a valid patchGroup (non-empty) name
+        bool valid() const noexcept
+        {
+            return !name_.empty();
+        }
 
         //- Find other patch in same region.
         //  \return index of patch or -1.
@@ -121,7 +129,7 @@ public:
 // Global Operators
 
 //- Write the coupleGroup dictionary entry
-Ostream& operator<<(Ostream& os, const coupleGroupIdentifier& p);
+Ostream& operator<<(Ostream& os, const coupleGroupIdentifier& ident);
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -130,10 +138,6 @@ Ostream& operator<<(Ostream& os, const coupleGroupIdentifier& p);
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-#include "coupleGroupIdentifierI.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
 #endif
 
 // ************************************************************************* //
diff --git a/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifierI.H b/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifierI.H
index 29c4e531236..7cec1b4541d 100644
--- a/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifierI.H
+++ b/src/OpenFOAM/meshes/Identifiers/patch/coupleGroupIdentifierI.H
@@ -1,53 +1 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2013 OpenFOAM Foundation
--------------------------------------------------------------------------------
-License
-    This file is part of OpenFOAM.
-
-    OpenFOAM is free software: you can redistribute it and/or modify it
-    under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
-    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-    for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-inline Foam::coupleGroupIdentifier::coupleGroupIdentifier
-(
-    const word& patchGroupName
-)
-:
-    name_(patchGroupName)
-{}
-
-
-// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
-
-const Foam::word& Foam::coupleGroupIdentifier::name() const
-{
-    return name_;
-}
-
-
-bool Foam::coupleGroupIdentifier::valid() const
-{
-    return !name_.empty();
-}
-
-
-// ************************************************************************* //
+#warning File removed - left for old dependency check only
diff --git a/src/OpenFOAM/meshes/Identifiers/patch/patchIdentifier.C b/src/OpenFOAM/meshes/Identifiers/patch/patchIdentifier.C
index 48ed341ec2d..b46b7e0eb51 100644
--- a/src/OpenFOAM/meshes/Identifiers/patch/patchIdentifier.C
+++ b/src/OpenFOAM/meshes/Identifiers/patch/patchIdentifier.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2013 OpenFOAM Foundation
-    Copyright (C) 2020 OpenCFD Ltd.
+    Copyright (C) 2020-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -80,14 +80,14 @@ Foam::patchIdentifier::patchIdentifier
 
 Foam::patchIdentifier::patchIdentifier
 (
-    const patchIdentifier& p,
+    const patchIdentifier& ident,
     const label index
 )
 :
-    name_(p.name_),
+    name_(ident.name_),
     index_(index),
-    physicalType_(p.physicalType_),
-    inGroups_(p.inGroups_)
+    physicalType_(ident.physicalType_),
+    inGroups_(ident.inGroups_)
 {}
 
 
@@ -95,15 +95,15 @@ Foam::patchIdentifier::patchIdentifier
 
 void Foam::patchIdentifier::write(Ostream& os) const
 {
-    if (physicalType_.size())
+    if (!physicalType_.empty())
     {
         os.writeEntry("physicalType", physicalType_);
     }
 
-    if (inGroups_.size())
+    if (!inGroups_.empty())
     {
         os.writeKeyword("inGroups");
-        // Write list with flatOutput
+        // Flat output of list
         inGroups_.writeList(os, 0) << token::END_STATEMENT << nl;
     }
 }
@@ -111,9 +111,9 @@ void Foam::patchIdentifier::write(Ostream& os) const
 
 // * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
 
-Foam::Ostream& Foam::operator<<(Ostream& os, const patchIdentifier& p)
+Foam::Ostream& Foam::operator<<(Ostream& os, const patchIdentifier& ident)
 {
-    p.write(os);
+    ident.write(os);
     os.check(FUNCTION_NAME);
     return os;
 }
diff --git a/src/OpenFOAM/meshes/Identifiers/patch/patchIdentifier.H b/src/OpenFOAM/meshes/Identifiers/patch/patchIdentifier.H
index e34a456f7d6..1964dc3ced4 100644
--- a/src/OpenFOAM/meshes/Identifiers/patch/patchIdentifier.H
+++ b/src/OpenFOAM/meshes/Identifiers/patch/patchIdentifier.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2013 OpenFOAM Foundation
-    Copyright (C) 2020 OpenCFD Ltd.
+    Copyright (C) 2020-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -28,7 +28,8 @@ Class
     Foam::patchIdentifier
 
 Description
-    Identifies a patch by name, patch index and physical type
+    Identifies a patch by name and index, with optional physical type
+    and group information.
 
 SourceFiles
     patchIdentifier.C
@@ -56,16 +57,16 @@ class patchIdentifier
 {
     // Private Data
 
-        //- Name of patch
+        //- Patch name
         word name_;
 
-        //- Index of patch in boundary
+        //- Patch index in boundary
         label index_;
 
-        //- Optional physical type
-        mutable word physicalType_;
+        //- Patch physical type (optional)
+        word physicalType_;
 
-        //- Optional groups to which the patch belongs
+        //- Groups to which the patch belongs (optional)
         wordList inGroups_;
 
 public:
@@ -98,7 +99,7 @@ public:
 
     // Constructors
 
-        //- Default construct, with index zero
+        //- Default construct. Uses name="", index=0
         patchIdentifier();
 
         //- Construct from mandatory components
@@ -124,7 +125,7 @@ public:
         //- Copy construct, resetting the index
         patchIdentifier
         (
-            const patchIdentifier& p,
+            const patchIdentifier& ident,
             const label index
         );
 
@@ -132,54 +133,54 @@ public:
     // Member Functions
 
         //- The patch name
-        const word& name() const
+        const word& name() const noexcept
         {
             return name_;
         }
 
         //- Modifiable patch name
-        word& name()
+        word& name() noexcept
         {
             return name_;
         }
 
-        //- The (optional) physical type of the patch
-        const word& physicalType() const
+        //- The index of this patch in the boundaryMesh
+        label index() const noexcept
         {
-            return physicalType_;
+            return index_;
         }
 
-        //- Modifiable (optional) physical type of the patch
-        word& physicalType()
+        //- Modifiable index of this patch in the boundaryMesh
+        label& index() noexcept
         {
-            return physicalType_;
+            return index_;
         }
 
-        //- The index of this patch in the boundaryMesh
-        label index() const
+        //- The (optional) physical type of the patch
+        const word& physicalType() const noexcept
         {
-            return index_;
+            return physicalType_;
         }
 
-        //- Modifiable index of this patch in the boundaryMesh
-        label& index()
+        //- Modifiable (optional) physical type of the patch
+        word& physicalType() noexcept
         {
-            return index_;
+            return physicalType_;
         }
 
         //- The (optional) groups that the patch belongs to
-        const wordList& inGroups() const
+        const wordList& inGroups() const noexcept
         {
             return inGroups_;
         }
 
         //- Modifiable (optional) groups that the patch belongs to
-        wordList& inGroups()
+        wordList& inGroups() noexcept
         {
             return inGroups_;
         }
 
-        //- True if the patch is in named group
+        //- True if given name is in a group
         bool inGroup(const word& name) const
         {
             return inGroups_.found(name);
@@ -195,7 +196,7 @@ public:
 
 //- Write (physicalType, inGroups) dictionary entries
 //- (without surrounding braces)
-Ostream& operator<<(Ostream& os, const patchIdentifier& p);
+Ostream& operator<<(Ostream& os, const patchIdentifier& ident);
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/OpenFOAM/meshes/Identifiers/surface/geometricSurfacePatch.C b/src/OpenFOAM/meshes/Identifiers/surface/geometricSurfacePatch.C
index 8cfbd6934ec..abe0b802ed7 100644
--- a/src/OpenFOAM/meshes/Identifiers/surface/geometricSurfacePatch.C
+++ b/src/OpenFOAM/meshes/Identifiers/surface/geometricSurfacePatch.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -29,29 +29,6 @@ License
 #include "geometricSurfacePatch.H"
 #include "dictionary.H"
 
-// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-static inline word readOptionalWord(Istream& is)
-{
-    token tok(is);
-
-    if (tok.isWord())
-    {
-        return tok.wordToken();
-    }
-    else
-    {
-        // Allow empty words
-        return word::validate(tok.stringToken());
-    }
-}
-
-} // End namespace Foam
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::geometricSurfacePatch::geometricSurfacePatch()
@@ -64,7 +41,7 @@ Foam::geometricSurfacePatch::geometricSurfacePatch(const label index)
 :
     name_("patch"),
     index_(index),
-    geometricType_(emptyType)
+    geometricType_()
 {}
 
 
@@ -100,9 +77,7 @@ Foam::geometricSurfacePatch::geometricSurfacePatch
     const label index
 )
 :
-    name_(name),
-    index_(index),
-    geometricType_()
+    geometricSurfacePatch(name, index)
 {
     dict.readIfPresent("geometricType", geometricType_);
 }
@@ -121,7 +96,7 @@ Foam::geometricSurfacePatch::geometricSurfacePatch
 
 void Foam::geometricSurfacePatch::write(Ostream& os) const
 {
-    if (geometricType_.size())
+    if (!geometricType_.empty())
     {
         os.writeEntry("geometricType", geometricType_);
     }
@@ -138,8 +113,8 @@ bool Foam::operator==
 {
     return
     (
-        (a.geometricType() == b.geometricType())
-     && (a.name() == b.name())
+        (a.name() == b.name())
+     && (a.geometricType() == b.geometricType())
     );
 }
 
@@ -158,8 +133,9 @@ bool Foam::operator!=
 
 Foam::Istream& Foam::operator>>(Istream& is, geometricSurfacePatch& obj)
 {
-    obj.name() = readOptionalWord(is);
-    obj.geometricType() = readOptionalWord(is);
+    // Also read "" for empty words
+    obj.name() = word::validate(token(is).stringToken());
+    obj.geometricType() = word::validate(token(is).stringToken());
 
     return is;
 }
@@ -172,6 +148,7 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const geometricSurfacePatch& obj)
     os << nl;
 
     // Empty words are double-quoted so they are treated as 'string'
+    // and not simply lost
 
     os.writeQuoted(obj.name(), obj.name().empty()) << token::SPACE;
     os.writeQuoted(obj.geometricType(), obj.geometricType().empty());
@@ -191,17 +168,4 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const geometricSurfacePatch& obj)
 }
 
 
-// * * * * * * * * * * * * * * * Housekeeping  * * * * * * * * * * * * * * * //
-
-Foam::geometricSurfacePatch::geometricSurfacePatch
-(
-    const word& geometricType,
-    const word& name,
-    const label index
-)
-:
-    geometricSurfacePatch(name, index, geometricType)
-{}
-
-
 // ************************************************************************* //
diff --git a/src/OpenFOAM/meshes/Identifiers/surface/geometricSurfacePatch.H b/src/OpenFOAM/meshes/Identifiers/surface/geometricSurfacePatch.H
index 1b82302fe3d..855d237abeb 100644
--- a/src/OpenFOAM/meshes/Identifiers/surface/geometricSurfacePatch.H
+++ b/src/OpenFOAM/meshes/Identifiers/surface/geometricSurfacePatch.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -28,8 +28,8 @@ Class
     Foam::geometricSurfacePatch
 
 Description
-    The geometricSurfacePatch is like patchIdentifier but for surfaces.
-    Holds type, name and index.
+    Identifies a surface patch/zone by name and index,
+    with geometric type.
 
 SourceFiles
     geometricSurfacePatch.C
@@ -40,6 +40,7 @@ SourceFiles
 #define geometricSurfacePatch_H
 
 #include "surfZoneIdentifier.H"
+#include "stdFoam.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -54,13 +55,13 @@ class geometricSurfacePatch
 {
     // Private Data
 
-        //- Name of patch
+        //- Patch/zone name
         word name_;
 
-        //- Index of patch in boundary
+        //- Patch/zone index in meshed surface
         label index_;
 
-        //- Type name of patch
+        //- Patch/zone type
         word geometricType_;
 
 public:
@@ -90,8 +91,10 @@ public:
         //- Helper to convert identifier types as an operation
         struct fromIdentifier
         {
-            geometricSurfacePatch
-            operator()(const surfZoneIdentifier& ident) const
+            geometricSurfacePatch operator()
+            (
+                const surfZoneIdentifier& ident
+            ) const
             {
                 return geometricSurfacePatch(ident);
             }
@@ -110,13 +113,15 @@ public:
 
     // Constructors
 
-        //- Default construct, use index=0, name="patch"
+        //- Default construct.
+        //- Uses name="patch", index=0, type=""
         geometricSurfacePatch();
 
-        //- Construct null with specified index
+        //- Construct null with specified index.
+        //- Uses name="patch", type=""
         explicit geometricSurfacePatch(const label index);
 
-        //- Construct from mandatory components
+        //- Construct from mandatory components, type=""
         geometricSurfacePatch(const word& name, const label index);
 
         //- Construct from components
@@ -142,39 +147,39 @@ public:
     // Member Functions
 
         //- The patch/zone name
-        const word& name() const
+        const word& name() const noexcept
         {
             return name_;
         }
 
         //- Modifiable patch/zone name
-        word& name()
+        word& name() noexcept
         {
             return name_;
         }
 
-        //- The geometric type of the patch/zone
-        const word& geometricType() const
+        //- The index of this patch/zone in the surface mesh
+        label index() const noexcept
         {
-            return geometricType_;
+            return index_;
         }
 
-        //- Modifiable geometric type of the patch/zone
-        word& geometricType()
+        //- Modifiable index of this patch/zone in the surface mesh
+        label& index() noexcept
         {
-            return geometricType_;
+            return index_;
         }
 
-        //- The index of this patch/zone in the surface mesh
-        label index() const
+        //- The geometric type of the patch/zone
+        const word& geometricType() const noexcept
         {
-            return index_;
+            return geometricType_;
         }
 
-        //- Modifiable index of this patch/zone in the surface mesh
-        label& index()
+        //- Modifiable geometric type of the patch/zone
+        word& geometricType() noexcept
         {
-            return index_;
+            return geometricType_;
         }
 
         //- Write (geometricType) dictionary entry
@@ -197,7 +202,10 @@ public:
             const word& geometricType,
             const word& name,
             const label index
-        );
+        )
+        :
+            geometricSurfacePatch(name, index, geometricType)
+        {}
 
         //- Deprecated(2020-01) Write dictionary
         //  \deprecated(2020-01) - Write dictionary
diff --git a/src/OpenFOAM/meshes/Identifiers/surface/surfZoneIdentifier.C b/src/OpenFOAM/meshes/Identifiers/surface/surfZoneIdentifier.C
index a34658d289d..deb490c8a0f 100644
--- a/src/OpenFOAM/meshes/Identifiers/surface/surfZoneIdentifier.C
+++ b/src/OpenFOAM/meshes/Identifiers/surface/surfZoneIdentifier.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -29,36 +29,11 @@ License
 #include "surfZoneIdentifier.H"
 #include "dictionary.H"
 
-// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-static inline word readOptionalWord(Istream& is)
-{
-    token tok(is);
-
-    if (tok.isWord())
-    {
-        return tok.wordToken();
-    }
-    else
-    {
-        // Allow empty words
-        return word::validate(tok.stringToken());
-    }
-}
-
-} // End namespace Foam
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::surfZoneIdentifier::surfZoneIdentifier()
 :
-    name_(),
-    index_(0),
-    geometricType_()
+    surfZoneIdentifier(0)
 {}
 
 
@@ -112,13 +87,13 @@ Foam::surfZoneIdentifier::surfZoneIdentifier
 
 Foam::surfZoneIdentifier::surfZoneIdentifier
 (
-    const surfZoneIdentifier& p,
+    const surfZoneIdentifier& ident,
     const label index
 )
 :
-    name_(p.name()),
+    name_(ident.name_),
     index_(index),
-    geometricType_(p.geometricType())
+    geometricType_(ident.geometricType_)
 {}
 
 
@@ -126,7 +101,7 @@ Foam::surfZoneIdentifier::surfZoneIdentifier
 
 void Foam::surfZoneIdentifier::write(Ostream& os) const
 {
-    if (geometricType_.size())
+    if (!geometricType_.empty())
     {
         os.writeEntry("geometricType", geometricType_);
     }
@@ -164,8 +139,9 @@ bool Foam::operator!=
 
 Foam::Istream& Foam::operator>>(Istream& is, surfZoneIdentifier& obj)
 {
-    obj.name() = readOptionalWord(is);
-    obj.geometricType() = readOptionalWord(is);
+    // Also read "" for empty words
+    obj.name() = word::validate(token(is).stringToken());
+    obj.geometricType() = word::validate(token(is).stringToken());
 
     return is;
 }
@@ -178,6 +154,7 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const surfZoneIdentifier& obj)
     os << nl;
 
     // Empty words are double-quoted so they are treated as 'string'
+    // and not simply lost
 
     os.writeQuoted(obj.name(), obj.name().empty()) << token::SPACE;
     os.writeQuoted(obj.geometricType(), obj.geometricType().empty());
diff --git a/src/OpenFOAM/meshes/Identifiers/surface/surfZoneIdentifier.H b/src/OpenFOAM/meshes/Identifiers/surface/surfZoneIdentifier.H
index 670d164f4ac..4a94e7bd049 100644
--- a/src/OpenFOAM/meshes/Identifiers/surface/surfZoneIdentifier.H
+++ b/src/OpenFOAM/meshes/Identifiers/surface/surfZoneIdentifier.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -28,7 +28,8 @@ Class
     Foam::surfZoneIdentifier
 
 Description
-    Identifies a surface patch/zone by name, patch index and geometricType.
+    Identifies a surface patch/zone by name and index,
+    with optional geometric type.
 
 SeeAlso
     patchIdentifier
@@ -43,7 +44,6 @@ SourceFiles
 
 #include "word.H"
 #include "label.H"
-#include "stdFoam.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -61,14 +61,14 @@ class surfZoneIdentifier
 {
     // Private Data
 
-        //- Name of zone
+        //- Patch/zone name
         word name_;
 
-        //- Index of zone in surface mesh
+        //- Patch/zone index in meshed surface
         label index_;
 
-        //- Type name of zone
-        mutable word geometricType_;
+        //- Patch/zone type (optional)
+        word geometricType_;
 
 public:
 
@@ -103,7 +103,7 @@ public:
 
     // Constructors
 
-        //- Default construct, with index zero
+        //- Default construct. Uses name="", index=0
         surfZoneIdentifier();
 
         //- Construct null with specified index
@@ -131,7 +131,7 @@ public:
         //- Copy construct, resetting the index
         surfZoneIdentifier
         (
-            const surfZoneIdentifier& p,
+            const surfZoneIdentifier& ident,
             const label index
         );
 
diff --git a/src/OpenFOAM/meshes/Identifiers/zones/ZoneIDs.H b/src/OpenFOAM/meshes/Identifiers/zone/ZoneIDs.H
similarity index 100%
rename from src/OpenFOAM/meshes/Identifiers/zones/ZoneIDs.H
rename to src/OpenFOAM/meshes/Identifiers/zone/ZoneIDs.H
diff --git a/src/OpenFOAM/meshes/Identifiers/zone/zoneIdentifier.C b/src/OpenFOAM/meshes/Identifiers/zone/zoneIdentifier.C
new file mode 100644
index 00000000000..f304057a5f9
--- /dev/null
+++ b/src/OpenFOAM/meshes/Identifiers/zone/zoneIdentifier.C
@@ -0,0 +1,121 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "zoneIdentifier.H"
+#include "dictionary.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::zoneIdentifier::zoneIdentifier()
+:
+    name_(),
+    index_(0)
+{}
+
+
+Foam::zoneIdentifier::zoneIdentifier
+(
+    const word& name,
+    const label index
+)
+:
+    name_(name),
+    index_(index)
+{}
+
+
+Foam::zoneIdentifier::zoneIdentifier
+(
+    const word& name,
+    const label index,
+    const word& physicalType,
+    const wordList& inGroups
+)
+:
+    name_(name),
+    index_(index),
+    physicalType_(physicalType),
+    inGroups_(inGroups)
+{}
+
+
+Foam::zoneIdentifier::zoneIdentifier
+(
+    const word& name,
+    const dictionary& dict,
+    const label index
+)
+:
+    zoneIdentifier(name, index)
+{
+    dict.readIfPresent("physicalType", physicalType_);
+    dict.readIfPresent("inGroups", inGroups_);
+}
+
+
+Foam::zoneIdentifier::zoneIdentifier
+(
+    const zoneIdentifier& ident,
+    const label index
+)
+:
+    name_(ident.name_),
+    index_(index),
+    physicalType_(ident.physicalType_),
+    inGroups_(ident.inGroups_)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::zoneIdentifier::write(Ostream& os) const
+{
+    if (!physicalType_.empty())
+    {
+        os.writeEntry("physicalType", physicalType_);
+    }
+
+    if (!inGroups_.empty())
+    {
+        os.writeKeyword("inGroups");
+        // Flat output of list
+        inGroups_.writeList(os, 0) << token::END_STATEMENT << nl;
+    }
+}
+
+
+// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
+
+Foam::Ostream& Foam::operator<<(Ostream& os, const zoneIdentifier& ident)
+{
+    ident.write(os);
+    os.check(FUNCTION_NAME);
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/meshes/Identifiers/zone/zoneIdentifier.H b/src/OpenFOAM/meshes/Identifiers/zone/zoneIdentifier.H
new file mode 100644
index 00000000000..24f483b188e
--- /dev/null
+++ b/src/OpenFOAM/meshes/Identifiers/zone/zoneIdentifier.H
@@ -0,0 +1,198 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::zoneIdentifier
+
+Description
+    Identifies a mesh zone by name and index, with optional physical type
+    and group information.
+
+SeeAlso
+    patchIdentifier
+
+SourceFiles
+    zoneIdentifier.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef zoneIdentifier_H
+#define zoneIdentifier_H
+
+#include "wordList.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class dictionary;
+
+/*---------------------------------------------------------------------------*\
+                       Class zoneIdentifier Declaration
+\*---------------------------------------------------------------------------*/
+
+class zoneIdentifier
+{
+    // Private Data
+
+        //- Zone name
+        word name_;
+
+        //- Zone index in mesh
+        label index_;
+
+        //- Zone type (optional)
+        word physicalType_;
+
+        //- Groups to which the zone belongs (optional)
+        wordList inGroups_;
+
+public:
+
+    // Generated Methods
+
+        //- Copy construct
+        zoneIdentifier(const zoneIdentifier&) = default;
+
+        //- Copy assignment
+        zoneIdentifier& operator=(const zoneIdentifier&) = default;
+
+        //- Destructor
+        virtual ~zoneIdentifier() = default;
+
+
+    // Constructors
+
+        //- Default construct. Uses name="", index=0
+        zoneIdentifier();
+
+        //- Construct from mandatory components
+        zoneIdentifier(const word& name, const label index);
+
+        //- Construct from components
+        zoneIdentifier
+        (
+            const word& name,
+            const label index,
+            const word& physicalType,
+            const wordList& inGroups = wordList()
+        );
+
+        //- Construct from dictionary
+        zoneIdentifier
+        (
+            const word& name,
+            const dictionary& dict,
+            const label index
+        );
+
+        //- Copy construct, resetting the index
+        zoneIdentifier
+        (
+            const zoneIdentifier& ident,
+            const label index
+        );
+
+
+    // Member Functions
+
+        //- The zone name
+        const word& name() const noexcept
+        {
+            return name_;
+        }
+
+        //- Modifiable zone name
+        word& name() noexcept
+        {
+            return name_;
+        }
+
+        //- The index of this zone in the zone list
+        label index() const noexcept
+        {
+            return index_;
+        }
+
+        //- Modifiable index of this zone in the zone list
+        label& index() noexcept
+        {
+            return index_;
+        }
+
+        //- The (optional) type of the zone
+        const word& physicalType() const noexcept
+        {
+            return physicalType_;
+        }
+
+        //- Modifiable (optional) type of the zone
+        word& physicalType() noexcept
+        {
+            return physicalType_;
+        }
+
+        //- The (optional) groups that the zone belongs to
+        const wordList& inGroups() const noexcept
+        {
+            return inGroups_;
+        }
+
+        //- Modifiable (optional) groups that the zone belongs to
+        wordList& inGroups() noexcept
+        {
+            return inGroups_;
+        }
+
+        //- True if given name is in a group
+        bool inGroup(const word& name) const
+        {
+            return inGroups_.found(name);
+        }
+
+        //- Write (physicalType, inGroups) dictionary entries
+        //- (without surrounding braces)
+        void write(Ostream& os) const;
+};
+
+
+// Global Operators
+
+//- Write (physicalType, inGroups) dictionary entries
+//- (without surrounding braces)
+Ostream& operator<<(Ostream& os, const zoneIdentifier& p);
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/meshes/pointMesh/pointBoundaryMesh/pointBoundaryMesh.C b/src/OpenFOAM/meshes/pointMesh/pointBoundaryMesh/pointBoundaryMesh.C
index 2fee220290c..ef3bf292672 100644
--- a/src/OpenFOAM/meshes/pointMesh/pointBoundaryMesh/pointBoundaryMesh.C
+++ b/src/OpenFOAM/meshes/pointMesh/pointBoundaryMesh/pointBoundaryMesh.C
@@ -71,6 +71,16 @@ Foam::labelList Foam::pointBoundaryMesh::indices
 }
 
 
+Foam::labelList Foam::pointBoundaryMesh::indices
+(
+    const wordRes& matcher,
+    const bool useGroups
+) const
+{
+    return mesh()().boundaryMesh().indices(matcher, useGroups);
+}
+
+
 Foam::label Foam::pointBoundaryMesh::findPatchID(const word& patchName) const
 {
     return mesh()().boundaryMesh().findPatchID(patchName);
diff --git a/src/OpenFOAM/meshes/pointMesh/pointBoundaryMesh/pointBoundaryMesh.H b/src/OpenFOAM/meshes/pointMesh/pointBoundaryMesh/pointBoundaryMesh.H
index d6a470afcc4..969f46e480b 100644
--- a/src/OpenFOAM/meshes/pointMesh/pointBoundaryMesh/pointBoundaryMesh.H
+++ b/src/OpenFOAM/meshes/pointMesh/pointBoundaryMesh/pointBoundaryMesh.H
@@ -48,6 +48,7 @@ namespace Foam
 // Forward Declarations
 class pointMesh;
 class polyBoundaryMesh;
+class wordRes;
 
 /*---------------------------------------------------------------------------*\
                        Class pointBoundaryMesh Declaration
@@ -99,10 +100,14 @@ public:
             return mesh_;
         }
 
-        //- Find patch indices given a name
-        //  A no-op (returns empty list) for an empty key
+        //- Return (sorted) patch indices for all matches.
+        //  A no-op (returns empty list) for an empty matcher
         labelList indices(const wordRe& matcher, const bool useGroups) const;
 
+        //- Return (sorted) patch indices for all matches.
+        //  A no-op (returns empty list) for an empty matcher
+        labelList indices(const wordRes& matcher, const bool useGroups) const;
+
         //- Find patch index given a name
         //  A no-op (returns -1) for an empty patchName
         label findPatchID(const word& patchName) const;
diff --git a/src/OpenFOAM/meshes/polyMesh/polyBoundaryMesh/polyBoundaryMesh.C b/src/OpenFOAM/meshes/polyMesh/polyBoundaryMesh/polyBoundaryMesh.C
index 341c984bdcf..0fa9493ecd5 100644
--- a/src/OpenFOAM/meshes/polyMesh/polyBoundaryMesh/polyBoundaryMesh.C
+++ b/src/OpenFOAM/meshes/polyMesh/polyBoundaryMesh/polyBoundaryMesh.C
@@ -34,6 +34,7 @@ License
 #include "lduSchedule.H"
 #include "globalMeshData.H"
 #include "stringListOps.H"
+#include "DynamicList.H"
 #include "PtrListOps.H"
 #include "edgeHashes.H"
 
@@ -45,6 +46,67 @@ namespace Foam
 }
 
 
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+bool Foam::polyBoundaryMesh::hasGroupIDs() const
+{
+    if (groupIDsPtr_)
+    {
+        // Use existing cache
+        return !groupIDsPtr_->empty();
+    }
+
+    const polyPatchList& patches = *this;
+
+    for (const polyPatch& p : patches)
+    {
+        if (!p.inGroups().empty())
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
+void Foam::polyBoundaryMesh::calcGroupIDs() const
+{
+    if (groupIDsPtr_)
+    {
+        return;  // Or FatalError
+    }
+
+    groupIDsPtr_.reset(new HashTable<labelList>(16));
+    auto& groupLookup = *groupIDsPtr_;
+
+    const polyPatchList& patches = *this;
+
+    forAll(patches, patchi)
+    {
+        const wordList& groups = patches[patchi].inGroups();
+
+        for (const word& groupName : groups)
+        {
+            groupLookup(groupName).append(patchi);
+        }
+    }
+
+    // Remove groups that clash with patch names
+    forAll(patches, patchi)
+    {
+        if (groupLookup.erase(patches[patchi].name()))
+        {
+            WarningInFunction
+                << "Removed group '" << patches[patchi].name()
+                << "' which clashes with patch " << patchi
+                << " of the same name."
+                << endl;
+        }
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::polyBoundaryMesh::polyBoundaryMesh
@@ -186,7 +248,7 @@ void Foam::polyBoundaryMesh::clearAddressing()
 {
     neighbourEdgesPtr_.clear();
     patchIDPtr_.clear();
-    groupPatchIDsPtr_.clear();
+    groupIDsPtr_.clear();
 
     polyPatchList& patches = *this;
 
@@ -258,7 +320,7 @@ Foam::polyBoundaryMesh::neighbourEdges() const
     if (!neighbourEdgesPtr_)
     {
         neighbourEdgesPtr_.reset(new List<labelPairList>(size()));
-        List<labelPairList>& neighbourEdges = neighbourEdgesPtr_();
+        auto& neighbourEdges = *neighbourEdgesPtr_;
 
         // Initialize.
         label nEdgePairs = 0;
@@ -402,47 +464,12 @@ const Foam::labelList& Foam::polyBoundaryMesh::patchID() const
 const Foam::HashTable<Foam::labelList>&
 Foam::polyBoundaryMesh::groupPatchIDs() const
 {
-    if (!groupPatchIDsPtr_)
+    if (!groupIDsPtr_)
     {
-        groupPatchIDsPtr_.reset(new HashTable<labelList>(16));
-        auto& groupPatchIDs = *groupPatchIDsPtr_;
-
-        const polyBoundaryMesh& patches = *this;
-
-        forAll(patches, patchi)
-        {
-            const wordList& groups = patches[patchi].inGroups();
-
-            for (const word& groupName : groups)
-            {
-                auto iter = groupPatchIDs.find(groupName);
-
-                if (iter.found())
-                {
-                    (*iter).append(patchi);
-                }
-                else
-                {
-                    groupPatchIDs.insert(groupName, labelList(one{}, patchi));
-                }
-            }
-        }
-
-        // Remove patch names from patchGroups
-        forAll(patches, patchi)
-        {
-            if (groupPatchIDs.erase(patches[patchi].name()))
-            {
-                WarningInFunction
-                    << "Removed patchGroup '" << patches[patchi].name()
-                    << "' which clashes with patch " << patchi
-                    << " of the same name."
-                    << endl;
-            }
-        }
+        calcGroupIDs();
     }
 
-    return *groupPatchIDsPtr_;
+    return *groupIDsPtr_;
 }
 
 
@@ -452,7 +479,7 @@ void Foam::polyBoundaryMesh::setGroup
     const labelUList& patchIDs
 )
 {
-    groupPatchIDsPtr_.clear();
+    groupIDsPtr_.clear();
 
     polyPatchList& patches = *this;
 
@@ -461,12 +488,7 @@ void Foam::polyBoundaryMesh::setGroup
     // Add to specified patches
     for (const label patchi : patchIDs)
     {
-        polyPatch& pp = patches[patchi];
-
-        if (!pp.inGroup(groupName))
-        {
-            pp.inGroups().append(groupName);
-        }
+        patches[patchi].inGroups().appendUniq(groupName);
         donePatch[patchi] = true;
     }
 
@@ -475,21 +497,19 @@ void Foam::polyBoundaryMesh::setGroup
     {
         if (!donePatch[patchi])
         {
-            polyPatch& pp = patches[patchi];
+            wordList& groups = patches[patchi].inGroups();
 
-            label newI = 0;
-            if (pp.inGroup(groupName))
+            if (groups.found(groupName))
             {
-                wordList& groups = pp.inGroups();
-
+                label newi = 0;
                 forAll(groups, i)
                 {
                     if (groups[i] != groupName)
                     {
-                        groups[newI++] = groups[i];
+                        groups[newi++] = groups[i];
                     }
                 }
-                groups.setSize(newI);
+                groups.resize(newi);
             }
         }
     }
@@ -613,64 +633,105 @@ Foam::labelList Foam::polyBoundaryMesh::indices
         return labelList();
     }
 
-    DynamicList<label> patchIndices;
+    // Only check groups if requested and they exist
+    const bool checkGroups = (useGroups && this->hasGroupIDs());
+
+    labelHashSet ids;
 
     if (matcher.isPattern())
     {
-        patchIndices = PtrListOps::findMatching(*this, matcher);
-
-        // Only examine patch groups if requested and when they exist.
-        if (useGroups && !groupPatchIDs().empty())
+        if (checkGroups)
         {
-            const wordList groupNames
-            (
-                groupPatchIDs().tocKeys(matcher)
-            );
-
-            if (groupNames.size())
+            const auto& groupLookup = groupPatchIDs();
+            forAllConstIters(groupLookup, iter)
             {
-                labelHashSet groupIndices;
-
-                for (const word& grpName : groupNames)
+                if (matcher.match(iter.key()))
                 {
-                    // Hash the patch ids for the group
-                    groupIndices.insert( groupPatchIDs()[grpName] );
+                    // Hash ids associated with the group
+                    ids.insert(iter.val());
                 }
-
-                groupIndices.erase(patchIndices);  // Skip existing
-                patchIndices.append(groupIndices.sortedToc());
             }
         }
+
+        if (ids.empty())
+        {
+            return PtrListOps::findMatching(*this, matcher);
+        }
+        else
+        {
+            ids.insert(PtrListOps::findMatching(*this, matcher));
+        }
     }
     else
     {
         // Literal string.
-        // Special version of above for reduced memory footprint
+        // Special version of above for reduced memory footprint.
 
         const label patchId = PtrListOps::firstMatching(*this, matcher);
 
         if (patchId >= 0)
         {
-            patchIndices.append(patchId);
+            return labelList(one{}, patchId);
         }
-
-        // Only examine patch groups if requested and when they exist.
-        if (useGroups && !groupPatchIDs().empty())
+        else if (checkGroups)
         {
             const auto iter = groupPatchIDs().cfind(matcher);
 
             if (iter.found())
             {
-                // Hash the patch ids for the group
-                labelHashSet groupIndices(iter.val());
+                // Hash ids associated with the group
+                ids.insert(iter.val());
+            }
+        }
+    }
+
+    return ids.sortedToc();
+}
+
+
+Foam::labelList Foam::polyBoundaryMesh::indices
+(
+    const wordRes& matcher,
+    const bool useGroups
+) const
+{
+    if (matcher.empty())
+    {
+        return labelList();
+    }
+    else if (matcher.size() == 1)
+    {
+        return this->indices(matcher.first(), useGroups);
+    }
+
+    labelHashSet ids;
+
+    // Only check groups if requested and they exist
+    if (useGroups && this->hasGroupIDs())
+    {
+        ids.resize(2*this->size());
 
-                groupIndices.erase(patchIndices);  // Skip existing
-                patchIndices.append(groupIndices.sortedToc());
+        const auto& groupLookup = groupPatchIDs();
+        forAllConstIters(groupLookup, iter)
+        {
+            if (matcher.match(iter.key()))
+            {
+                // Hash ids associated with the group
+                ids.insert(iter.val());
             }
         }
     }
 
-    return patchIndices;
+    if (ids.empty())
+    {
+        return PtrListOps::findMatching(*this, matcher);
+    }
+    else
+    {
+        ids.insert(PtrListOps::findMatching(*this, matcher));
+    }
+
+    return ids.sortedToc();
 }
 
 
@@ -791,44 +852,59 @@ Foam::labelHashSet Foam::polyBoundaryMesh::patchSet
 ) const
 {
     const wordList allPatchNames(this->names());
-    labelHashSet ids(size());
+    labelHashSet ids(2*this->size());
 
-    for (const wordRe& patchName : patchNames)
+    // Only check groups if requested and they exist
+    const bool checkGroups = (useGroups && this->hasGroupIDs());
+
+    for (const wordRe& matcher : patchNames)
     {
-        // Treat the given patch names as wild-cards and search the set
-        // of all patch names for matches
-        labelList patchIndices = findStrings(patchName, allPatchNames);
-        ids.insert(patchIndices);
+        labelList matchIndices = findMatchingStrings(matcher, allPatchNames);
+        ids.insert(matchIndices);
+
+        bool missed = matchIndices.empty();
 
-        if (patchIndices.empty())
+        if (missed && checkGroups)
         {
-            if (useGroups)
+            // Check group names
+            if (matcher.isPattern())
             {
-                // Treat as group name or regex for group name
-
-                const wordList groupNames
-                (
-                    groupPatchIDs().tocKeys(patchName)
-                );
-
-                for (const word& grpName : groupNames)
+                forAllConstIters(groupPatchIDs(), iter)
                 {
-                    ids.insert( groupPatchIDs()[grpName] );
+                    if (matcher.match(iter.key()))
+                    {
+                        // Hash ids associated with the group
+                        ids.insert(iter.val());
+                        missed = false;
+                    }
                 }
+            }
+            else
+            {
+                const auto iter = groupPatchIDs().cfind(matcher);
 
-                if (groupNames.empty() && warnNotFound)
+                if (iter.found())
                 {
-                    WarningInFunction
-                        << "Cannot find any patch or group names matching "
-                        << patchName
-                        << endl;
+                    // Hash ids associated with the group
+                    ids.insert(iter.val());
+                    missed = false;
                 }
             }
-            else if (warnNotFound)
+        }
+
+        if (missed && warnNotFound)
+        {
+            if (checkGroups)
             {
                 WarningInFunction
-                    << "Cannot find any patch names matching " << patchName
-                    << endl;
+                    << "Cannot find any patch or group names matching "
+                    << matcher << endl;
+            }
+            else
+            {
+                WarningInFunction
+                    << "Cannot find any patch names matching "
+                    << matcher << endl;
             }
         }
     }
@@ -850,15 +926,15 @@ void Foam::polyBoundaryMesh::matchGroups
     // Current set of unmatched patches
     nonGroupPatches = labelHashSet(patchIDs);
 
-    const HashTable<labelList>& groupPatchIDs = this->groupPatchIDs();
-    forAllConstIters(groupPatchIDs, iter)
+    const HashTable<labelList>& groupLookup = this->groupPatchIDs();
+    forAllConstIters(groupLookup, iter)
     {
         // Store currently unmatched patches so we can restore
         labelHashSet oldNonGroupPatches(nonGroupPatches);
 
         // Match by deleting patches in group from the current set and seeing
         // if all have been deleted.
-        labelHashSet groupPatchSet(iter());
+        labelHashSet groupPatchSet(iter.val());
 
         label nMatch = nonGroupPatches.erase(groupPatchSet);
 
@@ -972,7 +1048,7 @@ bool Foam::polyBoundaryMesh::checkDefinition(const bool report) const
 
     bool hasError = false;
 
-    wordHashSet patchNames(2*size());
+    wordHashSet patchNames(2*this->size());
 
     forAll(bm, patchi)
     {
@@ -1072,7 +1148,7 @@ void Foam::polyBoundaryMesh::updateMesh()
 {
     neighbourEdgesPtr_.clear();
     patchIDPtr_.clear();
-    groupPatchIDsPtr_.clear();
+    groupIDsPtr_.clear();
 
     PstreamBuffers pBufs(Pstream::defaultCommsType);
 
diff --git a/src/OpenFOAM/meshes/polyMesh/polyBoundaryMesh/polyBoundaryMesh.H b/src/OpenFOAM/meshes/polyMesh/polyBoundaryMesh/polyBoundaryMesh.H
index 8f90c0f8f5f..db50590500e 100644
--- a/src/OpenFOAM/meshes/polyMesh/polyBoundaryMesh/polyBoundaryMesh.H
+++ b/src/OpenFOAM/meshes/polyMesh/polyBoundaryMesh/polyBoundaryMesh.H
@@ -52,6 +52,7 @@ namespace Foam
 // Forward Declarations
 class polyMesh;
 class wordRe;
+class wordRes;
 
 Ostream& operator<<(Ostream& os, const polyBoundaryMesh& pbm);
 
@@ -73,8 +74,8 @@ class polyBoundaryMesh
         //- Demand-driven: list of patch ids per face.
         mutable autoPtr<labelList> patchIDPtr_;
 
-        //- Demand-driven:
-        mutable autoPtr<HashTable<labelList>> groupPatchIDsPtr_;
+        //- Demand-driven: list of patch ids per group
+        mutable autoPtr<HashTable<labelList>> groupIDsPtr_;
 
         //- Demand-driven: edges of neighbouring patches
         mutable autoPtr<List<labelPairList>> neighbourEdgesPtr_;
@@ -85,6 +86,12 @@ class polyBoundaryMesh
         //- Calculate geometry for the patches (transformation tensors etc.)
         void calcGeometry();
 
+        //- Some patches have inGroup entries
+        bool hasGroupIDs() const;
+
+        //- Calculate group name to patch ids lookup
+        void calcGroupIDs() const;
+
         //- No copy construct
         polyBoundaryMesh(const polyBoundaryMesh&) = delete;
 
@@ -196,15 +203,23 @@ public:
         labelRange range(const label patchi) const;
 
 
-        //- Return patch indices for all matches.
-        //  Optionally matches patchGroups
-        //  A no-op (returns empty list) for an empty key
+        //- Return (sorted) patch indices for all matches.
+        //  Optionally matches patch groups.
+        //  A no-op (returns empty list) for an empty matcher
         labelList indices
         (
             const wordRe& matcher,
             const bool useGroups = true
         ) const;
 
+        //- Return (sorted) patch indices for all matches.
+        //  Optionally matches patch groups.
+        //  A no-op (returns empty list) for an empty matcher
+        labelList indices
+        (
+            const wordRes& matcher,
+            const bool useGroups = true
+        ) const;
 
         //- Return patch index for the first match, return -1 if not found
         //  A no-op (returns -1) for an empty key
diff --git a/src/OpenFOAM/meshes/polyMesh/polyPatches/polyPatch/polyPatch.H b/src/OpenFOAM/meshes/polyMesh/polyPatches/polyPatch/polyPatch.H
index ba7570e96d5..4b5c2d4486c 100644
--- a/src/OpenFOAM/meshes/polyMesh/polyPatches/polyPatch/polyPatch.H
+++ b/src/OpenFOAM/meshes/polyMesh/polyPatches/polyPatch/polyPatch.H
@@ -53,7 +53,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class polyBoundaryMesh;
 class polyPatch;
 class polyTopoChange;
@@ -71,7 +71,7 @@ class polyPatch
     public patchIdentifier,
     public primitivePatch
 {
-    // Private data
+    // Private Data
 
         //- Start label of this patch in the polyMesh face list
         label start_;
@@ -79,14 +79,11 @@ class polyPatch
         //- Reference to boundary mesh
         const polyBoundaryMesh& boundaryMesh_;
 
+        //- Demand-driven: face-cell addressing
+        mutable labelList::subList* faceCellsPtr_;
 
-        // Demand-driven private data
-
-            //- face-cell addressing
-            mutable labelList::subList* faceCellsPtr_;
-
-            //- Global edge addressing
-            mutable labelList* mePtr_;
+        //- Demand-driven: global edge addressing
+        mutable labelList* mePtr_;
 
 
 protected:
@@ -247,8 +244,8 @@ public:
             return autoPtr<polyPatch>::New(*this, bm);
         }
 
-        //- Construct and return a clone, resetting the face list
-        //  and boundary mesh
+        //- Construct and return a clone,
+        //- resetting the face list and boundary mesh
         virtual autoPtr<polyPatch> clone
         (
             const polyBoundaryMesh& bm,
@@ -260,8 +257,8 @@ public:
             return autoPtr<polyPatch>::New(*this, bm, index, newSize, newStart);
         }
 
-        //- Construct and return a clone, resetting the face list
-        //  and boundary mesh
+        //- Construct and return a clone,
+        //- resetting the face list and boundary mesh
         virtual autoPtr<polyPatch> clone
         (
             const polyBoundaryMesh& bm,
@@ -488,7 +485,7 @@ public:
         }
 
 
-    // Member operators
+    // Member Operators
 
         //- Assignment
         void operator=(const polyPatch&);
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/ZoneMesh/ZoneMesh.C b/src/OpenFOAM/meshes/polyMesh/zones/ZoneMesh/ZoneMesh.C
index 2084d567822..bca75b08447 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/ZoneMesh/ZoneMesh.C
+++ b/src/OpenFOAM/meshes/polyMesh/zones/ZoneMesh/ZoneMesh.C
@@ -28,7 +28,7 @@ License
 
 #include "ZoneMesh.H"
 #include "entry.H"
-#include "demandDrivenData.H"
+#include "DynamicList.H"
 #include "Pstream.H"
 #include "PtrListOps.H"
 
@@ -67,8 +67,8 @@ void Foam::ZoneMesh<ZoneType, MeshType>::calcZoneMap() const
             nObjects += zn.size();
         }
 
-        zoneMapPtr_ = new Map<label>(2*nObjects);
-        Map<label>& zm = *zoneMapPtr_;
+        zoneMapPtr_.reset(new Map<label>(2*nObjects));
+        auto& zm = *zoneMapPtr_;
 
         // Fill in objects of all zones into the map.
         // The key is the global object index, value is the zone index
@@ -90,6 +90,67 @@ void Foam::ZoneMesh<ZoneType, MeshType>::calcZoneMap() const
 }
 
 
+template<class ZoneType, class MeshType>
+bool Foam::ZoneMesh<ZoneType, MeshType>::hasGroupIDs() const
+{
+    if (groupIDsPtr_)
+    {
+        // Use existing cache
+        return !groupIDsPtr_->empty();
+    }
+
+    const PtrList<ZoneType>& zones = *this;
+
+    for (const ZoneType& zn : zones)
+    {
+        if (!zn.inGroups().empty())
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
+template<class ZoneType, class MeshType>
+void Foam::ZoneMesh<ZoneType, MeshType>::calcGroupIDs() const
+{
+    if (groupIDsPtr_)
+    {
+        return;  // Or FatalError
+    }
+
+    groupIDsPtr_.reset(new HashTable<labelList>(16));
+    auto& groupLookup = *groupIDsPtr_;
+
+    const PtrList<ZoneType>& zones = *this;
+
+    forAll(zones, zonei)
+    {
+        const wordList& groups = zones[zonei].inGroups();
+
+        for (const word& groupName : groups)
+        {
+            groupLookup(groupName).append(zonei);
+        }
+    }
+
+    // Remove groups that clash with zone names
+    forAll(zones, zonei)
+    {
+        if (groupLookup.erase(zones[zonei].name()))
+        {
+            WarningInFunction
+                << "Removed group '" << zones[zonei].name()
+                << "' which clashes with zone " << zonei
+                << " of the same name."
+                << endl;
+        }
+    }
+}
+
+
 template<class ZoneType, class MeshType>
 bool Foam::ZoneMesh<ZoneType, MeshType>::read()
 {
@@ -150,8 +211,7 @@ Foam::ZoneMesh<ZoneType, MeshType>::ZoneMesh
 :
     PtrList<ZoneType>(),
     regIOobject(io),
-    mesh_(mesh),
-    zoneMapPtr_(nullptr)
+    mesh_(mesh)
 {
     read();
 }
@@ -167,8 +227,7 @@ Foam::ZoneMesh<ZoneType, MeshType>::ZoneMesh
 :
     PtrList<ZoneType>(size),
     regIOobject(io),
-    mesh_(mesh),
-    zoneMapPtr_(nullptr)
+    mesh_(mesh)
 {
     // Optionally read contents, otherwise keep size
     read();
@@ -185,8 +244,7 @@ Foam::ZoneMesh<ZoneType, MeshType>::ZoneMesh
 :
     PtrList<ZoneType>(),
     regIOobject(io),
-    mesh_(mesh),
-    zoneMapPtr_(nullptr)
+    mesh_(mesh)
 {
     if (!read())
     {
@@ -311,28 +369,120 @@ const
 template<class ZoneType, class MeshType>
 Foam::labelList Foam::ZoneMesh<ZoneType, MeshType>::indices
 (
-    const wordRe& matcher
+    const wordRe& matcher,
+    const bool useGroups
 ) const
 {
     if (matcher.empty())
     {
         return labelList();
     }
-    return PtrListOps::findMatching(*this, matcher);
+
+    // Only check groups if requested and they exist
+    const bool checkGroups = (useGroups && this->hasGroupIDs());
+
+    labelHashSet ids;
+
+    if (checkGroups)
+    {
+        ids.resize(2*this->size());
+    }
+
+    if (matcher.isPattern())
+    {
+        if (checkGroups)
+        {
+            const auto& groupLookup = groupZoneIDs();
+            forAllConstIters(groupLookup, iter)
+            {
+                if (matcher.match(iter.key()))
+                {
+                    // Hash ids associated with the group
+                    ids.insert(iter.val());
+                }
+            }
+        }
+
+        if (ids.empty())
+        {
+            return PtrListOps::findMatching(*this, matcher);
+        }
+        else
+        {
+            ids.insert(PtrListOps::findMatching(*this, matcher));
+        }
+    }
+    else
+    {
+        // Literal string.
+        // Special version of above for reduced memory footprint
+
+        const label zoneId = PtrListOps::firstMatching(*this, matcher);
+
+        if (zoneId >= 0)
+        {
+            return labelList(one{}, zoneId);
+        }
+        else if (checkGroups)
+        {
+            const auto iter = groupZoneIDs().cfind(matcher);
+
+            if (iter.found())
+            {
+                // Hash ids associated with the group
+                ids.insert(iter.val());
+            }
+        }
+    }
+
+    return ids.sortedToc();
 }
 
 
 template<class ZoneType, class MeshType>
 Foam::labelList Foam::ZoneMesh<ZoneType, MeshType>::indices
 (
-    const wordRes& matcher
+    const wordRes& matcher,
+    const bool useGroups
 ) const
 {
     if (matcher.empty())
     {
         return labelList();
     }
-    return PtrListOps::findMatching(*this, matcher);
+    else if (matcher.size() == 1)
+    {
+        return this->indices(matcher.first(), useGroups);
+    }
+
+    labelHashSet ids;
+
+    // Only check groups if requested and they exist
+    if (useGroups && this->hasGroupIDs())
+    {
+        ids.resize(2*this->size());
+
+        const auto& groupLookup = groupZoneIDs();
+        forAllConstIters(groupLookup, iter)
+        {
+            if (matcher.match(iter.key()))
+            {
+                // Hash the ids associated with the group
+                ids.insert(iter.val());
+            }
+        }
+    }
+
+    if (ids.empty())
+    {
+        return PtrListOps::findMatching(*this, matcher);
+    }
+    else
+    {
+        ids.insert(PtrListOps::findMatching(*this, matcher));
+    }
+
+    return ids.sortedToc();
 }
 
 
@@ -477,29 +627,89 @@ Foam::bitSet Foam::ZoneMesh<ZoneType, MeshType>::selection
 template<class ZoneType, class MeshType>
 Foam::bitSet Foam::ZoneMesh<ZoneType, MeshType>::selection
 (
-    const wordRe& matcher
+    const wordRe& matcher,
+    const bool useGroups
 ) const
 {
     // matcher.empty() is handled by indices()
-    return this->selection(this->indices(matcher));
+    return this->selection(this->indices(matcher, useGroups));
 }
 
 
 template<class ZoneType, class MeshType>
 Foam::bitSet Foam::ZoneMesh<ZoneType, MeshType>::selection
 (
-    const wordRes& matcher
+    const wordRes& matcher,
+    const bool useGroups
 ) const
 {
     // matcher.empty() is handled by indices()
-    return this->selection(this->indices(matcher));
+    return this->selection(this->indices(matcher, useGroups));
+}
+
+
+template<class ZoneType, class MeshType>
+const Foam::HashTable<Foam::labelList>&
+Foam::ZoneMesh<ZoneType, MeshType>::groupZoneIDs() const
+{
+    if (!groupIDsPtr_)
+    {
+        calcGroupIDs();
+    }
+
+    return *groupIDsPtr_;
+}
+
+
+template<class ZoneType, class MeshType>
+void Foam::ZoneMesh<ZoneType, MeshType>::setGroup
+(
+    const word& groupName,
+    const labelUList& zoneIDs
+)
+{
+    groupIDsPtr_.clear();
+
+    PtrList<ZoneType>& zones = *this;
+
+    boolList doneZone(zones.size(), false);
+
+    // Add to specified zones
+    for (const label zonei : zoneIDs)
+    {
+        zones[zonei].inGroups().appendUniq(groupName);
+        doneZone[zonei] = true;
+    }
+
+    // Remove from other zones
+    forAll(zones, zonei)
+    {
+        if (!doneZone[zonei])
+        {
+            wordList& groups = zones[zonei].inGroups();
+
+            if (groups.found(groupName))
+            {
+                label newi = 0;
+                forAll(groups, i)
+                {
+                    if (groups[i] != groupName)
+                    {
+                        groups[newi++] = groups[i];
+                    }
+                }
+                groups.resize(newi);
+            }
+        }
+    }
 }
 
 
 template<class ZoneType, class MeshType>
 void Foam::ZoneMesh<ZoneType, MeshType>::clearAddressing()
 {
-    deleteDemandDrivenData(zoneMapPtr_);
+    zoneMapPtr_.clear();
+    groupIDsPtr_.clear();
 
     PtrList<ZoneType>& zones = *this;
 
@@ -524,16 +734,16 @@ bool Foam::ZoneMesh<ZoneType, MeshType>::checkDefinition
     const bool report
 ) const
 {
-    bool inError = false;
+    bool hasError = false;
 
     const PtrList<ZoneType>& zones = *this;
 
     for (const ZoneType& zn : zones)
     {
-        inError |= zn.checkDefinition(report);
+        hasError |= zn.checkDefinition(report);
     }
 
-    return inError;
+    return hasError;
 }
 
 
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/ZoneMesh/ZoneMesh.H b/src/OpenFOAM/meshes/polyMesh/zones/ZoneMesh/ZoneMesh.H
index e4c0960a1df..d96de5d2715 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/ZoneMesh/ZoneMesh.H
+++ b/src/OpenFOAM/meshes/polyMesh/zones/ZoneMesh/ZoneMesh.H
@@ -41,6 +41,7 @@ SourceFiles
 #include "regIOobject.H"
 #include "pointField.H"
 #include "Map.H"
+#include "HashSet.H"
 #include "PtrList.H"
 #include "bitSet.H"
 #include "wordRes.H"
@@ -72,8 +73,11 @@ class ZoneMesh
         //- Reference to mesh
         const MeshType& mesh_;
 
-        //- Map of zone labels for given element
-        mutable Map<label>* zoneMapPtr_;
+        //- Demand-driven: map of zone labels for given element
+        mutable autoPtr<Map<label>> zoneMapPtr_;
+
+        //- Demand-driven: list of zone ids per group
+        mutable autoPtr<HashTable<labelList>> groupIDsPtr_;
 
 
     // Private Member Functions
@@ -84,6 +88,12 @@ class ZoneMesh
         //- Create zone map
         void calcZoneMap() const;
 
+        //- Some zones have inGroup entries
+        bool hasGroupIDs() const;
+
+        //- Calculate group name to zone ids lookup
+        void calcGroupIDs() const;
+
         //- No copy construct
         ZoneMesh(const ZoneMesh&) = delete;
 
@@ -165,13 +175,23 @@ public:
         wordList sortedNames(const wordRes& matcher) const;
 
 
-        //- Return zone indices for all matches
+        //- Return (sorted) zone indices for all matches
+        //  Optionally matches zone groups.
         //  A no-op (returns empty list) for an empty matcher
-        labelList indices(const wordRe& matcher) const;
+        labelList indices
+        (
+            const wordRe& matcher,
+            const bool useGroups = true
+        ) const;
 
-        //- Return zone indices for all matches
+        //- Return (sorted) zone indices for all matches
+        //  Optionally matches zone groups.
         //  A no-op (returns empty list) for an empty matcher
-        labelList indices(const wordRes& matcher) const;
+        labelList indices
+        (
+            const wordRes& matcher,
+            const bool useGroups = true
+        ) const;
 
         //- Zone index for the first match, return -1 if not found
         //  A no-op (returns -1) for an empty key
@@ -204,15 +224,30 @@ public:
         //- specification as a bitSet.
         //  The bitSet is empty (zero-size) if there are no elements matched
         //  anywhere.
+        //  Optionally matches zoneGroups.
         //  A no-op (returns empty bitSet) for an empty matcher
-        bitSet selection(const wordRe& matcher) const;
+        bitSet selection
+        (
+            const wordRe& matcher,
+            const bool useGroups = true
+        ) const;
 
         //- Return all elements (cells, faces, points) that match the zone
         //- specification as a bitSet.
         //  The bitSet is empty (zero-size) if there are no elements matched
         //  anywhere.
         //  A no-op (returns empty bitSet) for an empty matcher
-        bitSet selection(const wordRes& matcher) const;
+        bitSet selection
+        (
+            const wordRes& matcher,
+            const bool useGroups = true
+        ) const;
+
+        //- The zone indices per zone group
+        const HashTable<labelList>& groupZoneIDs() const;
+
+        //- Set/add group with zones
+        void setGroup(const word& groupName, const labelUList& zoneIDs);
 
 
         //- Clear addressing
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/cellZone/cellZone.C b/src/OpenFOAM/meshes/polyMesh/zones/cellZone/cellZone.C
index f8440be35fc..a8f5d287c68 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/cellZone/cellZone.C
+++ b/src/OpenFOAM/meshes/polyMesh/zones/cellZone/cellZone.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2017-2018 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -32,7 +32,6 @@ License
 #include "polyMesh.H"
 #include "primitiveMesh.H"
 #include "IOstream.H"
-#include "demandDrivenData.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -133,12 +132,6 @@ Foam::label Foam::cellZone::whichCell(const label globalCellID) const
 }
 
 
-const Foam::cellZoneMesh& Foam::cellZone::zoneMesh() const
-{
-    return zoneMesh_;
-}
-
-
 bool Foam::cellZone::checkDefinition(const bool report) const
 {
     return zone::checkDefinition(zoneMesh_.mesh().nCells(), report);
@@ -147,12 +140,13 @@ bool Foam::cellZone::checkDefinition(const bool report) const
 
 void Foam::cellZone::writeDict(Ostream& os) const
 {
-    os  << nl << name() << nl << token::BEGIN_BLOCK << nl
-        << "    type " << type() << token::END_STATEMENT << nl;
+    os.beginBlock(name());
 
+    os.writeEntry("type", type());
+    zoneIdentifier::write(os);
     writeEntry(this->labelsName, os);
 
-    os  << token::END_BLOCK << endl;
+    os.endBlock();
 }
 
 
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/cellZone/cellZone.H b/src/OpenFOAM/meshes/polyMesh/zones/cellZone/cellZone.H
index 5737eadbec0..857e878b215 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/cellZone/cellZone.H
+++ b/src/OpenFOAM/meshes/polyMesh/zones/cellZone/cellZone.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2018 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -52,7 +52,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class cellZone;
 Ostream& operator<<(Ostream& os, const cellZone& zn);
 
@@ -64,15 +64,13 @@ class cellZone
 :
     public zone
 {
-protected:
-
-    // Protected data
+    // Private Data
 
         //- Reference to zone list
         const cellZoneMesh& zoneMesh_;
 
 
-    // Protected Member Functions
+    // Private Member Functions
 
         //- No copy construct
         cellZone(const cellZone&) = delete;
@@ -83,6 +81,7 @@ public:
     // Static Data Members
 
         //- The name associated with the zone-labels dictionary entry
+        //- ("cellLabels")
         static const char * const labelsName;
 
 
@@ -110,7 +109,12 @@ public:
     // Constructors
 
         //- Construct an empty zone
-        cellZone(const word& name, const label index, const cellZoneMesh& zm);
+        cellZone
+        (
+            const word& name,
+            const label index,
+            const cellZoneMesh& zm
+        );
 
         //- Construct from components
         cellZone
@@ -198,12 +202,15 @@ public:
 
     // Member Functions
 
+        //- Return reference to the zone mesh
+        const cellZoneMesh& zoneMesh() const noexcept
+        {
+            return zoneMesh_;
+        }
+
         //- Helper function to re-direct to zone::localID(...)
         label whichCell(const label globalCellID) const;
 
-        //- Return zoneMesh reference
-        const cellZoneMesh& zoneMesh() const;
-
         //- Check zone definition. Return true if in error.
         virtual bool checkDefinition(const bool report = false) const;
 
@@ -220,7 +227,7 @@ public:
 
     // Member Operators
 
-        //- Assign to zone, clearing demand-driven data
+        //- Assign addressing, clearing demand-driven data
         void operator=(const cellZone& zn);
 
         //- Assign addressing, clearing demand-driven data
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/faceZone/faceZone.C b/src/OpenFOAM/meshes/polyMesh/zones/faceZone/faceZone.C
index a155d66278f..a85ebc84006 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/faceZone/faceZone.C
+++ b/src/OpenFOAM/meshes/polyMesh/zones/faceZone/faceZone.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -60,7 +60,7 @@ void Foam::faceZone::setFlipMap(const bool val)
     {
         // Avoid copying old values on resize
         flipMap_.clear();
-        flipMap_.setSize(this->size(), val);
+        flipMap_.resize(this->size(), val);
     }
 }
 
@@ -166,28 +166,27 @@ void Foam::faceZone::calcCellLayers() const
 
 void Foam::faceZone::checkAddressing() const
 {
-    if (size() != flipMap_.size())
+    const labelList& addr = *this;
+
+    if (addr.size() != flipMap_.size())
     {
         FatalErrorInFunction
-            << "Size of addressing: " << size()
+            << "Size of addressing: " << addr.size()
             << " size of flip map: " << flipMap_.size()
             << abort(FatalError);
     }
 
-    const labelList& mf = *this;
-
     // Note: nFaces, nCells might not be set yet on mesh so use owner size
     const label nFaces = zoneMesh().mesh().faceOwner().size();
 
-    bool hasWarned = false;
-    forAll(mf, i)
+    for (const label facei : addr)
     {
-        if (!hasWarned && (mf[i] < 0 || mf[i] >= nFaces))
+        if (facei < 0 || facei >= nFaces)
         {
             WarningInFunction
-                << "Illegal face index " << mf[i] << " outside range 0.."
-                << nFaces-1 << endl;
-            hasWarned = true;
+                << "Illegal face index " << facei
+                << " outside range 0.." << nFaces-1 << endl;
+            break;  // Only report once
         }
     }
 }
@@ -229,7 +228,7 @@ Foam::faceZone::faceZone
     slaveCellsPtr_(nullptr),
     mePtr_(nullptr)
 {
-    flipMap_.setSize(size(), flipMapValue);
+    flipMap_.resize(size(), flipMapValue);
     checkAddressing();
 }
 
@@ -251,7 +250,7 @@ Foam::faceZone::faceZone
     slaveCellsPtr_(nullptr),
     mePtr_(nullptr)
 {
-    flipMap_.setSize(size(), flipMapValue);
+    flipMap_.resize(size(), flipMapValue);
     checkAddressing();
 }
 
@@ -370,12 +369,6 @@ Foam::faceZone::~faceZone()
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-const Foam::faceZoneMesh& Foam::faceZone::zoneMesh() const
-{
-    return zoneMesh_;
-}
-
-
 Foam::label Foam::faceZone::whichFace(const label globalFaceID) const
 {
     return zone::localID(globalFaceID);
@@ -491,11 +484,12 @@ void Foam::faceZone::updateMesh(const mapPolyMesh& mpm)
     boolList newFlipMap(flipMap_.size());
     label nFaces = 0;
 
+    const labelList& addr = *this;
     const labelList& faceMap = mpm.reverseFaceMap();
 
-    forAll(*this, i)
+    forAll(addr, i)
     {
-        const label facei = operator[](i);
+        const label facei = addr[i];
 
         if (faceMap[facei] >= 0)
         {
@@ -531,11 +525,14 @@ bool Foam::faceZone::checkParallelSync(const bool report) const
     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     {
+        const labelList& addr = *this;
+
         boolList neiZoneFace(mesh.nBoundaryFaces(), false);
         boolList neiZoneFlip(mesh.nBoundaryFaces(), false);
-        forAll(*this, i)
+
+        forAll(addr, i)
         {
-            const label facei = operator[](i);
+            const label facei = addr[i];
 
             if (!mesh.isInternalFace(facei))
             {
@@ -548,9 +545,9 @@ bool Foam::faceZone::checkParallelSync(const bool report) const
         boolList myZoneFlip(neiZoneFlip);
         syncTools::swapBoundaryFaceList(mesh, neiZoneFlip);
 
-        forAll(*this, i)
+        forAll(addr, i)
         {
-            const label facei = operator[](i);
+            const label facei = addr[i];
             const label patchi = bm.whichPatch(facei);
 
             if (patchi != -1 && bm[patchi].coupled())
@@ -626,13 +623,14 @@ void Foam::faceZone::write(Ostream& os) const
 
 void Foam::faceZone::writeDict(Ostream& os) const
 {
-    os  << nl << name() << nl << token::BEGIN_BLOCK << nl
-        << "    type " << type() << token::END_STATEMENT << nl;
+    os.beginBlock(name());
 
+    os.writeEntry("type", type());
+    zoneIdentifier::write(os);
     writeEntry(this->labelsName, os);
     flipMap().writeEntry("flipMap", os);
 
-    os  << token::END_BLOCK << endl;
+    os.endBlock();
 }
 
 
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/faceZone/faceZone.H b/src/OpenFOAM/meshes/polyMesh/zones/faceZone/faceZone.H
index 0e6c9460c0c..6bdb8c65d48 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/faceZone/faceZone.H
+++ b/src/OpenFOAM/meshes/polyMesh/zones/faceZone/faceZone.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2018 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -53,7 +53,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class mapPolyMesh;
 class faceZone;
 Ostream& operator<<(Ostream& os, const faceZone& zn);
@@ -67,46 +67,32 @@ class faceZone
 :
     public zone
 {
-    // Private Member Functions
-
-        //- Set flip-map to uniform value
-        void setFlipMap(const bool val);
-
-        //- No copy construct
-        faceZone(const faceZone&) = delete;
-
-        //- No copy assignment
-        void operator=(const faceZone&) = delete;
-
-
-protected:
-
-    // Protected Data
+    // Private Data
 
         //- Flip map for all faces in the zone.
-        //  Use true if the face needs flipping for the correct orientation.
+        //  True if the face needs flipping for the correct orientation.
         boolList flipMap_;
 
         //- Reference to zone list
         const faceZoneMesh& zoneMesh_;
 
-
-      // Demand-driven data
-
-        //- Primitive patch made out of correctly flipped faces
+        //- Demand-driven: Primitive patch of correctly flipped faces
         mutable primitiveFacePatch* patchPtr_;
 
-        //- Master cell layer
+        //- Demand-driven: Master cell layer
         mutable labelList* masterCellsPtr_;
 
-        //- Slave cell layer
+        //- Demand-driven: Slave cell layer
         mutable labelList* slaveCellsPtr_;
 
-        //- Global edge addressing
+        //- Demand-driven: Global edge addressing
         mutable labelList* mePtr_;
 
 
-    // Protected Member Functions
+    // Private Member Functions
+
+        //- Set flip-map to uniform value
+        void setFlipMap(const bool val);
 
         //- Build primitive patch
         void calcFaceZonePatch() const;
@@ -118,11 +104,19 @@ protected:
         void checkAddressing() const;
 
 
+        //- No copy construct
+        faceZone(const faceZone&) = delete;
+
+        //- No copy assignment
+        void operator=(const faceZone&) = delete;
+
+
 public:
 
     // Static Data Members
 
         //- The name associated with the zone-labels dictionary entry
+        //- ("faceLabels")
         static const char * const labelsName;
 
 
@@ -150,7 +144,12 @@ public:
     // Constructors
 
         //- Construct an empty zone
-        faceZone(const word& name, const label index, const faceZoneMesh& zm);
+        faceZone
+        (
+            const word& name,
+            const label index,
+            const faceZoneMesh& zm
+        );
 
         //- Construct from components with uniform flip map value
         faceZone
@@ -265,11 +264,14 @@ public:
 
     // Member Functions
 
-        //- Return zoneMesh reference
-        const faceZoneMesh& zoneMesh() const;
+        //- Return reference to the zone mesh
+        const faceZoneMesh& zoneMesh() const noexcept
+        {
+            return zoneMesh_;
+        }
 
         //- Return face flip map
-        const boolList& flipMap() const
+        const boolList& flipMap() const noexcept
         {
             return flipMap_;
         }
@@ -281,7 +283,7 @@ public:
         const primitiveFacePatch& operator()() const;
 
 
-      // Addressing into mesh
+    // Addressing into mesh
 
         //- Return labels of master cells (cells next to the master face
         //- zone in the prescribed direction)
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/pointZone/pointZone.C b/src/OpenFOAM/meshes/polyMesh/zones/pointZone/pointZone.C
index b659f277b17..a6a4a09e1d4 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/pointZone/pointZone.C
+++ b/src/OpenFOAM/meshes/polyMesh/zones/pointZone/pointZone.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2018 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,7 +31,6 @@ License
 #include "pointZoneMesh.H"
 #include "polyMesh.H"
 #include "primitiveMesh.H"
-#include "demandDrivenData.H"
 #include "syncTools.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -127,12 +126,6 @@ Foam::pointZone::pointZone
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-const Foam::pointZoneMesh& Foam::pointZone::zoneMesh() const
-{
-    return zoneMesh_;
-}
-
-
 Foam::label Foam::pointZone::whichPoint(const label globalPointID) const
 {
     return zone::localID(globalPointID);
@@ -149,7 +142,7 @@ bool Foam::pointZone::checkParallelSync(const bool report) const
 {
     const polyMesh& mesh = zoneMesh().mesh();
 
-    labelList maxZone(mesh.nPoints(), -1);
+    labelList maxZone(mesh.nPoints(), label(-1));
     labelList minZone(mesh.nPoints(), labelMax);
 
     const labelList& addr = *this;
@@ -162,7 +155,7 @@ bool Foam::pointZone::checkParallelSync(const bool report) const
     syncTools::syncPointList(mesh, maxZone, maxEqOp<label>(), label(-1));
     syncTools::syncPointList(mesh, minZone, minEqOp<label>(), labelMax);
 
-    bool error = false;
+    bool hasError = false;
 
     forAll(maxZone, pointi)
     {
@@ -176,7 +169,8 @@ bool Foam::pointZone::checkParallelSync(const bool report) const
          && (maxZone[pointi] != minZone[pointi])
         )
         {
-            if (report && !error)
+            hasError = true;
+            if (report)
             {
                 Info<< " ***Problem with pointZone " << index()
                     << " named " << name()
@@ -190,22 +184,23 @@ bool Foam::pointZone::checkParallelSync(const bool report) const
                     << "(suppressing further warnings)"
                     << endl;
             }
-            error = true;
+            break;  // Only report once
         }
     }
 
-    return error;
+    return hasError;
 }
 
 
 void Foam::pointZone::writeDict(Ostream& os) const
 {
-    os  << nl << name_ << nl << token::BEGIN_BLOCK << nl
-        << "    type " << type() << token::END_STATEMENT << nl;
+    os.beginBlock(name());
 
+    os.writeEntry("type", type());
+    zoneIdentifier::write(os);
     writeEntry(this->labelsName, os);
 
-    os  << token::END_BLOCK << endl;
+    os.endBlock();
 }
 
 
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/pointZone/pointZone.H b/src/OpenFOAM/meshes/polyMesh/zones/pointZone/pointZone.H
index 3c8973307e0..0b883e84130 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/pointZone/pointZone.H
+++ b/src/OpenFOAM/meshes/polyMesh/zones/pointZone/pointZone.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2017-2018 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -54,7 +54,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class pointZone;
 Ostream& operator<<(Ostream& os, const pointZone& zn);
 
@@ -67,15 +67,13 @@ class pointZone
 :
     public zone
 {
-protected:
-
-    // Protected Data
+    // Private Data
 
         //- Reference to zone list
         const pointZoneMesh& zoneMesh_;
 
 
-    // Protected Member Functions
+    // Private Member Functions
 
         //- No copy construct
         pointZone(const pointZone&) = delete;
@@ -86,6 +84,7 @@ public:
     // Static Data Members
 
         //- The name associated with the zone-labels dictionary entry
+        //- ("pointLabels")
         static const char * const labelsName;
 
 
@@ -113,7 +112,12 @@ public:
     // Constructors
 
         //- Construct an empty zone
-        pointZone(const word& name, const label index, const pointZoneMesh& zm);
+        pointZone
+        (
+            const word& name,
+            const label index,
+            const pointZoneMesh& zm
+        );
 
         //- Construct from components
         pointZone
@@ -170,7 +174,7 @@ public:
         }
 
         //- Construct and return a clone, resetting the point list
-        //  and zone mesh
+        //- and zone mesh
         virtual autoPtr<pointZone> clone
         (
             const pointZoneMesh& zm,
@@ -201,8 +205,11 @@ public:
 
     // Member Functions
 
-        //- Return zoneMesh reference
-        const pointZoneMesh& zoneMesh() const;
+        //- Return reference to the zone mesh
+        const pointZoneMesh& zoneMesh() const noexcept
+        {
+            return zoneMesh_;
+        }
 
         //- Helper function to re-direct to zone::localID(...)
         label whichPoint(const label globalPointID) const;
@@ -224,7 +231,7 @@ public:
 
     // Member Operators
 
-        //- Assign to zone, clearing demand-driven data
+        //- Assign addressing, clearing demand-driven data
         void operator=(const pointZone& zn);
 
         //- Assign addressing, clearing demand-driven data
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/zone/zone.C b/src/OpenFOAM/meshes/polyMesh/zones/zone/zone.C
index 20167b5983c..77190fef48a 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/zone/zone.C
+++ b/src/OpenFOAM/meshes/polyMesh/zones/zone/zone.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,9 +27,10 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "zone.H"
+#include "dictionary.H"
+#include "HashSet.H"
 #include "IOstream.H"
 #include "demandDrivenData.H"
-#include "HashSet.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -39,51 +40,20 @@ namespace Foam
 }
 
 
-// * * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * //
-
-const Foam::Map<Foam::label>& Foam::zone::lookupMap() const
-{
-    if (!lookupMapPtr_)
-    {
-        calcLookupMap();
-    }
-
-    return *lookupMapPtr_;
-}
-
-
-void Foam::zone::calcLookupMap() const
-{
-    DebugInFunction << "Calculating lookup map" << endl;
-
-    if (lookupMapPtr_)
-    {
-        FatalErrorInFunction
-            << "Lookup map already calculated" << nl
-            << abort(FatalError);
-    }
-
-    const labelList& addr = *this;
-
-    lookupMapPtr_ = new Map<label>(2*addr.size());
-    Map<label>& lm = *lookupMapPtr_;
-
-    forAll(addr, i)
-    {
-        lm.insert(addr[i], i);
-    }
-
-    DebugInfo << "Finished calculating lookup map" << endl;
-}
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
+Foam::zone::zone()
+:
+    zoneIdentifier(),
+    labelList(),
+    lookupMapPtr_(nullptr)
+{}
 
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::zone::zone(const word& name, const label index)
 :
+    zoneIdentifier(name, index),
     labelList(),
-    name_(name),
-    index_(index),
     lookupMapPtr_(nullptr)
 {}
 
@@ -95,9 +65,8 @@ Foam::zone::zone
     const label index
 )
 :
+    zoneIdentifier(name, index),
     labelList(addr),
-    name_(name),
-    index_(index),
     lookupMapPtr_(nullptr)
 {}
 
@@ -109,9 +78,8 @@ Foam::zone::zone
     const label index
 )
 :
+    zoneIdentifier(name, index),
     labelList(std::move(addr)),
-    name_(name),
-    index_(index),
     lookupMapPtr_(nullptr)
 {}
 
@@ -124,10 +92,7 @@ Foam::zone::zone
     const label index
 )
 :
-    labelList(dict.lookup(labelsName)),
-    name_(name),
-    index_(index),
-    lookupMapPtr_(nullptr)
+    zone(name, dict.get<labelList>(labelsName), index)
 {}
 
 
@@ -138,10 +103,7 @@ Foam::zone::zone
     const label index
 )
 :
-    labelList(addr),
-    name_(origZone.name()),
-    index_(index),
-    lookupMapPtr_(nullptr)
+    zone(origZone.name(), addr, index)
 {}
 
 
@@ -152,10 +114,7 @@ Foam::zone::zone
     const label index
 )
 :
-    labelList(std::move(addr)),
-    name_(origZone.name()),
-    index_(index),
-    lookupMapPtr_(nullptr)
+    zone(origZone.name(), std::move(addr), index)
 {}
 
 
@@ -169,9 +128,30 @@ Foam::zone::~zone()
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-Foam::label Foam::zone::localID(const label globalCellID) const
+const Foam::Map<Foam::label>& Foam::zone::lookupMap() const
+{
+    if (!lookupMapPtr_)
+    {
+        DebugInFunction << "Calculating lookup map" << endl;
+
+        const labelList& addr = *this;
+
+        lookupMapPtr_ = new Map<label>(2*addr.size());
+        auto& lm = *lookupMapPtr_;
+
+        forAll(addr, i)
+        {
+            lm.insert(addr[i], i);
+        }
+    }
+
+    return *lookupMapPtr_;
+}
+
+
+Foam::label Foam::zone::localID(const label globalID) const
 {
-    return lookupMap().lookup(globalCellID, -1);
+    return lookupMap().lookup(globalID, -1);
 }
 
 
@@ -199,7 +179,7 @@ bool Foam::zone::checkDefinition(const label maxSize, const bool report) const
             if (report)
             {
                 SeriousErrorInFunction
-                    << "Zone " << name_
+                    << "Zone " << this->name()
                     << " contains invalid index label " << idx << nl
                     << "Valid index labels are 0.."
                     << maxSize-1 << endl;
@@ -215,7 +195,7 @@ bool Foam::zone::checkDefinition(const label maxSize, const bool report) const
             if (report)
             {
                 WarningInFunction
-                    << "Zone " << name_
+                    << "Zone " << this->name()
                     << " contains duplicate index label " << idx << endl;
             }
         }
@@ -227,7 +207,7 @@ bool Foam::zone::checkDefinition(const label maxSize, const bool report) const
 
 void Foam::zone::write(Ostream& os) const
 {
-    os  << nl << name_
+    os  << nl << this->name()
         << nl << static_cast<const labelList&>(*this);
 }
 
diff --git a/src/OpenFOAM/meshes/polyMesh/zones/zone/zone.H b/src/OpenFOAM/meshes/polyMesh/zones/zone/zone.H
index d82b67199fc..d211b3f64c2 100644
--- a/src/OpenFOAM/meshes/polyMesh/zones/zone/zone.H
+++ b/src/OpenFOAM/meshes/polyMesh/zones/zone/zone.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -41,9 +41,9 @@ SourceFiles
 #ifndef zone_H
 #define zone_H
 
+#include "zoneIdentifier.H"
 #include "labelList.H"
 #include "typeInfo.H"
-#include "dictionary.H"
 #include "Map.H"
 #include "pointFieldFwd.H"
 
@@ -52,9 +52,9 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declaration of friend functions and operators
-
+// Forward Declarations
 class zone;
+class dictionary;
 Ostream& operator<<(Ostream& os, const zone& zn);
 
 /*---------------------------------------------------------------------------*\
@@ -63,37 +63,15 @@ Ostream& operator<<(Ostream& os, const zone& zn);
 
 class zone
 :
+    public zoneIdentifier,
     public labelList
 {
-    // Private Member Functions
-
-        //- No copy construct
-        zone(const zone&) = delete;
-
-
-protected:
-
-    // Protected data
+    // Private Data
 
-        //- Name of zone
-        word name_;
-
-        //- Index of zone
-        label index_;
-
-
-      // Demand-driven private data
-
-        //- Map of labels in zone for fast location lookup
+        //- Demand-driven: map of labels in zone for fast location lookup
         mutable Map<label>* lookupMapPtr_;
 
 
-    // Protected Member Functions
-
-        //- Construct the look-up map
-        void calcLookupMap() const;
-
-
 public:
 
     //- Runtime type information
@@ -102,6 +80,9 @@ public:
 
     // Constructors
 
+        //- Default construct
+        zone();
+
         //- Construct an empty zone
         zone(const word& name, const label index);
 
@@ -155,32 +136,13 @@ public:
 
     // Member Functions
 
-        //- Return name
-        const word& name() const
-        {
-            return name_;
-        }
-
-        //- Map storing the local index for every global index.  Used to find
-        //  the index of the item in the zone from the known global index. If
-        //  the item is not in the zone, returns -1
-        label localID(const label globalID) const;
-
-        //- Return the index of this zone in zone list
-        label index() const
-        {
-            return index_;
-        }
-
-        //- Return the index of this zone in zone list
-        label& index()
-        {
-            return index_;
-        }
-
         //- Return a reference to the look-up map
         const Map<label>& lookupMap() const;
 
+        //- Lookup local address in zone for given global index.
+        //  \return the local address, or -1 if the item is not in the zone
+        label localID(const label globalID) const;
+
         //- Clear addressing
         virtual void clearAddressing();
 
diff --git a/src/OpenFOAM/primitives/strings/lists/stringListOps.H b/src/OpenFOAM/primitives/strings/lists/stringListOps.H
index e4c1d56d8f2..376293565ae 100644
--- a/src/OpenFOAM/primitives/strings/lists/stringListOps.H
+++ b/src/OpenFOAM/primitives/strings/lists/stringListOps.H
@@ -53,6 +53,16 @@ SourceFiles
 
 namespace Foam
 {
+    //- Find first list item that matches, -1 on failure
+    template<class UnaryMatchPredicate, class StringType>
+    label firstMatchingString
+    (
+        const UnaryMatchPredicate& matcher,
+        const UList<StringType>& input,
+        const bool invert=false
+    );
+
+
     //- Extract list indices for all matches.
     //  The unary match predicate has the following signature:
     //  \code
diff --git a/src/OpenFOAM/primitives/strings/lists/stringListOpsTemplates.C b/src/OpenFOAM/primitives/strings/lists/stringListOpsTemplates.C
index 5e3f6ddf462..8c144db26ea 100644
--- a/src/OpenFOAM/primitives/strings/lists/stringListOpsTemplates.C
+++ b/src/OpenFOAM/primitives/strings/lists/stringListOpsTemplates.C
@@ -28,6 +28,28 @@ License
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+template<class UnaryMatchPredicate, class StringType>
+Foam::label Foam::firstMatchingString
+(
+    const UnaryMatchPredicate& matcher,
+    const UList<StringType>& input,
+    const bool invert
+)
+{
+    const label len = input.size();
+
+    for (label i=0; i < len; ++i)
+    {
+        if (matcher(input[i]) ? !invert : invert)
+        {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+
 template<class UnaryMatchPredicate, class StringType>
 Foam::labelList Foam::findMatchingStrings
 (
diff --git a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
index da7962e51b1..96fef01048a 100644
--- a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
+++ b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.C
@@ -38,6 +38,31 @@ namespace Foam
     defineTypeNameAndDebug(faBoundaryMesh, 0);
 }
 
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+// bool Foam::faBoundaryMesh::hasGroupIDs() const
+// {
+//     /// if (groupIDsPtr_)
+//     /// {
+//     ///     // Use existing cache
+//     ///     return !groupIDsPtr_->empty();
+//     /// }
+//
+//     const faPatchList& patches = *this;
+//
+//     for (const faPatch& p : patches)
+//     {
+//         if (!p.inGroups().empty())
+//         {
+//             return true;
+//         }
+//     }
+//
+//     return false;
+// }
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::faBoundaryMesh::faBoundaryMesh
@@ -237,7 +262,7 @@ Foam::labelRange Foam::faBoundaryMesh::range() const
 Foam::labelList Foam::faBoundaryMesh::indices
 (
     const wordRe& matcher,
-    const bool useGroups  // ignored
+    const bool useGroups  /* ignored */
 ) const
 {
     if (matcher.empty())
@@ -266,6 +291,25 @@ Foam::labelList Foam::faBoundaryMesh::indices
 }
 
 
+Foam::labelList Foam::faBoundaryMesh::indices
+(
+    const wordRes& matcher,
+    const bool useGroups  /* ignored */
+) const
+{
+    if (matcher.empty())
+    {
+        return labelList();
+    }
+    else if (matcher.size() == 1)
+    {
+        return this->indices(matcher.first(), useGroups);
+    }
+
+    return PtrListOps::findMatching(*this, matcher);
+}
+
+
 Foam::label Foam::faBoundaryMesh::findIndex(const wordRe& key) const
 {
     if (key.empty())
diff --git a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
index 397719aedf1..4bbd4016f68 100644
--- a/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
+++ b/src/finiteArea/faMesh/faBoundaryMesh/faBoundaryMesh.H
@@ -56,6 +56,7 @@ namespace Foam
 // Forward Declarations
 class faMesh;
 class faBoundaryMesh;
+class wordRes;
 Ostream& operator<<(Ostream&, const faBoundaryMesh&);
 
 /*---------------------------------------------------------------------------*\
@@ -72,6 +73,18 @@ class faBoundaryMesh
         //- Reference to mesh
         const faMesh& mesh_;
 
+        //- Demand-driven: list of patch ids per group
+        /// mutable autoPtr<HashTable<labelList>> groupIDsPtr_;
+
+
+    // Private Member Functions
+
+        //- Some patches have inGroup entries
+        /// bool hasGroupIDs() const;
+
+        //- Calculate group name to patch ids lookup
+        /// void calcGroupIDs() const;
+
         //- No copy construct
         faBoundaryMesh(const faBoundaryMesh&) = delete;
 
@@ -150,13 +163,22 @@ public:
         labelRange range() const;
 
 
-        //- Return patch indices for all matches.
-        //  A no-op (returns -1) for an empty key
-        //  \note Matching patchGroups currently not supported
+        //- Return (sorted) patch indices for all matches.
+        //  [FUTURE] Optionally matches patch groups.
+        //  A no-op (returns empty list) for an empty matcher
         labelList indices
         (
             const wordRe& matcher,
-            const bool useGroups = false  /* ignored */
+            const bool useGroups = true  //!< currently ignored
+        ) const;
+
+        //- Return (sorted) patch indices for all matches.
+        //  [FUTURE] Optionally matches patch groups.
+        //  A no-op (returns empty list) for an empty matcher
+        labelList indices
+        (
+            const wordRes& matcher,
+            const bool useGroups = true  //!< currently ignored
         ) const;
 
         //- Return patch index for the first match, return -1 if not found
@@ -206,7 +228,7 @@ public:
 
         //- Identical to the indices() method (AUG-2018)
         FOAM_DEPRECATED_FOR(2018-08, "indices() method")
-        labelList findIndices(const wordRe& key, bool useGroups=false) const
+        labelList findIndices(const wordRe& key, bool useGroups=true) const
         {
             return indices(key, useGroups);
         }
diff --git a/src/finiteArea/faMesh/faMesh.C b/src/finiteArea/faMesh/faMesh.C
index 0893fbd6ed5..ef64e0541ec 100644
--- a/src/finiteArea/faMesh/faMesh.C
+++ b/src/finiteArea/faMesh/faMesh.C
@@ -66,9 +66,6 @@ static labelList selectPatchFaces
     const wordRes& polyPatchNames
 )
 {
-    //- Return the set of patch IDs corresponding to the given names
-    //  By default warns if given names are not found.
-    //  Optionally matches to patchGroups as well as patchNames.
     const labelList patchIDs
     (
         pbm.patchSet
diff --git a/src/finiteArea/faMesh/faPatches/faPatch/faPatch.C b/src/finiteArea/faMesh/faPatches/faPatch/faPatch.C
index a050e497a84..6ca7f3db147 100644
--- a/src/finiteArea/faMesh/faPatches/faPatch/faPatch.C
+++ b/src/finiteArea/faMesh/faPatches/faPatch/faPatch.C
@@ -66,8 +66,8 @@ Foam::faPatch::faPatch
     const label ngbPolyPatchIndex
 )
 :
-    labelList(edgeLabels),
     patchIdentifier(name, index),
+    labelList(edgeLabels),
     nbrPolyPatchId_(ngbPolyPatchIndex),
     boundaryMesh_(bm),
     edgeFacesPtr_(nullptr),
@@ -84,8 +84,8 @@ Foam::faPatch::faPatch
     const faBoundaryMesh& bm
 )
 :
-    labelList(dict.get<labelList>("edgeLabels")),
     patchIdentifier(name, dict, index),
+    labelList(dict.get<labelList>("edgeLabels")),
     nbrPolyPatchId_(dict.get<label>("ngbPolyPatchIndex")),
     boundaryMesh_(bm),
     edgeFacesPtr_(nullptr),
@@ -96,8 +96,8 @@ Foam::faPatch::faPatch
 
 Foam::faPatch::faPatch(const faPatch& p, const faBoundaryMesh& bm)
 :
-    labelList(p),
     patchIdentifier(p, p.index()),
+    labelList(p),
     nbrPolyPatchId_(p.nbrPolyPatchId_),
     boundaryMesh_(bm),
     edgeFacesPtr_(nullptr),
diff --git a/src/finiteArea/faMesh/faPatches/faPatch/faPatch.H b/src/finiteArea/faMesh/faPatches/faPatch/faPatch.H
index 61333db7813..9be7e6153ef 100644
--- a/src/finiteArea/faMesh/faPatches/faPatch/faPatch.H
+++ b/src/finiteArea/faMesh/faPatches/faPatch/faPatch.H
@@ -69,8 +69,8 @@ Ostream& operator<<(Ostream&, const faPatch&);
 
 class faPatch
 :
-    public labelList,
-    public patchIdentifier
+    public patchIdentifier,
+    public labelList
 {
     // Private Data
 
diff --git a/src/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.C b/src/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.C
index b3d70e1c340..9b00d915b5a 100644
--- a/src/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.C
+++ b/src/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.C
@@ -83,6 +83,16 @@ Foam::labelList Foam::fvBoundaryMesh::indices
 }
 
 
+Foam::labelList Foam::fvBoundaryMesh::indices
+(
+    const wordRes& matcher,
+    const bool useGroups
+) const
+{
+    return mesh().boundaryMesh().indices(matcher, useGroups);
+}
+
+
 Foam::label Foam::fvBoundaryMesh::findPatchID(const word& patchName) const
 {
     if (patchName.empty())
diff --git a/src/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.H b/src/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.H
index 31c27e34a6a..569723b3a2a 100644
--- a/src/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.H
+++ b/src/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2018-2020 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -49,6 +49,7 @@ namespace Foam
 // Forward Declarations
 class fvMesh;
 class polyBoundaryMesh;
+class wordRes;
 
 /*---------------------------------------------------------------------------*\
                        Class fvBoundaryMesh Declaration
@@ -109,10 +110,14 @@ public:
         //- with only those pointing to interfaces being set
         lduInterfacePtrsList interfaces() const;
 
-        //- Return patch indices for all matches.
+        //- Return (sorted) patch indices for all matches.
         //  A no-op (returns empty list) for an empty matcher
         labelList indices(const wordRe& matcher, const bool useGroups) const;
 
+        //- Return (sorted) patch indices for all matches.
+        //  A no-op (returns empty list) for an empty matcher
+        labelList indices(const wordRes& matcher, const bool useGroups) const;
+
         //- Find patch index given a name
         //  A no-op (returns -1) for an empty patchName
         label findPatchID(const word& patchName) const;
diff --git a/src/surfMesh/triSurface/patches/surfacePatch.C b/src/surfMesh/triSurface/patches/surfacePatch.C
index d4b1920234f..ff4bd9e841f 100644
--- a/src/surfMesh/triSurface/patches/surfacePatch.C
+++ b/src/surfMesh/triSurface/patches/surfacePatch.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -114,9 +114,9 @@ bool Foam::operator==
 {
     return
     (
-        (a.geometricType() == b.geometricType())
-     && (a.size() == b.size())
+        (a.size() == b.size())
      && (a.start() == b.start())
+     && (a.geometricType() == b.geometricType())
     );
 }
 
@@ -144,19 +144,4 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const surfacePatch& obj)
 }
 
 
-// * * * * * * * * * * * * * * * Housekeeping  * * * * * * * * * * * * * * * //
-
-Foam::surfacePatch::surfacePatch
-(
-    const word& geometricType,
-    const word& name,
-    const label size,
-    const label start,
-    const label index
-)
-:
-    surfacePatch(name, size, start, index, geometricType)
-{}
-
-
 // ************************************************************************* //
diff --git a/src/surfMesh/triSurface/patches/surfacePatch.H b/src/surfMesh/triSurface/patches/surfacePatch.H
index 5ed5c8b8e20..c36ae7ce7bf 100644
--- a/src/surfMesh/triSurface/patches/surfacePatch.H
+++ b/src/surfMesh/triSurface/patches/surfacePatch.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -106,25 +106,25 @@ public:
     // Member Functions
 
         //- Return start label of this patch in the face list
-        label start() const
+        label start() const noexcept
         {
             return start_;
         }
 
         //- Return start label of this patch in the face list
-        label& start()
+        label& start() noexcept
         {
             return start_;
         }
 
         //- Return size of this patch in the face list
-        label size() const
+        label size() const noexcept
         {
             return size_;
         }
 
         //- Return size of this patch in the face list
-        label& size()
+        label& size() noexcept
         {
             return size_;
         }
@@ -156,7 +156,10 @@ public:
             const label size,
             const label start,
             const label index
-        );
+        )
+        :
+            surfacePatch(name, size, start, index, geometricType)
+        {}
 
         //- Deprecated(2020-01) Ostream output
         //  \deprecated(2020-01) - Ostream output
-- 
GitLab