From 7cb51f5b985d2bb09663151a54a49cfecda91f4a Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Thu, 1 Nov 2018 17:58:36 +0000
Subject: [PATCH] ENH: limit vtk floatField range (fixes #1055)

- for space-savings the VTK fields are normally written as 'float'
  rather than double. When a double field contains very large values,
  these can result in a overflow when converted to float.

  Now trap these with the appropriate numeric limits.
  No warning when these values are clipped: it should be readily
  apparent from the output.

ENH: handle symmTensor component swapping directly on VTK output.

- use VTK output routines in vtkSurfaceWriter to benefit from the
  above changes
---
 .../vtk/format/foamVtkAppendRawFormatter.C    |  20 +-
 .../vtk/format/foamVtkAsciiFormatter.C        |  19 +-
 .../vtk/format/foamVtkBase64Layer.C           |  20 +-
 .../vtk/format/foamVtkLegacyRawFormatter.C    |  35 ++--
 src/fileFormats/vtk/output/foamVtkOutput.H    |  17 ++
 .../writers/vtk/vtkSurfaceWriter.C            | 194 ++++--------------
 .../writers/vtk/vtkSurfaceWriter.H            |  14 +-
 .../writers/vtk/vtkSurfaceWriterImpl.C        | 108 +++++++---
 8 files changed, 223 insertions(+), 204 deletions(-)

diff --git a/src/fileFormats/vtk/format/foamVtkAppendRawFormatter.C b/src/fileFormats/vtk/format/foamVtkAppendRawFormatter.C
index ee230f3d366..f8b7bcf29fd 100644
--- a/src/fileFormats/vtk/format/foamVtkAppendRawFormatter.C
+++ b/src/fileFormats/vtk/format/foamVtkAppendRawFormatter.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,6 +25,7 @@ License
 
 #include "foamVtkAppendRawFormatter.H"
 #include "foamVtkOutputOptions.H"
+#include <limits>
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -105,8 +106,21 @@ void Foam::vtk::appendRawFormatter::write(const float val)
 void Foam::vtk::appendRawFormatter::write(const double val)
 {
     // std::cerr<<"double as float=" << val << '\n';
-    float copy(val);
-    write(copy);
+
+    // Limit range of double to float conversion
+    if (val >= std::numeric_limits<float>::max())
+    {
+        write(std::numeric_limits<float>::max());
+    }
+    else if (val <= std::numeric_limits<float>::lowest())
+    {
+        write(std::numeric_limits<float>::lowest());
+    }
+    else
+    {
+        float copy(val);
+        write(copy);
+    }
 }
 
 
diff --git a/src/fileFormats/vtk/format/foamVtkAsciiFormatter.C b/src/fileFormats/vtk/format/foamVtkAsciiFormatter.C
index 1bcbc3b84cf..d220627ab71 100644
--- a/src/fileFormats/vtk/format/foamVtkAsciiFormatter.C
+++ b/src/fileFormats/vtk/format/foamVtkAsciiFormatter.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,6 +25,7 @@ License
 
 #include "foamVtkAsciiFormatter.H"
 #include "foamVtkOutputOptions.H"
+#include <limits>
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -139,8 +140,20 @@ void Foam::vtk::asciiFormatter::write(const float val)
 
 void Foam::vtk::asciiFormatter::write(const double val)
 {
-    next();
-    os()<< float(val);
+    // Limit range of double to float conversion
+    if (val >= std::numeric_limits<float>::max())
+    {
+        write(std::numeric_limits<float>::max());
+    }
+    else if (val <= std::numeric_limits<float>::lowest())
+    {
+        write(std::numeric_limits<float>::lowest());
+    }
+    else
+    {
+        float copy(val);
+        write(copy);
+    }
 }
 
 
diff --git a/src/fileFormats/vtk/format/foamVtkBase64Layer.C b/src/fileFormats/vtk/format/foamVtkBase64Layer.C
index 500a8dce9c0..fbdc7ad5257 100644
--- a/src/fileFormats/vtk/format/foamVtkBase64Layer.C
+++ b/src/fileFormats/vtk/format/foamVtkBase64Layer.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2017-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -24,6 +24,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "foamVtkBase64Layer.H"
+#include <limits>
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -96,8 +97,21 @@ void Foam::vtk::foamVtkBase64Layer::write(const float val)
 void Foam::vtk::foamVtkBase64Layer::write(const double val)
 {
     // std::cerr<<"double as float=" << val << '\n';
-    float copy(val);
-    write(copy);
+
+    // Limit range of double to float conversion
+    if (val >= std::numeric_limits<float>::max())
+    {
+        write(std::numeric_limits<float>::max());
+    }
+    else if (val <= std::numeric_limits<float>::lowest())
+    {
+        write(std::numeric_limits<float>::lowest());
+    }
+    else
+    {
+        float copy(val);
+        write(copy);
+    }
 }
 
 
diff --git a/src/fileFormats/vtk/format/foamVtkLegacyRawFormatter.C b/src/fileFormats/vtk/format/foamVtkLegacyRawFormatter.C
index 3582033eb2f..18f6890404c 100644
--- a/src/fileFormats/vtk/format/foamVtkLegacyRawFormatter.C
+++ b/src/fileFormats/vtk/format/foamVtkLegacyRawFormatter.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2016-2017 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -26,6 +26,7 @@ License
 #include "foamVtkLegacyRawFormatter.H"
 #include "foamVtkOutputOptions.H"
 #include "endian.H"
+#include <limits>
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -98,10 +99,7 @@ void Foam::vtk::legacyRawFormatter::write
 }
 
 
