From 294a3d05ba501fa00d74726b4510855f576e5de1 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Tue, 23 Jan 2018 15:39:45 +0100
Subject: [PATCH] BUG: problems converting clouds to ensight or vtk format
 (closes #708)

- problems when the cloud was not available on all processors.

- NB: ensight measured data only allows a single cloud, but
  foamToEnsight writes all clouds.
---
 .../foamToEnsight/ensightOutputCloud.H        |   4 +-
 .../ensightOutputCloudTemplates.C             |  19 ++-
 .../foamToEnsight/findCloudFields.H           |  15 +-
 .../foamToEnsight/foamToEnsight.C             |  24 +++-
 .../foamToEnsightParts/findFields.H           |  60 ++++----
 .../foamToEnsightParts/foamToEnsightParts.C   |  33 +++--
 .../dataConversion/foamToVTK/findClouds.H     |  26 ++--
 .../dataConversion/foamToVTK/foamToVTK.C      | 133 ++++++++++++------
 .../foamToVTK/foamVtkLagrangianWriter.C       |  15 +-
 .../foamToVTK/foamVtkLagrangianWriter.H       |  14 +-
 .../foamVtkLagrangianWriterTemplates.C        |  42 ++++--
 .../HashTables/HashTableOps/HashTableOps.H    |  92 ++++++++++++
 12 files changed, 333 insertions(+), 144 deletions(-)
 create mode 100644 src/OpenFOAM/containers/HashTables/HashTableOps/HashTableOps.H

diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/ensightOutputCloud.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/ensightOutputCloud.H
index d8b87935eed..b768f5180ae 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/ensightOutputCloud.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/ensightOutputCloud.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016 OpenCFD Ltd.
+     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -74,7 +74,7 @@ bool writeCloudField
 template<class Type>
 bool writeCloudField
 (
-    const IOobject& fieldObject,
+    IOobject& fieldObject,
     const bool exists,
     autoPtr<ensightFile>& output
 );
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/ensightOutputCloudTemplates.C b/applications/utilities/postProcessing/dataConversion/foamToEnsight/ensightOutputCloudTemplates.C
index fcf68edc190..04fd865c2ca 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/ensightOutputCloudTemplates.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/ensightOutputCloudTemplates.C
@@ -3,7 +3,7 @@
   \\      /  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.
+     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -36,8 +36,8 @@ License
 template<class Type>
 bool Foam::ensightCloud::writeCloudField
 (
-    const Foam::IOField<Type>& field,
-    Foam::ensightFile& os
+    const IOField<Type>& field,
+    ensightFile& os
 )
 {
     const bool exists = (returnReduce(field.size(), sumOp<label>()) > 0);
@@ -125,14 +125,23 @@ bool Foam::ensightCloud::writeCloudField
 template<class Type>
 bool Foam::ensightCloud::writeCloudField
 (
-    const Foam::IOobject& fieldObject,
+    IOobject& fieldObject,
     const bool exists,
-    Foam::autoPtr<Foam::ensightFile>& output
+    autoPtr<ensightFile>& output
 )
 {
     if (exists)
     {
+        // when exists == true, it exists globally,
+        // but can still be missing on the local processor.
+        // Handle this by READ_IF_PRESENT instead.
+
+        const IOobject::readOption rOpt = fieldObject.readOpt();
+        fieldObject.readOpt() = IOobject::READ_IF_PRESENT;
+
         IOField<Type> field(fieldObject);
+        fieldObject.readOpt() = rOpt;
+
         writeCloudField(field, output.rawRef());
     }
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/findCloudFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/findCloudFields.H
index 650258ef1f2..c7fec95c0a4 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/findCloudFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/findCloudFields.H
@@ -7,7 +7,7 @@ HashTable<HashTable<word>> cloudFields;
 if (timeDirs.size() && !noLagrangian)
 {
     const fileName& baseDir = mesh.time().path();
-    const fileName& cloudPrefix = regionPrefix/cloud::prefix;
+    const fileName cloudPrefix(regionPrefix/cloud::prefix);
 
     Info<< "Searching for lagrangian ... " << flush;
 
@@ -35,11 +35,12 @@ if (timeDirs.size() && !noLagrangian)
                 cloudPrefix/cloudName
             );
 
-            // Clouds always have "positions" (v1706 and lower) or "coordinates"
-            if (cloudObjs.found("positions") || cloudObjs.found("coordinates"))
+            // Clouds require "coordinates".
+            // The "positions" are for v1706 and lower.
+            if (cloudObjs.found("coordinates") || cloudObjs.found("positions"))
             {
                 // Save the cloud fields on a per cloud basis
-                auto fieldsPerCloud = cloudFields(cloudName);
+                auto& fieldsPerCloud = cloudFields(cloudName);
 
                 forAllConstIters(cloudObjs, fieldIter)
                 {
@@ -59,6 +60,12 @@ if (timeDirs.size() && !noLagrangian)
         cloudIter().erase("positions");
     }
 
+    if (Pstream::parRun())
+    {
+        Pstream::mapCombineGather(cloudFields, HashTablePlusEqOp<word>());
+        Pstream::mapCombineScatter(cloudFields);
+    }
+
     if (cloudFields.empty())
     {
         Info<< "none detected." << endl;
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C b/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C
index 737b3e5b648..d183ce5b355 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C
@@ -3,7 +3,7 @@
   \\      /  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.
+     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -72,6 +72,8 @@ Note
 #include "IOobjectList.H"
 #include "IOmanip.H"
 #include "OFstream.H"
+#include "PstreamCombineReduceOps.H"
+#include "HashTableOps.H"
 
 #include "fvc.H"
 #include "volFields.H"
@@ -622,8 +624,13 @@ int main(int argc, char *argv[])
 
             Info<< "Write " << cloudName << " (";
 
-            bool cloudExists = currentCloudDirs.found(cloudName);
-            reduce(cloudExists, orOp<bool>());
+            const bool cloudExists =
+                returnReduce
+                (
+                    currentCloudDirs.found(cloudName),
+                    orOp<bool>()
+                );
+
 
             {
                 autoPtr<ensightFile> os = ensCase.newCloud(cloudName);
@@ -643,10 +650,10 @@ int main(int argc, char *argv[])
                 }
             }
 
-            forAllConstIter(HashTable<word>, theseCloudFields, fieldIter)
+            forAllConstIters(theseCloudFields, fieldIter)
             {
                 const word& fieldName = fieldIter.key();
-                const word& fieldType = fieldIter();
+                const word& fieldType = fieldIter.object();
 
                 IOobject fieldObject
                 (
@@ -657,10 +664,13 @@ int main(int argc, char *argv[])
                     IOobject::MUST_READ
                 );
 
-                // cannot have field without cloud positions
-                bool fieldExists = cloudExists;
+                bool fieldExists = cloudExists; // No field without positions
                 if (cloudExists)
                 {
+                    // Want MUST_READ (globally) and valid=false (locally),
+                    // but that combination does not work.
+                    // So check the header and sync globally
+
                     fieldExists =
                         fieldObject.typeHeaderOk<IOField<scalar>>(false);
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/findFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/findFields.H
index 9ac1c568080..1172760228e 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/findFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/findFields.H
@@ -8,8 +8,8 @@ HashTable<HashTable<word>> cloudFields;
 
 if (timeDirs.size())
 {
-    const fileName& cloudPrefix = regionPrefix/cloud::prefix;
     const word& lastTimeName = timeDirs.last().name();
+    const fileName cloudPrefix(regionPrefix/cloud::prefix);
 
     IOobjectList objs(mesh, lastTimeName);
 
@@ -28,7 +28,7 @@ if (timeDirs.size())
 
 
     //
-    // now check for lagrangian/<cloudName>
+    // Now check for lagrangian/<cloudName>
     //
     fileNameList cloudDirs;
     if (!noLagrangian)
@@ -46,48 +46,45 @@ if (timeDirs.size())
     {
         const word& cloudName = cloudDirs[cloudI];
 
-        // Create a new hash table for each cloud
-        cloudFields.insert(cloudName, HashTable<word>());
-
-        // Identify the new cloud within the hash table
-        HashTable<HashTable<word>>::iterator cloudIter =
-            cloudFields.find(cloudName);
-
-        IOobjectList objs
+        IOobjectList cloudObjs
         (
             mesh,
             lastTimeName,
             cloudPrefix/cloudName
         );
 
-        bool hasCoordinates = false;
-        forAllConstIter(IOobjectList, objs, fieldIter)
+        // Clouds require "coordinates".
+        // The "positions" are for v1706 and lower.
+        if (cloudObjs.found("coordinates") || cloudObjs.found("positions"))
         {
-            const IOobject obj = *fieldIter();
-            const word& fieldName = obj.name();
-            const word& fieldType = obj.headerClassName();
+            // Save the cloud fields on a per cloud basis
+            auto& fieldsPerCloud = cloudFields(cloudName);
 
-            if (fieldName == "positions" || fieldName == "coordinates")
-            {
-                hasCoordinates = true;
-            }
-            else if (cloudFieldTypes.found(fieldType))
+            forAllConstIters(cloudObjs, fieldIter)
             {
-                // simply ignore types that we don't handle
-                cloudIter().insert(fieldName, fieldType);
-            }
-        }
+                const IOobject* obj = fieldIter();
 
-        // drop this cloud if it has no positions or is otherwise empty
-        if (!hasCoordinates || cloudIter().empty())
-        {
-            Info<< "removing cloud " << cloudName << endl;
-            cloudFields.erase(cloudIter);
+                const word& fieldName = obj->name();
+                const word& fieldType = obj->headerClassName();
+
+                if (cloudFieldTypes.found(fieldType))
+                {
+                    // Field name/type - ignore types that we don't handle
+                    fieldsPerCloud.insert(fieldName, fieldType);
+                }
+            }
         }
     }
 
+    // Only retain a cloud that actually has fields
+    cloudFields.filterValues
+    (
+        [](const HashTable<word>& v){ return v.size(); }
+    );
+
+
     //
-    // verify that the variable is present for all times
+    // Verify that the variable is present for all times
     //
     for (label i=0; volumeFields.size() && i < timeDirs.size(); ++i)
     {
@@ -114,3 +111,6 @@ if (timeDirs.size())
         volumeFields.erase(missing);
     }
 }
+
+
+// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
index bac5a91a440..6b6e4d15e9f 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016 OpenCFD Ltd.
+     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -275,10 +275,10 @@ int main(int argc, char *argv[])
 
         Info<< "Write volume field (" << flush;
 
-        forAllConstIter(HashTable<word>, volumeFields, fieldIter)
+        forAllConstIters(volumeFields, fieldIter)
         {
             const word& fieldName = fieldIter.key();
-            const word& fieldType = fieldIter();
+            const word& fieldType = fieldIter.object();
 
             IOobject fieldObject
             (
@@ -364,10 +364,12 @@ int main(int argc, char *argv[])
         Info<< " )" << endl;
 
         // Check for clouds
-        forAllConstIter(HashTable<HashTable<word>>, cloudFields, cloudIter)
+        forAllConstIters(cloudFields, cloudIter)
         {
             const word& cloudName = cloudIter.key();
-            const fileName& cloudPrefix = regionPrefix/cloud::prefix;
+            const HashTable<word>& theseCloudFields = cloudIter.object();
+
+            const fileName cloudPrefix(regionPrefix/cloud::prefix);
 
             if (!isDir(runTime.timePath()/cloudPrefix/cloudName))
             {
@@ -381,13 +383,15 @@ int main(int argc, char *argv[])
                 cloudPrefix/cloudName
             );
 
-            // Check that the positions/coordinates field is present for this
-            // time
-            if
+            // Clouds require "coordinates".
+            // The "positions" are for v1706 and lower.
+            const bool cloudExists =
             (
-                !cloudObjs.found("positions")
-             || !cloudObjs.found("coordinates")
-            )
+                cloudObjs.found("coordinates")
+             || cloudObjs.found("positions")
+            );
+
+            if (!cloudExists)
             {
                 continue;
             }
@@ -403,18 +407,17 @@ int main(int argc, char *argv[])
             Info<< " positions";
 
 
-            forAllConstIter(HashTable<word>, cloudIter(), fieldIter)
+            forAllConstIters(theseCloudFields, fieldIter)
             {
                 const word& fieldName = fieldIter.key();
-                const word& fieldType = fieldIter();
+                const word& fieldType = fieldIter.object();
 
                 IOobject *fieldObject = cloudObjs.lookup(fieldName);
 
                 if (!fieldObject)
                 {
                     Info<< "missing "
-                        << runTime.timeName()/cloudPrefix/cloudName
-                        / fieldName
+                        << runTime.timeName()/cloudPrefix/cloudName/fieldName
                         << endl;
                     continue;
                 }
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/findClouds.H b/applications/utilities/postProcessing/dataConversion/foamToVTK/findClouds.H
index 115c4b3d3b4..ebeb36cbff9 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/findClouds.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToVTK/findClouds.H
@@ -1,12 +1,12 @@
 // check all time directories for the following:
 
 // Any cloud names:
-HashSet<fileName> allCloudDirs;
+HashSet<word> allCloudDirs;
 
 if (timeDirs.size() && !noLagrangian)
 {
     const fileName& baseDir = mesh.time().path();
-    const fileName& cloudPrefix = regionPrefix/cloud::prefix;
+    const fileName cloudPrefix(regionPrefix/cloud::prefix);
 
     Info<< "Searching for lagrangian ... " << flush;
 
@@ -32,14 +32,15 @@ if (timeDirs.size() && !noLagrangian)
                 cloudPrefix/cloudName
             );
 
-            // Clouds always require "positions"/"coordinates"
-            if (cloudObjs.found("positions") || cloudObjs.found("coordinates"))
+            // Clouds require "coordinates".
+            // The "positions" are for v1706 and lower.
+            if (cloudObjs.found("coordinates") || cloudObjs.found("positions"))
             {
                 if (allCloudDirs.insert(cloudName))
                 {
-                    Info<< "At time: " << timeName
+                    Info<< nl << "    At time: " << timeName
                         << " detected cloud directory : " << cloudName
-                        << endl;
+                        << flush;
                 }
             }
         }
@@ -49,14 +50,21 @@ if (timeDirs.size() && !noLagrangian)
     {
         Info<< "none detected." << endl;
     }
+
+    if (Pstream::parRun())
+    {
+        Pstream::combineGather(allCloudDirs, HashSetPlusEqOp<word>());
+        Pstream::combineScatter(allCloudDirs);
+    }
 }
 
-// sorted list of cloud names
-const fileNameList cloudNames(allCloudDirs.sortedToc());
+
+// Sorted list of cloud names
+const wordList cloudNames(allCloudDirs.sortedToc());
 
 if (cloudNames.size())
 {
-    // complete the echo information
+    // Complete the echo information
     Info<< "(";
     for (const word& cloudName : cloudNames)
     {
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamToVTK.C b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamToVTK.C
index c847be8b964..1630c793f23 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamToVTK.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamToVTK.C
@@ -3,7 +3,7 @@
   \\      /  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.
+     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -146,6 +146,8 @@ Note
 #include "pointMesh.H"
 #include "volPointInterpolation.H"
 #include "emptyPolyPatch.H"
+#include "PstreamCombineReduceOps.H"
+#include "HashTableOps.H"
 #include "labelIOField.H"
 #include "scalarIOField.H"
 #include "sphericalTensorIOField.H"
@@ -156,7 +158,6 @@ Note
 #include "passiveParticle.H"
 #include "stringOps.H"
 #include "areaFields.H"
-
 #include "meshSubsetHelper.H"
 #include "readFields.H"
 #include "faceSet.H"
@@ -190,13 +191,17 @@ void print(const char* msg, Ostream& os, const UPtrList<const GeoField>& flds)
 }
 
 
-void print(Ostream& os, const wordList& flds)
+void print(const char* msg, Ostream& os, const wordList& flds)
 {
-    forAll(flds, i)
+    if (flds.size())
     {
-        os  << ' ' << flds[i];
+        os  << msg;
+        forAll(flds, i)
+        {
+            os  << ' ' << flds[i];
+        }
+        os  << endl;
     }
-    os  << endl;
 }
 
 
@@ -617,6 +622,16 @@ int main(int argc, char *argv[])
         pointTensorField::typeName
     };
 
+    // Supported cloud (lagrangian) field types
+    const wordHashSet cFieldTypes
+    {
+        labelIOField::typeName,
+        scalarIOField::typeName,
+        vectorIOField::typeName,
+        symmTensorIOField::typeName,
+        tensorIOField::typeName
+    };
+
     forAll(timeDirs, timei)
     {
         runTime.setTime(timeDirs[timei], timei);
@@ -1446,7 +1461,7 @@ int main(int argc, char *argv[])
         //
         //---------------------------------------------------------------------
 
-        for (const fileName& cloudName : cloudNames)
+        for (const word& cloudName : cloudNames)
         {
             // Always create the cloud directory.
             mkDir(fvPath/cloud::prefix/cloudName);
@@ -1459,50 +1474,84 @@ int main(int argc, char *argv[])
             Info<< "    Lagrangian: "
                 << relativeName(runTime, outputName) << nl;
 
-            IOobjectList sprayObjs
+            IOobjectList cloudObjs
             (
                 mesh,
                 runTime.timeName(),
                 cloud::prefix/cloudName
             );
 
-            if (sprayObjs.found("positions") || sprayObjs.found("coordinates"))
+            // Clouds require "coordinates".
+            // The "positions" are for v1706 and lower.
+            bool cloudExists =
+            (
+                cloudObjs.found("coordinates")
+             || cloudObjs.found("positions")
+            );
+            reduce(cloudExists, orOp<bool>());
+
+            if (cloudExists)
             {
-                wordList labelNames(sprayObjs.names(labelIOField::typeName));
-                Info<< "        labels      :";
-                print(Info, labelNames);
+                // Limited to types that we explicitly handle
+                HashTable<wordHashSet> cloudFields = cloudObjs.classes();
+                cloudFields.retain(cFieldTypes);
 
-                wordList scalarNames(sprayObjs.names(scalarIOField::typeName));
-                Info<< "        scalars     :";
-                print(Info, scalarNames);
+                // The number of cloud fields (locally)
+                label nCloudFields = 0;
+                forAllConstIters(cloudFields, citer)
+                {
+                    nCloudFields += citer.object().size();
+                }
 
-                wordList vectorNames(sprayObjs.names(vectorIOField::typeName));
-                Info<< "        vectors     :";
-                print(Info, vectorNames);
+                // Ensure all processes have identical information
+                if (Pstream::parRun())
+                {
+                    Pstream::mapCombineGather
+                    (
+                        cloudFields,
+                        HashSetPlusEqOp<word>()
+                    );
+                    Pstream::mapCombineScatter(cloudFields);
+                }
 
-                wordList sphereNames
+
+                // Build lists of field names and echo some information
+
+                const wordList labelNames
                 (
-                    sprayObjs.names
-                    (
-                        sphericalTensorIOField::typeName
-                    )
+                    cloudFields(labelIOField::typeName).sortedToc()
                 );
-                Info<< "        sphTensors  :";
-                print(Info, sphereNames);
+                print("        labels      :", Info, labelNames);
 
-                wordList symmNames
+                const wordList scalarNames
                 (
-                    sprayObjs.names
-                    (
-                        symmTensorIOField::typeName
-                    )
+                    cloudFields(scalarIOField::typeName).sortedToc()
+                );
+                print("        scalars     :", Info, scalarNames);
+
+                const wordList vectorNames
+                (
+                    cloudFields(vectorIOField::typeName).sortedToc()
+                );
+                print("        vectors     :", Info, vectorNames);
+
+                const wordList sphNames
+                (
+                    cloudFields(sphericalTensorIOField::typeName).sortedToc()
                 );
-                Info<< "        symmTensors :";
-                print(Info, symmNames);
+                print("        sphTensors  :", Info, sphNames);
 
-                wordList tensorNames(sprayObjs.names(tensorIOField::typeName));
-                Info<< "        tensors     :";
-                print(Info, tensorNames);
+                const wordList symmNames
+                (
+                    cloudFields(symmTensorIOField::typeName).sortedToc()
+                );
+                print("        symmTensors :", Info, symmNames);
+
+                const wordList tensorNames
+                (
+                    cloudFields(tensorIOField::typeName).sortedToc()
+                );
+                print("        tensors     :", Info, tensorNames);
 
                 vtk::lagrangianWriter writer
                 (
@@ -1512,22 +1561,14 @@ int main(int argc, char *argv[])
                     fmtType
                 );
 
-                // Write number of fields
-                writer.beginParcelData
-                (
-                    labelNames.size()
-                  + scalarNames.size()
-                  + vectorNames.size()
-                  + sphereNames.size()
-                  + symmNames.size()
-                  + tensorNames.size()
-                );
+                // Write number of fields (on this processor)
+                writer.beginParcelData(nCloudFields);
 
                 // Fields
                 writer.writeIOField<label>(labelNames);
                 writer.writeIOField<scalar>(scalarNames);
                 writer.writeIOField<vector>(vectorNames);
-                writer.writeIOField<sphericalTensor>(sphereNames);
+                writer.writeIOField<sphericalTensor>(sphNames);
                 writer.writeIOField<symmTensor>(symmNames);
                 writer.writeIOField<tensor>(tensorNames);
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.C b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.C
index 23e7621e7ff..60511094043 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.C
@@ -3,7 +3,7 @@
   \\      /  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.
+     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -65,7 +65,8 @@ void Foam::vtk::lagrangianWriter::writePoints()
     }
     else
     {
-        beginPiece(); // Tricky - hide in here
+        beginPiece();           // Tricky - hide begin piece in here
+        if (!nParcels_) return; // No parcels? ... skip everything else
 
         format().tag(vtk::fileTag::POINTS)
             .openDataArray<float,3>(vtk::dataArrayAttr::POINTS)
@@ -219,16 +220,12 @@ Foam::vtk::lagrangianWriter::lagrangianWriter
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::vtk::lagrangianWriter::~lagrangianWriter()
-{}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 void Foam::vtk::lagrangianWriter::beginParcelData(label nFields)
 {
+    if (!nParcels_) return;   // Skip if there are no parcels
+
     const vtk::fileTag dataType =
     (
         useVerts_
@@ -249,6 +246,8 @@ void Foam::vtk::lagrangianWriter::beginParcelData(label nFields)
 
 void Foam::vtk::lagrangianWriter::endParcelData()
 {
+    if (!nParcels_) return;   // Skip if there are no parcels
+
     const vtk::fileTag dataType =
     (
         useVerts_
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.H b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.H
index 5457ce8c605..5e6d2fc9c7b 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriter.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
-     \\/     M anipulation  | Copyright (C) 2016 OpenCFD Ltd.
+     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,7 +25,7 @@ Class
     Foam::vtk::lagrangianWriter
 
 Description
-    Write fields (internal).
+    Write lagrangian positions and fields (clouds).
 
 SourceFiles
     lagrangianWriter.C
@@ -36,11 +36,11 @@ SourceFiles
 #ifndef foamVtkLagrangianWriter_H
 #define foamVtkLagrangianWriter_H
 
-#include "OFstream.H"
 #include "Cloud.H"
 #include "volFields.H"
 #include "pointFields.H"
 #include "foamVtkOutputOptions.H"
+#include <fstream>
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -115,7 +115,7 @@ public:
 
 
     //- Destructor
-    ~lagrangianWriter();
+    ~lagrangianWriter() = default;
 
 
     // Member Functions
@@ -135,15 +135,17 @@ public:
             return nParcels_;
         }
 
+        //- Begin parcel data (point data).
+        //  The nFields parameter is only needed for legacy format.
         void beginParcelData(label nFields);
         void endParcelData();
 
         //- Write file footer
         void writeFooter();
 
-        //- Write IOField
+        //- Write IOFields
         template<class Type>
-        void writeIOField(const wordList& objectNames);
+        void writeIOField(const wordList& fieldNames);
 };
 
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriterTemplates.C b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriterTemplates.C
index bc3a2fd6308..5b4181f55f7 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriterTemplates.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToVTK/foamVtkLagrangianWriterTemplates.C
@@ -3,7 +3,7 @@
   \\      /  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.
+     \\/     M anipulation  | Copyright (C) 2016-2018 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -29,30 +29,48 @@ License
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 template<class Type>
-void Foam::vtk::lagrangianWriter::writeIOField
-(
-    const wordList& objectNames
-)
+void Foam::vtk::lagrangianWriter::writeIOField(const wordList& fieldNames)
 {
     const int nCmpt(pTraits<Type>::nComponents);
 
     const bool useIntField =
         std::is_integral<typename pTraits<Type>::cmptType>();
 
-    for (const word& fldName : objectNames)
+    const fileName cloudDir(cloud::prefix/cloudName_);
+
+    for (const word& fldName : fieldNames)
     {
-        IOobject header
+        // Globally the field is expected to exist (MUST_READ), but can
+        // be missing on a local processor.
+        //
+        // However, constructing IOField with MUST_READ and valid=false fails.
+        // Workaround: READ_IF_PRESENT and verify the header globally
+
+        IOobject fieldObject
         (
             fldName,
             mesh_.time().timeName(),
-            cloud::prefix/cloudName_,
+            cloudDir,
             mesh_,
-            IOobject::MUST_READ,
-            IOobject::NO_WRITE,
-            false  // no register
+            IOobject::READ_IF_PRESENT
         );
 
-        IOField<Type> fld(header);
+        // Check global existence - could make an error
+        const bool fieldExists =
+            returnReduce
+            (
+                fieldObject.typeHeaderOk<IOField<Type>>(false),
+                orOp<bool>()
+            );
+
+        if (!fieldExists)
+        {
+            continue;
+        }
+
+        IOField<Type> fld(fieldObject);
+
+        // NOTE: Could skip if there are no local parcels...
 
         if (useIntField)
         {
diff --git a/src/OpenFOAM/containers/HashTables/HashTableOps/HashTableOps.H b/src/OpenFOAM/containers/HashTables/HashTableOps/HashTableOps.H
new file mode 100644
index 00000000000..034742e59c7
--- /dev/null
+++ b/src/OpenFOAM/containers/HashTables/HashTableOps/HashTableOps.H
@@ -0,0 +1,92 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+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/>.
+
+InNamspace
+    Foam
+
+Description
+    Various functions to operate on HashTables.
+
+SourceFiles
+    HashTableOps.H
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef HashTableOps_H
+#define HashTableOps_H
+
+#include "HashSet.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+//- Combine HashSet operation. Equivalent to 'a += b'
+template<class Key=word, class Hash=string::hash>
+struct HashSetPlusEqOp
+{
+    typedef HashSet<Key, Hash> value_type;
+
+    void operator()(value_type& a, const value_type& b) const
+    {
+        a += b;
+    }
+};
+
+
+//- Combine HashTable operation. Equivalent to 'a += b'
+template<class T, class Key=word, class Hash=string::hash>
+struct HashTablePlusEqOp
+{
+    typedef HashTable<T, Key, Hash> value_type;
+
+    void operator()(value_type& a, const value_type& b) const
+    {
+        if (b.size())
+        {
+            if (a.size())
+            {
+                forAllConstIters(b, citer)
+                {
+                    a.insert(citer.key(), citer.object());
+                }
+            }
+            else
+            {
+                a = b;
+            }
+        }
+    }
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
-- 
GitLab