From 1adac971507663143be0e9d7b73ce61ec7af98e1 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Mon, 10 Sep 2018 18:28:55 +0200
Subject: [PATCH] ENH: parallel output for vtk::patchWriter (issue #926)

---
 .../vtk/output/foamVtkPatchWriter.C           | 661 +++++++++++++-----
 .../vtk/output/foamVtkPatchWriter.H           | 220 +++---
 .../vtk/output/foamVtkPatchWriterTemplates.C  | 363 ++++++++--
 3 files changed, 893 insertions(+), 351 deletions(-)

diff --git a/src/conversion/vtk/output/foamVtkPatchWriter.C b/src/conversion/vtk/output/foamVtkPatchWriter.C
index 3ca9500a84f..1bc43447065 100644
--- a/src/conversion/vtk/output/foamVtkPatchWriter.C
+++ b/src/conversion/vtk/output/foamVtkPatchWriter.C
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
+     \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,19 +25,53 @@ License
 
 #include "foamVtkPatchWriter.H"
 #include "foamVtkOutput.H"
-
+#include "globalIndex.H"
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
 void Foam::vtk::patchWriter::beginPiece()
 {
-    if (!legacy_)
+    // Basic sizes
+    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
+
+    nLocalPoints_ = nLocalFaces_ = nLocalVerts_ = 0;
+
+    for (const label patchId : patchIDs_)
+    {
+        const polyPatch& pp = patches[patchId];
+
+        nLocalPoints_ += pp.nPoints();
+        nLocalFaces_  += pp.size();
+
+        for (const face& f : pp)
+        {
+            nLocalVerts_ += f.size();
+        }
+    }
+
+    numberOfPoints_ = nLocalPoints_;
+    numberOfCells_ = nLocalFaces_;
+
+    if (parallel_)
+    {
+        reduce(numberOfPoints_, sumOp<label>());
+        reduce(numberOfCells_,  sumOp<label>());
+    }
+
+
+    // Nothing else to do for legacy
+    if (legacy()) return;
+
+
+    if (format_)
     {
         format()
-            .openTag(vtk::fileTag::PIECE)
-            .xmlAttr(vtk::fileAttr::NUMBER_OF_POINTS, nPoints_)
-            .xmlAttr(vtk::fileAttr::NUMBER_OF_POLYS,  nFaces_)
-            .closeTag();
+            .tag
+            (
+                vtk::fileTag::PIECE,
+                vtk::fileAttr::NUMBER_OF_POINTS, numberOfPoints_,
+                vtk::fileAttr::NUMBER_OF_POLYS,  numberOfCells_
+            );
     }
 }
 
@@ -46,135 +80,244 @@ void Foam::vtk::patchWriter::writePoints()
 {
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
-    const uint64_t payLoad = (nPoints_*3*sizeof(float));
-
-    if (legacy_)
+    if (format_)
     {
-        legacy::beginPoints(os_, nPoints_);
+        if (legacy())
+        {
+            legacy::beginPoints(os_, numberOfPoints_);
+        }
+        else
+        {
+            const uint64_t payLoad =
+                vtk::sizeofData<float, 3>(numberOfPoints_);
+
+            format()
+                .tag(vtk::fileTag::POINTS)
+                .beginDataArray<float, 3>(vtk::dataArrayAttr::POINTS);
+
+            format().writeSize(payLoad);
+        }
     }
-    else
+
+
+    if (parallel_ ? Pstream::master() : true)
     {
-        format().tag(vtk::fileTag::POINTS)
-            .openDataArray<float, 3>(vtk::dataArrayAttr::POINTS)
-            .closeTag();
+        for (const label patchId : patchIDs_)
+        {
+            const polyPatch& pp = patches[patchId];
+
+            vtk::writeList(format(), pp.localPoints());
+        }
     }
 
-    format().writeSize(payLoad);
 
-    for (const label patchId : patchIDs_)
+    if (parallel_)
     {
-        const polyPatch& pp = patches[patchId];
+        // Patch Ids are identical across all processes
+        const label nPatches = patchIDs_.size();
+
+        if (Pstream::master())
+        {
+            pointField recv;
+
+            // Receive each point field and write
+            for
+            (
+                int slave=Pstream::firstSlave();
+                slave<=Pstream::lastSlave();
+                ++slave
+            )
+            {
+                IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+
+                for (label i=0; i < nPatches; ++i)
+                {
+                    fromSlave >> recv;
 
-        vtk::writeList(format(), pp.localPoints());
+                    vtk::writeList(format(), recv);
+                }
+            }
+        }
+        else
+        {
+            // Send each point field to master
+            OPstream toMaster
+            (
+                Pstream::commsTypes::blocking,
+                Pstream::masterNo()
+            );
+
+            for (const label patchId : patchIDs_)
+            {
+                const polyPatch& pp = patches[patchId];
+
+                toMaster << pp.localPoints();
+            }
+        }
     }
-    format().flush();
 
-    if (!legacy_)
+
+    if (format_)
     {
-        format()
-            .endDataArray()
-            .endTag(vtk::fileTag::POINTS);
+        format().flush();
+        format().endDataArray();
+
+        if (!legacy())
+        {
+            format()
+                .endTag(vtk::fileTag::POINTS);
+        }
     }
 }
 
 
-void Foam::vtk::patchWriter::writePolysLegacy()
+void Foam::vtk::patchWriter::writePolysLegacy
+(
+    const globalIndex& pointOffsets
+)
 {
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
-    // connectivity count without additional storage (done internally)
-    uint64_t nConnectivity = 0;
-    for (const label patchId : patchIDs_)
-    {
-        const polyPatch& pp = patches[patchId];
+    // Connectivity count without additional storage (done internally)
 
-        forAll(pp, facei)
-        {
-            nConnectivity += pp[facei].size();
-        }
+    label nFaces = nLocalFaces_;
+    label nVerts = nLocalVerts_;
+
+    if (parallel_)
+    {
+        reduce(nFaces, sumOp<label>());
+        reduce(nVerts, sumOp<label>());
     }
 
-    legacy::beginPolys(os_, nFaces_, nConnectivity);
+    if (nFaces != numberOfCells_)
+    {
+        FatalErrorInFunction
+            << "Expecting " << numberOfCells_
+            << " faces, but found " << nFaces
+            << exit(FatalError);
+    }
 
+    legacy::beginPolys(os_, nFaces, nVerts);
 
-    // legacy: size + connectivity together
-    // [nPts, id1, id2, ..., nPts, id1, id2, ...]
+    labelList vertLabels(nLocalFaces_ + nLocalVerts_);
 
-    label off = 0;
-    for (const label patchId : patchIDs_)
     {
-        const polyPatch& pp = patches[patchId];
+        // Legacy: size + connectivity together
+        // [nPts, id1, id2, ..., nPts, id1, id2, ...]
 
-        forAll(pp, facei)
+        auto iter = vertLabels.begin();
+
+        label off = pointOffsets.localStart();
+
+        for (const label patchId : patchIDs_)
         {
-            const face& f = pp.localFaces()[facei];
+            const polyPatch& pp = patches[patchId];
 
-            format().write(f.size());  // The size prefix
-            forAll(f, fi)
+            for (const face& f : pp.localFaces())
             {
-                format().write(off + f[fi]);
+                *iter = f.size();       // The size prefix
+                ++iter;
+
+                for (const label pfi : f)
+                {
+                    *iter = pfi + off;  // Face vertex label
+                    ++iter;
+                }
             }
+            off += pp.nPoints();
         }
-        off += pp.nPoints();
     }
 
-    format().flush();
+
+    if (parallel_)
+    {
+        vtk::writeListParallel(format_.ref(), vertLabels);
+    }
+    else
+    {
+        vtk::writeList(format(), vertLabels);
+    }
+
+    if (format_)
+    {
+        format().flush();
+    }
 }
 
 
-void Foam::vtk::patchWriter::writePolys()
+void Foam::vtk::patchWriter::writePolys
+(
+    const globalIndex& pointOffsets
+)
 {
+    if (format_)
+    {
+        format().tag(vtk::fileTag::POLYS);
+    }
+
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
     //
     // 'connectivity'
     //
+    {
+        labelList vertLabels(nLocalVerts_);
 
-    format().tag(vtk::fileTag::POLYS);
+        label nVerts = nLocalVerts_;
 
-    //
-    // 'connectivity'
-    //
-    {
-        // payload count
-        uint64_t payLoad = 0;
-        for (const label patchId : patchIDs_)
+        if (parallel_)
         {
-            const polyPatch& pp = patches[patchId];
-
-            forAll(pp, facei)
-            {
-                payLoad += pp[facei].size();
-            }
+            reduce(nVerts, sumOp<label>());
         }
 
-        format().openDataArray<label>(vtk::dataArrayAttr::CONNECTIVITY)
-            .closeTag();
+        if (format_)
+        {
+            const uint64_t payLoad =
+                vtk::sizeofData<label>(nVerts);
 
-        // payload size
-        format().writeSize(payLoad * sizeof(label));
+            format().beginDataArray<label>(vtk::dataArrayAttr::CONNECTIVITY);
+            format().writeSize(payLoad);
+        }
 
-        label off = 0;
-        for (const label patchId : patchIDs_)
         {
-            const polyPatch& pp = patches[patchId];
+            // XML: connectivity only
+            // [id1, id2, ..., id1, id2, ...]
+
+            auto iter = vertLabels.begin();
+
+            label off = pointOffsets.localStart();
 
-            forAll(pp, facei)
+            for (const label patchId : patchIDs_)
             {
-                const face& f = pp.localFaces()[facei];
-                forAll(f, fi)
+                const polyPatch& pp = patches[patchId];
+
+                for (const face& f : pp.localFaces())
                 {
-                    format().write(off + f[fi]);
+                    for (const label pfi : f)
+                    {
+                        *iter = pfi + off;  // Face vertex label
+                        ++iter;
+                    }
                 }
+                off += pp.nPoints();
             }
-
-            off += pp.nPoints();
         }
 
-        format().flush();
 
-        format()
-            .endDataArray();
+        if (parallel_)
+        {
+            vtk::writeListParallel(format_.ref(), vertLabels);
+        }
+        else
+        {
+            vtk::writeList(format(), vertLabels);
+        }
+
+        if (format_)
+        {
+            format().flush();
+            format().endDataArray();
+        }
     }
 
 
@@ -182,44 +325,64 @@ void Foam::vtk::patchWriter::writePolys()
     // 'offsets'  (connectivity offsets)
     //
     {
-        format()
-            .openDataArray<label>(vtk::dataArrayAttr::OFFSETS)
-            .closeTag();
+        labelList vertOffsets(nLocalFaces_);
+        label nOffs = vertOffsets.size();
+
+        // global connectivity offsets
+        const globalIndex procOffset(nLocalVerts_);
+
+        if (parallel_)
+        {
+            reduce(nOffs, sumOp<label>());
+        }
+
+        if (format_)
+        {
+            const uint64_t payLoad =
+                vtk::sizeofData<label>(nOffs);
+
+            format().beginDataArray<label>(vtk::dataArrayAttr::OFFSETS);
+            format().writeSize(payLoad);
+        }
+
+
+        label off = procOffset.localStart();
 
-        // payload size
-        format().writeSize(nFaces_ * sizeof(label));
+        auto iter = vertOffsets.begin();
 
-        label off = 0;
         for (const label patchId : patchIDs_)
         {
             const polyPatch& pp = patches[patchId];
 
-            forAll(pp, facei)
+            for (const face& f : pp)
             {
-                off += pp[facei].size();
-
-                format().write(off);
+                off += f.size();   // End offset
+                *iter = off;
+                ++iter;
             }
         }
 
-        format().flush();
-        format().endDataArray();
-    }
 
-    format().endTag(vtk::fileTag::POLYS);
-}
+        if (parallel_)
+        {
+            vtk::writeListParallel(format_.ref(), vertOffsets);
+        }
+        else
+        {
+            vtk::writeList(format_.ref(), vertOffsets);
+        }
 
 
-void Foam::vtk::patchWriter::writeMesh()
-{
-    writePoints();
-    if (legacy_)
-    {
-        writePolysLegacy();
+        if (format_)
+        {
+            format().flush();
+            format().endDataArray();
+        }
     }
-    else
+
+    if (format_)
     {
-        writePolys();
+        format().endTag(vtk::fileTag::POLYS);
     }
 }
 
@@ -229,161 +392,273 @@ void Foam::vtk::patchWriter::writeMesh()
 Foam::vtk::patchWriter::patchWriter
 (
     const fvMesh& mesh,
-    const fileName& baseName,
-    const vtk::outputOptions outOpts,
-    const bool nearCellValue,
-    const labelList& patchIDs
+    const labelList& patchIDs,
+    const vtk::outputOptions opts,
+    const bool useNearCellValue
 )
 :
+    vtk::fileWriter(vtk::fileTag::POLY_DATA, opts),
     mesh_(mesh),
-    legacy_(outOpts.legacy()),
-    format_(),
-    nearCellValue_(nearCellValue),
     patchIDs_(patchIDs),
-    os_(),
-    nPoints_(0),
-    nFaces_(0)
+    useNearCellValue_(useNearCellValue),
+    numberOfPoints_(0),
+    numberOfCells_(0),
+    nLocalPoints_(0),
+    nLocalFaces_(0),
+    nLocalVerts_(0)
 {
-    outputOptions opts(outOpts);
-    opts.append(false);  // No append supported
+    // We do not currently support append mode
+    opts_.append(false);
+}
 
-    os_.open((baseName + (legacy_ ? ".vtk" : ".vtp")).c_str());
-    format_ = opts.newFormatter(os_);
 
-    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
+Foam::vtk::patchWriter::patchWriter
+(
+    const fvMesh& mesh,
+    const labelList& patchIDs,
+    const fileName& file,
+    bool parallel
+)
+:
+    patchWriter(mesh, patchIDs)
+{
+    open(file, parallel);
+}
 
-    const word& title =
-    (
-        patchIDs_.size() == 1
-      ? patches[patchIDs_.first()].name()
-      : "patches"
-    );
 
-    // Basic sizes
-    nPoints_ = nFaces_ = 0;
-    for (const label patchId : patchIDs_)
+Foam::vtk::patchWriter::patchWriter
+(
+    const fvMesh& mesh,
+    const labelList& patchIDs,
+    const vtk::outputOptions opts,
+    const fileName& file,
+    bool parallel
+)
+:
+    patchWriter(mesh, patchIDs, opts)
+{
+    open(file, parallel);
+}
+
+
+Foam::vtk::patchWriter::patchWriter
+(
+    const fvMesh& mesh,
+    const labelList& patchIDs,
+    const vtk::outputOptions opts,
+    const bool useNearCellValue,
+    const fileName& file,
+    bool parallel
+)
+:
+    patchWriter(mesh, patchIDs, opts, useNearCellValue)
+{
+    open(file, parallel);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::vtk::patchWriter::beginFile(std::string title)
+{
+    if (title.size())
     {
-        const polyPatch& pp = patches[patchId];
+        return vtk::fileWriter::beginFile(title);
+    }
 
-        nPoints_ += pp.nPoints();
-        nFaces_  += pp.size();
+    // Provide default title
+
+    if (legacy())
+    {
+        title =
+        (
+            patchIDs_.size() == 1
+          ? mesh_.boundaryMesh()[patchIDs_.first()].name()
+          : "patches"
+        );
+
+        return vtk::fileWriter::beginFile(title);
     }
 
 
-    if (legacy_)
+    // XML (inline)
+
+    if (patchIDs_.size() == 1)
     {
-        legacy::fileHeader(format(), title, vtk::fileTag::POLY_DATA);
+        title =
+        (
+            "patch='" + mesh_.boundaryMesh()[patchIDs_.first()].name() + "'"
+        );
     }
     else
     {
-        // XML (inline)
-
-        format()
-            .xmlHeader()
-            .xmlComment(title)
-            .beginVTKFile(vtk::fileTag::POLY_DATA, "0.1");
+        title =
+        (
+            "npatches='" + Foam::name(patchIDs_.size()) + "'"
+        );
     }
 
-    beginPiece();
-    writeMesh();
+    title +=
+    (
+        " time='" + mesh_.time().timeName()
+      + "' index='" + Foam::name(mesh_.time().timeIndex())
+      + "'"
+    );
+
+    return vtk::fileWriter::beginFile(title);
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+bool Foam::vtk::patchWriter::writeGeometry()
+{
+    enter_Piece();
 
-Foam::vtk::patchWriter::~patchWriter()
-{}
+    beginPiece();
 
+    writePoints();
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+    const globalIndex globalPointOffset(nLocalPoints_);
 
-void Foam::vtk::patchWriter::beginCellData(label nFields)
-{
-    if (legacy_)
+    if (legacy())
     {
-        legacy::beginCellData(format(), nFaces_, nFields);
+        writePolysLegacy(globalPointOffset);
     }
     else
     {
-        format().beginCellData();
+        writePolys(globalPointOffset);
     }
+
+    return true;
 }
 
 
-void Foam::vtk::patchWriter::endCellData()
+bool Foam::vtk::patchWriter::beginCellData(label nFields)
 {
-    if (!legacy_)
-    {
-        format().endCellData();
-    }
+    return enter_CellData(numberOfCells_, nFields);
 }
 
 
-void Foam::vtk::patchWriter::beginPointData(label nFields)
+bool Foam::vtk::patchWriter::beginPointData(label nFields)
 {
-    if (legacy_)
-    {
-        legacy::beginPointData(format(), nPoints_, nFields);
-    }
-    else
-    {
-        format().beginPointData();
-    }
+    return enter_PointData(numberOfPoints_, nFields);
 }
 
 
-void Foam::vtk::patchWriter::endPointData()
+void Foam::vtk::patchWriter::writePatchIDs()
 {
-    if (!legacy_)
+    if (isState(outputState::CELL_DATA))
     {
-        format().endPointData();
+        ++nCellData_;
     }
-}
-
-
-void Foam::vtk::patchWriter::writeFooter()
-{
-    if (!legacy_)
+    else
     {
-        // slight cheat. </Piece> too
-        format().endTag(vtk::fileTag::PIECE);
-
-        format().endTag(vtk::fileTag::POLY_DATA)
-            .endVTKFile();
+        FatalErrorInFunction
+            << "Bad writer state (" << stateNames[state_]
+            << ") - should be (" << stateNames[outputState::CELL_DATA]
+            << ") for patchID field" << nl << endl
+            << exit(FatalError);
     }
-}
 
+    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
-void Foam::vtk::patchWriter::writePatchIDs()
-{
-    // Patch ids first
-    const uint64_t payLoad = nFaces_ * sizeof(label);
+    label nFaces = nLocalFaces_;
 
-    if (legacy_)
+    if (parallel_)
     {
-        legacy::intField<1>(format(), "patchID", nFaces_);  // 1 component
+        reduce(nFaces, sumOp<label>());
     }
-    else
+
+    if (format_)
     {
-        format().openDataArray<label>("patchID")
-            .closeTag();
+        if (legacy())
+        {
+            legacy::intField<1>(format(), "patchID", nFaces);  // 1 component
+        }
+        else
+        {
+            const uint64_t payLoad =
+                vtk::sizeofData<label>(nFaces);
+
+            format().beginDataArray<label>("patchID");
+            format().writeSize(payLoad);
+        }
     }
 
-    format().writeSize(payLoad);
+    if (parallel_ ? Pstream::master() : true)
+    {
+        for (const label patchId : patchIDs_)
+        {
+            label count = patches[patchId].size();
+            const label val = patchId;
+
+            while (count--)
+            {
+                format().write(val);
+            }
+        }
+    }
 
-    for (const label patchId : patchIDs_)
+    if (parallel_)
     {
-        const label sz = mesh_.boundaryMesh()[patchId].size();
+        if (Pstream::master())
+        {
+            labelList recv;
+
+            // Receive each pair
+            for
+            (
+                int slave=Pstream::firstSlave();
+                slave<=Pstream::lastSlave();
+                ++slave
+            )
+            {
+                IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+
+                fromSlave >> recv;
 
-        for (label facei = 0; facei < sz; ++facei)
+                for (label i=0; i < recv.size(); ++i)
+                {
+                    label count = recv[i];
+                    ++i;
+                    const label val = recv[i];
+
+                    while (count--)
+                    {
+                        format().write(val);
+                    }
+                }
+            }
+        }
+        else
         {
-            format().write(patchId);
+            // Send to master
+            OPstream toMaster
+            (
+                Pstream::commsTypes::blocking,
+                Pstream::masterNo()
+            );
+
+
+            labelList send(2*patchIDs_.size());
+
+            // Encode as [size, id] pairs
+            label i = 0;
+            for (const label patchId : patchIDs_)
+            {
+                send[i] = patches[patchId].size();
+                send[i+1] = patchId;
+
+                i += 2;
+            }
+
+            toMaster << send;
         }
     }
-    format().flush();
 
-    if (!legacy_)
+
+    if (format_)
     {
+        format().flush();
         format().endDataArray();
     }
 }
diff --git a/src/conversion/vtk/output/foamVtkPatchWriter.H b/src/conversion/vtk/output/foamVtkPatchWriter.H
index 86f1676a461..98ad8c32d2e 100644
--- a/src/conversion/vtk/output/foamVtkPatchWriter.H
+++ b/src/conversion/vtk/output/foamVtkPatchWriter.H
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
+     \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,7 +25,17 @@ Class
     Foam::vtk::patchWriter
 
 Description
-    Write patch fields
+    Write OpenFOAM patches and patch fields in VTP or legacy vtk format.
+
+    The file output states are managed by the Foam::vtk::fileWriter class.
+    FieldData (eg, TimeValue) must appear before any geometry pieces.
+
+Note
+    Parallel output is combined into a single Piece without point merging,
+    which is similar to using multi-piece data sets, but allows more
+    convenient creation as a streaming process.
+    In the future, the duplicate points at processor connections
+    may be addressed using ghost points.
 
 SourceFiles
     foamVtkPatchWriter.C
@@ -36,66 +46,71 @@ SourceFiles
 #ifndef foamVtkPatchWriter_H
 #define foamVtkPatchWriter_H
 
-#include "pointMesh.H"
-#include "OFstream.H"
+#include "foamVtkFileWriter.H"
 #include "volFields.H"
 #include "pointFields.H"
-#include "indirectPrimitivePatch.H"
 #include "PrimitivePatchInterpolation.H"
-#include "foamVtkOutputOptions.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
-class volPointInterpolation;
+
+// Forward declarations
+class globalIndex;
 
 namespace vtk
 {
 
 /*---------------------------------------------------------------------------*\
-                           Class patchWriter Declaration
+                      Class vtk::patchWriter Declaration
 \*---------------------------------------------------------------------------*/
 
 class patchWriter
+:
+    public vtk::fileWriter
 {
     // Private Member Data
 
         //- Reference to the OpenFOAM mesh (or subset)
         const fvMesh& mesh_;
 
-        //- Commonly used query
-        const bool legacy_;
+        //- The selected patch ids
+        labelList patchIDs_;
 
-        autoPtr<vtk::formatter> format_;
+        //- Use internal field value instead of patch value
+        bool useNearCellValue_;
 
-        const bool nearCellValue_;
+        //- The numer of field points for the current Piece
+        label numberOfPoints_;
 
-        const labelList patchIDs_;
+        //- The numer of field cells (faces) for the current Piece
+        label numberOfCells_;
 
-        std::ofstream os_;
+        //- Local number of points
+        label nLocalPoints_;
 
-        label nPoints_;
+        //- Local number of faces
+        label nLocalFaces_;
 
-        label nFaces_;
+        //- Local face vertices (connectivity) count. Sum of face sizes.
+        label nLocalVerts_;
 
 
     // Private Member Functions
 
-        //- Begin piece
+        //- Determing sizes (nLocalPoints_, nLocalFaces_, nLocalVerts_),
+        //- and begin piece.
         void beginPiece();
 
         //- Write patch points
         void writePoints();
 
-        //- Write patch faces
-        void writePolysLegacy();
+        //- Write patch faces, legacy format
+        void writePolysLegacy(const globalIndex& pointOffsets);
 
         //- Write patch faces
-        void writePolys();
-
-        //- Write mesh topology
-        void writeMesh();
+        void writePolys(const globalIndex& pointOffsets);
 
 
         //- No copy construct
@@ -109,104 +124,137 @@ public:
 
     // Constructors
 
-        //- Construct from components
+        //- Construct from components (default format INLINE_BASE64)
+        //  \param useNearCellValue to use cell instead of patch values
+        patchWriter
+        (
+            const fvMesh& mesh,
+            const labelList& patchIDs,
+            const vtk::outputOptions opts = vtk::formatType::INLINE_BASE64,
+            const bool useNearCellValue = false
+        );
+
+        //- Construct from components (default format INLINE_BASE64),
+        //- and open the file for writing.
+        //  The file name is with/without an extension.
+        patchWriter
+        (
+            const fvMesh& mesh,
+            const labelList& patchIDs,
+            const fileName& file,
+            bool parallel = Pstream::parRun()
+        );
+
+        //- Construct from components (default format INLINE_BASE64),
+        //- Construct from components and open the file for writing.
+        //  The file name is with/without an extension.
+        patchWriter
+        (
+            const fvMesh& mesh,
+            const labelList& patchIDs,
+            const vtk::outputOptions opts,
+            const fileName& file,
+            bool parallel = Pstream::parRun()
+        );
+
+        //- Construct from components and open the file for writing.
+        //  The file name is with/without an extension.
+        //  \param useNearCellValue to use cell instead of patch values
         patchWriter
         (
             const fvMesh& mesh,
-            const fileName& baseName,
-            const vtk::outputOptions outOpts,
-            const bool nearCellValue,
-            const labelList& patchIDs
+            const labelList& patchIDs,
+            const vtk::outputOptions opts,
+            const bool useNearCellValue,
+            const fileName& file,
+            bool parallel = Pstream::parRun()
         );
 
 
     //- Destructor
-    ~patchWriter();
+    virtual ~patchWriter() = default;
 
 
     // Member Functions
 
-        inline std::ofstream& os()
-        {
-            return os_;
-        }
-
-        inline vtk::formatter& format()
-        {
-            return *format_;
-        }
+        //- File extension for current format type.
+        using vtk::fileWriter::ext;
 
-        inline label nPoints() const
+        //- File extension for given output type
+        inline static word ext(vtk::outputOptions opts)
         {
-            return nPoints_;
+            return opts.ext(vtk::fileTag::POLY_DATA);
         }
 
-        inline label nFaces() const
+        //- The patch IDs
+        inline const labelList& patchIDs() const
         {
-            return nFaces_;
+            return patchIDs_;
         }
 
-        //- Open write for CellData of count fields.
-        //  The parameters are only used for the legacy format.
-        void beginCellData(label nFields);
-
-        //- Close write for CellData
-        void endCellData();
-
-        //- Open write for PointData of count fields
-        //  The parameters are only used for the legacy format.
-        void beginPointData(label nFields);
-
-        //- Close write for PointData
-        void endPointData();
-
-        //- Write cellIDs
+        //- Write file header (non-collective)
+        //  \note Expected calling states: (OPENED).
+        virtual bool beginFile(std::string title = "");
+
+        //- Write patch topology
+        //  Also writes the file header if not previously written.
+        //  \note Must be called prior to writing CellData or PointData
+        virtual bool writeGeometry();
+
+        //- Begin CellData output section for specified number of fields.
+        //  Must be called prior to writing any cell data fields.
+        //  \param nFields is for legacy format only.
+        //      When nFields=0, this a no-op for legacy format.
+        //  \note Expected calling states: (PIECE | POINT_DATA).
+        //
+        //  \return True if the state changed
+        virtual bool beginCellData(label nFields = 0);
+
+        //- Begin PointData for specified number of fields.
+        //  Must be called prior to writing any point data fields.
+        //  \param nFields is for legacy format only.
+        //      When nFields=0, this a no-op for legacy format.
+        //  \note Expected calling states: (PIECE | CELL_DATA).
+        //
+        //  \return True if the state changed
+        virtual bool beginPointData(label nFields = 0);
+
+
+        //- Write patch ids as CellData.
+        //  Must be called within the CELL_DATA state.
         void writePatchIDs();
 
-        //- Write file footer
-        void writeFooter();
-
 
-      // Write fields (individually)
+    // Write
 
-        //- Write volume field
+        //- Write point field
         template<class Type, template<class> class PatchField>
-        void write(const GeometricField<Type, PatchField, volMesh>& field);
+        void write
+        (
+            const GeometricField<Type, PatchField, pointMesh>& field
+        );
 
-        //- Write point fields
+        //- Write volume field
         template<class Type, template<class> class PatchField>
-        void write(const GeometricField<Type, PatchField, pointMesh>& field);
-
-        //- Write point-interpolated volume field
-        template<class Type>
         void write
         (
-            const PrimitivePatchInterpolation<primitivePatch>& pInterp,
-            const GeometricField<Type, fvPatchField, volMesh>& field
+            const GeometricField<Type, PatchField, volMesh>& field
         );
 
-
-      // Write fields (collectively)
-
-        //- Write multiple volume/point fields
-        template<class Type, template<class> class PatchField, class GeoMesh>
+        //- Write volume field with point interpolation
+        template<class Type>
         void write
         (
-            const UPtrList
-            <
-                const GeometricField<Type, PatchField, GeoMesh>
-            >& flds
+            const GeometricField<Type, fvPatchField, volMesh>& field,
+            const PrimitivePatchInterpolation<primitivePatch>& pInterp
         );
 
-        //- Write multiple point-interpolated volume fields
+        //- Write volume field with point interpolation
         template<class Type>
         void write
         (
-            const PrimitivePatchInterpolation<primitivePatch>& pInterp,
-            const UPtrList
-            <
-                const GeometricField<Type, fvPatchField, volMesh>
-            >& flds
+            const GeometricField<Type, fvPatchField, volMesh>& field,
+            const PrimitivePatchInterpolation<primitivePatch>* pInterp
         );
 };
 
diff --git a/src/conversion/vtk/output/foamVtkPatchWriterTemplates.C b/src/conversion/vtk/output/foamVtkPatchWriterTemplates.C
index 0bc96023904..d8d59a35e41 100644
--- a/src/conversion/vtk/output/foamVtkPatchWriterTemplates.C
+++ b/src/conversion/vtk/output/foamVtkPatchWriterTemplates.C
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
+     \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,42 +31,109 @@ License
 template<class Type, template<class> class PatchField>
 void Foam::vtk::patchWriter::write
 (
-    const GeometricField<Type, PatchField, volMesh>& field
+    const GeometricField<Type, PatchField, pointMesh>& field
 )
 {
-    const int nCmpt(pTraits<Type>::nComponents);
-    const uint64_t payLoad(nFaces_ * nCmpt * sizeof(float));
-
-    if (legacy_)
+    if (isState(outputState::POINT_DATA))
     {
-        legacy::floatField<nCmpt>(format(), field.name(), nFaces_);
+        ++nPointData_;
     }
     else
     {
-        format().openDataArray<float, nCmpt>(field.name())
-            .closeTag();
+        FatalErrorInFunction
+            << "Bad writer state (" << stateNames[state_]
+            << ") - should be (" << stateNames[outputState::POINT_DATA]
+            << ") for field " << field.name() << nl << endl
+            << exit(FatalError);
+    }
+
+    const direction nCmpt(pTraits<Type>::nComponents);
+
+    label nPoints = nLocalPoints_;
+
+    if (parallel_)
+    {
+        reduce(nPoints, sumOp<label>());
     }
 
-    format().writeSize(payLoad);
 
-    for (const label patchId : patchIDs_)
+    if (format_)
     {
-        const auto& pfld = field.boundaryField()[patchId];
+        if (legacy())
+        {
+            legacy::floatField<nCmpt>(format(), field.name(), nPoints);
+        }
+        else
+        {
+            const uint64_t payLoad =
+                vtk::sizeofData<float, nCmpt>(nPoints);
+
+            format().beginDataArray<float, nCmpt>(field.name());
+            format().writeSize(payLoad);
+        }
+    }
 
-        if (nearCellValue_)
+
+    if (parallel_ ? Pstream::master() : true)
+    {
+        for (const label patchId : patchIDs_)
         {
+            const auto& pfld = field.boundaryField()[patchId];
+
             vtk::writeList(format(), pfld.patchInternalField()());
         }
+    }
+
+
+    if (parallel_)
+    {
+        // Patch Ids are identical across all processes
+        const label nPatches = patchIDs_.size();
+
+        if (Pstream::master())
+        {
+            Field<Type> recv;
+
+            // Receive each patch field and write
+            for
+            (
+                int slave=Pstream::firstSlave();
+                slave<=Pstream::lastSlave();
+                ++slave
+            )
+            {
+                IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+
+                for (label i=0; i < nPatches; ++i)
+                {
+                    fromSlave >> recv;
+
+                    vtk::writeList(format(), recv);
+                }
+            }
+        }
         else
         {
-            vtk::writeList(format(), pfld);
+            // Send each patch field to master
+            OPstream toMaster
+            (
+                Pstream::commsTypes::blocking,
+                Pstream::masterNo()
+            );
+
+            for (const label patchId : patchIDs_)
+            {
+                const auto& pfld = field.boundaryField()[patchId];
+
+                toMaster << pfld.patchInternalField()();
+            }
         }
     }
 
-    format().flush();
 
-    if (!legacy_)
+    if (format_)
     {
+        format().flush();
         format().endDataArray();
     }
 }
@@ -75,35 +142,122 @@ void Foam::vtk::patchWriter::write
 template<class Type, template<class> class PatchField>
 void Foam::vtk::patchWriter::write
 (
-    const GeometricField<Type, PatchField, pointMesh>& field
+    const GeometricField<Type, PatchField, volMesh>& field
 )
 {
-    const int nCmpt(pTraits<Type>::nComponents);
-    const uint64_t payLoad(nPoints_ * nCmpt * sizeof(float));
-
-    if (legacy_)
+    if (isState(outputState::CELL_DATA))
     {
-        legacy::floatField<nCmpt>(format(), field.name(), nPoints_);
+        ++nCellData_;
     }
     else
     {
-        format().openDataArray<float, nCmpt>(field.name())
-            .closeTag();
+        FatalErrorInFunction
+            << "Bad writer state (" << stateNames[state_]
+            << ") - should be (" << stateNames[outputState::CELL_DATA]
+            << ") for field " << field.name() << nl << endl
+            << exit(FatalError);
+    }
+
+    const direction nCmpt(pTraits<Type>::nComponents);
+
+    label nFaces = nLocalFaces_;
+
+    if (parallel_)
+    {
+        reduce(nFaces, sumOp<label>());
+    }
+
+
+    if (format_)
+    {
+        if (legacy())
+        {
+            legacy::floatField<nCmpt>(format(), field.name(), nFaces);
+        }
+        else
+        {
+            const uint64_t payLoad =
+                vtk::sizeofData<float, nCmpt>(nFaces);
+
+            format().beginDataArray<float, nCmpt>(field.name());
+            format().writeSize(payLoad);
+        }
     }
 
-    format().writeSize(payLoad);
 
-    for (const label patchId : patchIDs_)
+    if (parallel_ ? Pstream::master() : true)
+    {
+        for (const label patchId : patchIDs_)
+        {
+            const auto& pfld = field.boundaryField()[patchId];
+
+            if (useNearCellValue_)
+            {
+                vtk::writeList(format(), pfld.patchInternalField()());
+            }
+            else
+            {
+                vtk::writeList(format(), pfld);
+            }
+        }
+    }
+
+    if (parallel_)
     {
-        const auto& pfld = field.boundaryField()[patchId];
+        // Patch Ids are identical across all processes
+        const label nPatches = patchIDs_.size();
 
-        vtk::writeList(format(), pfld.patchInternalField()());
+        if (Pstream::master())
+        {
+            Field<Type> recv;
+
+            // Receive each patch field and write
+            for
+            (
+                int slave=Pstream::firstSlave();
+                slave<=Pstream::lastSlave();
+                ++slave
+            )
+            {
+                IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+
+                for (label i=0; i < nPatches; ++i)
+                {
+                    fromSlave >> recv;
+
+                    vtk::writeList(format(), recv);
+                }
+            }
+        }
+        else
+        {
+            // Send each patch field to master
+            OPstream toMaster
+            (
+                Pstream::commsTypes::blocking,
+                Pstream::masterNo()
+            );
+
+            for (const label patchId : patchIDs_)
+            {
+                const auto& pfld = field.boundaryField()[patchId];
+
+                if (useNearCellValue_)
+                {
+                    toMaster << pfld.patchInternalField()();
+                }
+                else
+                {
+                    toMaster << static_cast<const Field<Type>&>(pfld);
+                }
+            }
+        }
     }
 
-    format().flush();
 
-    if (!legacy_)
+    if (format_)
     {
+        format().flush();
         format().endDataArray();
     }
 }
@@ -112,76 +266,141 @@ void Foam::vtk::patchWriter::write
 template<class Type>
 void Foam::vtk::patchWriter::write
 (
-    const PrimitivePatchInterpolation<primitivePatch>& pInter,
-    const GeometricField<Type, fvPatchField, volMesh>& field
+    const GeometricField<Type, fvPatchField, volMesh>& field,
+    const PrimitivePatchInterpolation<primitivePatch>& pInter
 )
 {
-    const int nCmpt(pTraits<Type>::nComponents);
-    const uint64_t payLoad(nPoints_ * nCmpt * sizeof(float));
-
-    if (legacy_)
+    if (isState(outputState::POINT_DATA))
     {
-        legacy::floatField<nCmpt>(format(), field.name(), nPoints_);
+        ++nPointData_;
     }
     else
     {
-        format().openDataArray<float, nCmpt>(field.name())
-            .closeTag();
+        FatalErrorInFunction
+            << "Bad writer state (" << stateNames[state_]
+            << ") - should be (" << stateNames[outputState::POINT_DATA]
+            << ") for field " << field.name() << nl << endl
+            << exit(FatalError);
     }
 
-    format().writeSize(payLoad);
+    const direction nCmpt(pTraits<Type>::nComponents);
+
+    label nPoints = nLocalPoints_;
 
-    for (const label patchId : patchIDs_)
+    if (parallel_)
     {
-        const auto& pfld = field.boundaryField()[patchId];
+        reduce(nPoints, sumOp<label>());
+    }
 
-        if (nearCellValue_)
-        {
-            auto tfield =
-                pInter.faceToPointInterpolate(pfld.patchInternalField()());
 
-            vtk::writeList(format(), tfield());
+    if (format_)
+    {
+        if (legacy())
+        {
+            legacy::floatField<nCmpt>(format(), field.name(), nPoints);
         }
         else
         {
-            auto tfield = pInter.faceToPointInterpolate(pfld);
+            const uint64_t payLoad =
+                vtk::sizeofData<float, nCmpt>(nPoints);
 
-            vtk::writeList(format(), tfield());
+            format().beginDataArray<float, nCmpt>(field.name());
+            format().writeSize(payLoad);
         }
     }
 
-    format().flush();
 
-    if (!legacy_)
+    if (parallel_ ? Pstream::master() : true)
     {
-        format().endDataArray();
+        for (const label patchId : patchIDs_)
+        {
+            const auto& pfld = field.boundaryField()[patchId];
+
+            if (useNearCellValue_)
+            {
+                auto tfield =
+                    pInter.faceToPointInterpolate
+                    (
+                        pfld.patchInternalField()()
+                    );
+
+                vtk::writeList(format(), tfield());
+            }
+            else
+            {
+                auto tfield = pInter.faceToPointInterpolate(pfld);
+
+                vtk::writeList(format(), tfield());
+            }
+        }
     }
-}
 
 
-template<class Type, template<class> class PatchField, class GeoMesh>
-void Foam::vtk::patchWriter::write
-(
-    const UPtrList<const GeometricField<Type, PatchField, GeoMesh>>& flds
-)
-{
-    for (const auto& field : flds)
+    if (parallel_)
     {
-        write(field);
+        // Patch Ids are identical across all processes
+        const label nPatches = patchIDs_.size();
+
+        if (Pstream::master())
+        {
+            Field<Type> recv;
+
+            // Receive each patch field and write
+            for
+            (
+                int slave=Pstream::firstSlave();
+                slave<=Pstream::lastSlave();
+                ++slave
+            )
+            {
+                IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+
+                for (label i=0; i < nPatches; ++i)
+                {
+                    fromSlave >> recv;
+
+                    vtk::writeList(format(), recv);
+                }
+            }
+        }
+        else
+        {
+            // Send each patch field to master
+            OPstream toMaster
+            (
+                Pstream::commsTypes::blocking,
+                Pstream::masterNo()
+            );
+
+            for (const label patchId : patchIDs_)
+            {
+                const auto& pfld = field.boundaryField()[patchId];
+
+                if (useNearCellValue_)
+                {
+                    auto tfield =
+                        pInter.faceToPointInterpolate
+                        (
+                            pfld.patchInternalField()()
+                        );
+
+                    toMaster << tfield();
+                }
+                else
+                {
+                    auto tfield = pInter.faceToPointInterpolate(pfld);
+
+                    toMaster << tfield();
+                }
+            }
+        }
     }
-}
 
 
-template<class Type>
-void Foam::vtk::patchWriter::write
-(
-    const PrimitivePatchInterpolation<primitivePatch>& pInter,
-    const UPtrList<const GeometricField<Type, fvPatchField, volMesh>>& flds
-)
-{
-    for (const auto& field : flds)
+    if (format_)
     {
-        write(pInter, field);
+        format().flush();
+        format().endDataArray();
     }
 }
 
-- 
GitLab