-void Foam::vtk::legacyRawFormatter::write
-(
-    const label val
-)
+void Foam::vtk::legacyRawFormatter::write(const label val)
 {
     // std::cerr<<"label is:" << sizeof(val) << '\n';
 
@@ -121,10 +119,7 @@ void Foam::vtk::legacyRawFormatter::write
 }
 
 
-void Foam::vtk::legacyRawFormatter::write
-(
-    const float val
-)
+void Foam::vtk::legacyRawFormatter::write(const float val)
 {
     // std::cerr<<"float is:" << sizeof(val) << '\n';
 
@@ -142,15 +137,25 @@ void Foam::vtk::legacyRawFormatter::write
 }
 
 
-void Foam::vtk::legacyRawFormatter::write
-(
-    const double val
-)
+void Foam::vtk::legacyRawFormatter::write(const double val)
 {
     // Legacy cannot support Float64 anyhow.
     // std::cerr<<"write double as float:" << val << '\n';
-    float copy(val);
-    write(copy);
+
+    // Limit range of double to float conversion
+    if (val >= std::numeric_limits<float>::max())
+    {
+        write(std::numeric_limits<float>::max());
+    }
+    else if (val <= std::numeric_limits<float>::lowest())
+    {
+        write(std::numeric_limits<float>::lowest());
+    }
+    else
+    {
+        float copy(val);
+        write(copy);
+    }
 }
 
 
diff --git a/src/fileFormats/vtk/output/foamVtkOutput.H b/src/fileFormats/vtk/output/foamVtkOutput.H
index 55f31cd91fc..2f4a0d2f7af 100644
--- a/src/fileFormats/vtk/output/foamVtkOutput.H
+++ b/src/fileFormats/vtk/output/foamVtkOutput.H
@@ -48,6 +48,7 @@ SourceFiles
 #include "foamVtkCore.H"
 #include "foamVtkFormatter.H"
 #include "floatScalar.H"
+#include "symmTensor.H"
 #include "IOstream.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -231,6 +232,22 @@ namespace legacy
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+//- Template specialization for symmTensor ordering
+template<>
+inline void write(vtk::formatter& fmt, const symmTensor& val)
+{
+    // symmTensor ( XX, XY, XZ, YY, YZ, ZZ )
+    // VTK order  ( XX, YY, ZZ, XY, YZ, XZ ) -> (0, 3, 5, 1, 4, 2)
+
+    fmt.write(component(val, 0)); // XX
+    fmt.write(component(val, 3)); // YY
+    fmt.write(component(val, 5)); // ZZ
+    fmt.write(component(val, 1)); // XY
+    fmt.write(component(val, 4)); // YZ
+    fmt.write(component(val, 2)); // XZ
+}
+
+
 } // End namespace vtk
 } // End namespace Foam
 
diff --git a/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriter.C b/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriter.C
index e7b117c6f02..8cde644ccbe 100644
--- a/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriter.C
+++ b/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriter.C
@@ -25,9 +25,8 @@ License
 
 #include "vtkSurfaceWriter.H"
 #include "foamVtkOutputOptions.H"
-
-#include "OFstream.H"
 #include "OSspecific.H"
+#include <fstream>
 #include "makeSurfaceWriterMethods.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -49,7 +48,7 @@ defineSurfaceWriterWriteFields(Foam::vtkSurfaceWriter);
 
 void Foam::vtkSurfaceWriter::writeGeometry
 (
-    Ostream& os,
+    vtk::formatter& format,
     const meshedSurf& surf,
     std::string title
 )
@@ -62,153 +61,40 @@ void Foam::vtkSurfaceWriter::writeGeometry
         title = "sampleSurface";
     }
 
-    // header
-    os
-        << "# vtk DataFile Version 2.0" << nl
-        << title.c_str() << nl
-        << "ASCII" << nl
-        << "DATASET POLYDATA" << nl;
+    vtk::legacy::fileHeader
+    (
+        format,
+        title,
+        vtk::fileTag::POLY_DATA
+    );
 
-    // Write vertex coords
-    os  << "POINTS " << points.size() << " double" << nl;
-    for (const point& pt : points)
-    {
-        os  << float(pt.x()) << ' '
-            << float(pt.y()) << ' '
-            << float(pt.z()) << nl;
-    }
-    os  << nl;
+    vtk::legacy::beginPoints(format.os(), points.size());
 
+    vtk::writeList(format, points);
+    format.flush();
 
     // Write faces
-    label nNodes = 0;
+    // connectivity count without additional storage (done internally)
+    label nConnectivity = 0;
     for (const face& f : faces)
     {
-        nNodes += f.size();
+        nConnectivity += f.size();
     }
 
-    os  << "POLYGONS " << faces.size() << ' '
-        << faces.size() + nNodes << nl;
-
-    for (const face& f : faces)
-    {
-        os  << f.size();
-        for (const label verti : f)
-        {
-            os  << ' ' << verti;
-        }
-        os  << nl;
-    }
-}
-
-
-namespace Foam
-{
-
-    template<>
-    void Foam::vtkSurfaceWriter::writeData
-    (
-        Ostream& os,
-        const Field<scalar>& values
-    )
-    {
-        os  << "1 " << values.size() << " double" << nl;
-
-        forAll(values, elemI)
-        {
-            if (elemI)
-            {
-                if (elemI % 10)
-                {
-                    os  << ' ';
-                }
-                else
-                {
-                    os  << nl;
-                }
-            }
-
-            os  << float(values[elemI]);
-        }
-        os  << nl;
-    }
-
-
-    template<>
-    void Foam::vtkSurfaceWriter::writeData
-    (
-        Ostream& os,
-        const Field<vector>& values
-    )
-    {
-        os  << "3 " << values.size() << " double" << nl;
-
-        for (const vector& v : values)
-        {
-            os  << float(v[0]) << ' '
-                << float(v[1]) << ' '
-                << float(v[2]) << nl;
-        }
-    }
-
-
-    template<>
-    void Foam::vtkSurfaceWriter::writeData
+    vtk::legacy::beginPolys
     (
-        Ostream& os,
-        const Field<sphericalTensor>& values
-    )
-    {
-        os  << "1 " << values.size() << " double" << nl;
-
-        for (const sphericalTensor& v : values)
-        {
-            os  << float(v[0]) << nl;
-        }
-    }
-
+        format.os(),
+        faces.size(),
+        nConnectivity
+    );
 
-    template<>
-    void Foam::vtkSurfaceWriter::writeData
-    (
-        Ostream& os,
-        const Field<symmTensor>& values
-    )
+    for (const face& f : faces)
     {
-        os  << "6 " << values.size() << " double" << nl;
-
-        // symmTensor ( XX, XY, XZ, YY, YZ, ZZ )
-        // VTK order  ( XX, YY, ZZ, XY, YZ, XZ ) -> (0, 3, 5, 1, 4, 2)
-
-        for (const symmTensor& v : values)
-        {
-            os  << float(v[0]) << ' ' << float(v[3]) << ' ' << float(v[5])
-                << ' '
-                << float(v[1]) << ' ' << float(v[4]) << ' ' << float(v[2])
-                << nl;
-        }
+        format.write(f.size());  // The size prefix
+        vtk::writeList(format, f);
     }
 
-
-    template<>
-    void Foam::vtkSurfaceWriter::writeData
-    (
-        Ostream& os,
-        const Field<tensor>& values
-    )
-    {
-        os  << "9 " << values.size() << " double" << nl;
-
-        for (const tensor& v : values)
-        {
-            os  << float(v[0]) << ' ' << float(v[1]) << ' ' << float(v[2])
-                << ' '
-                << float(v[3]) << ' ' << float(v[4]) << ' ' << float(v[5])
-                << ' '
-                << float(v[6]) << ' ' << float(v[7]) << ' ' << float(v[8])
-                << nl;
-        }
-    }
+    format.flush();
 }
 
 
@@ -235,11 +121,14 @@ Foam::vtkSurfaceWriter::vtkSurfaceWriter(const dictionary& options)
 
     vtk::outputOptions opts(static_cast<vtk::formatType>(fmtType_));
 
-    opts.ascii
-    (
-        options.found("format")
-     && (IOstream::formatEnum(options.get<word>("format")) == IOstream::ASCII)
-    );
+    const word formatName = options.lookupOrDefault<word>("format", "");
+    if (formatName.size())
+    {
+        opts.ascii
+        (
+            IOstream::formatEnum(formatName) == IOstream::ASCII
+        );
+    }
 
     if (options.lookupOrDefault("legacy", false))
     {
@@ -272,22 +161,29 @@ Foam::fileName Foam::vtkSurfaceWriter::write
 {
     // geometry:  rootdir/time/surfaceName.{vtk|vtp}
 
+    fileName outputFile(outputDir/surfaceName + ".vtk");
+
     if (!isDir(outputDir))
     {
         mkDir(outputDir);
     }
-
-    OFstream os(outputDir/surfaceName + ".vtk");
-    os.precision(precision_);
-
     if (verbose)
     {
-        Info<< "Writing geometry to " << os.name() << endl;
+        Info<< "Writing geometry to " << outputFile << endl;
     }
 
-    writeGeometry(os, surf, surfaceName);
+    // As vtk::outputOptions
+    vtk::outputOptions opts(static_cast<vtk::formatType>(fmtType_));
+    opts.legacy(true);
+    opts.precision(precision_);
+
+    std::ofstream os(outputFile);
+
+    auto format = opts.newFormatter(os);
+
+    writeGeometry(*format, surf, surfaceName);
 
-    return os.name();
+    return outputFile;
 }
 
 
diff --git a/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriter.H b/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriter.H
index 2d4472f5a48..06f2ad70534 100644
--- a/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriter.H
+++ b/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriter.H
@@ -85,6 +85,12 @@ SourceFiles
 namespace Foam
 {
 
+namespace vtk
+{
+// Forward declarations
+class formatter;
+}
+
 /*---------------------------------------------------------------------------*\
                       Class vtkSurfaceWriter Declaration
 \*---------------------------------------------------------------------------*/
@@ -107,13 +113,17 @@ class vtkSurfaceWriter
 
         static void writeGeometry
         (
-            Ostream& os,
+            vtk::formatter& format,
             const meshedSurf& surf,
             std::string title = ""
         );
 
         template<class Type>
-        static void writeData(Ostream&, const Field<Type>&);
+        static void writeField
+        (
+            vtk::formatter& format,
+            const Field<Type>& values
+        );
 
 
         //- Templated write operation
diff --git a/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriterImpl.C b/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriterImpl.C
index ac7974ed782..d29931a36ea 100644
--- a/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriterImpl.C
+++ b/src/sampling/sampledSurface/writers/vtk/vtkSurfaceWriterImpl.C
@@ -23,28 +23,69 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "OFstream.H"
+#include "foamVtkOutputOptions.H"
 #include "OSspecific.H"
+#include <fstream>
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    template<class Type>
+    static inline void beginFloatField
+    (
+        vtk::formatter& format,
+        const word& fieldName,
+        const Field<Type>& values
+    )
+    {
+        // Use 'double' instead of 'float' for ASCII output (issue #891)
+        if (format.opts().ascii())
+        {
+            format.os()
+                << fieldName << ' '
+                << int(pTraits<Type>::nComponents) << ' '
+                << values.size() << " double" << nl;
+        }
+        else
+        {
+            format.os()
+                << fieldName << ' '
+                << int(pTraits<Type>::nComponents) << ' '
+                << values.size() << " float" << nl;
+        }
+    }
+}
+
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
+// // Unspecialized (unknown) data type - map as zeros
+// void Foam::vtkSurfaceWriter::writeZeros
+// (
+//     vtk::formatter& format,
+//     const label count
+// )
+// {
+//     const float val(0);
+//
+//     for (label i=0; i < count; ++i)
+//     {
+//         format.write(val);
+//     }
+//     format.flush();
+// }
+
+
 template<class Type>
-void Foam::vtkSurfaceWriter::writeData
+void Foam::vtkSurfaceWriter::writeField
 (
-    Ostream& os,
+    vtk::formatter& format,
     const Field<Type>& values
 )
 {
-    // Unspecialized (unknown) data type - map as zeros
-
-    const label len = values.size();
-
-    os  << "1 " << len << " double" << nl;
-
-    for (label i=0; i < len; ++i)
-    {
-        os  << float(0) << nl;
-    }
+    vtk::writeList(format, values);
+    format.flush();
 }
 
 
@@ -62,39 +103,48 @@ Foam::fileName Foam::vtkSurfaceWriter::writeTemplate
 {
     // field:  rootdir/time/<field>_surfaceName.{vtk|vtp}
 
+    fileName outputFile(outputDir/fieldName + '_' + surfaceName + ".vtk");
+
     if (!isDir(outputDir))
     {
         mkDir(outputDir);
     }
-
-    OFstream os(outputDir/fieldName + '_' + surfaceName + ".vtk");
-    os.precision(precision_);
-
     if (verbose)
     {
-        Info<< "Writing field " << fieldName << " to " << os.name() << endl;
+        Info<< "Writing field " << fieldName << " to "
+            << outputFile << endl;
     }
 
-    writeGeometry(os, surf);
+    // As vtk::outputOptions
+    vtk::outputOptions opts(static_cast<vtk::formatType>(fmtType_));
+    opts.legacy(true);
+    opts.precision(precision_);
+
+    std::ofstream os(outputFile);
+
+    auto format = opts.newFormatter(os);
+
+    writeGeometry(*format, surf);
 
-    // start writing data
     if (isNodeValues)
     {
-        os  << "POINT_DATA ";
+        format->os()
+            << "POINT_DATA "
+            << values.size() << nl
+            << "FIELD attributes 1" << nl;
     }
     else
     {
-        os  << "CELL_DATA ";
+        format->os()
+            << "CELL_DATA "
+            << values.size() << nl
+            << "FIELD attributes 1" << nl;
     }
 
-    os  << values.size() << nl
-        << "FIELD attributes 1" << nl
-        << fieldName << " ";
-
-    // Write data
-    writeData(os, values);
+    beginFloatField(*format, fieldName, values);
+    writeField(*format, values);
 
-    return os.name();
+    return outputFile;
 }
 
 
-- 
GitLab