diff --git a/dune/vtk/filewriter.hh b/dune/vtk/filewriter.hh index 5a8472bc33f7ee9dc7d9de82857ef6a2b80643dc..ecddbd2b0f6f72dcebb8f2f249af0e278e1ed5c0 100644 --- a/dune/vtk/filewriter.hh +++ b/dune/vtk/filewriter.hh @@ -14,7 +14,7 @@ namespace Dune virtual ~FileWriter () = default; /// Write to file given by `filename` and (optionally) store additional data in `dataDir` - virtual void write (std::string const& filename, Std::optional<std::string> dataDir = {}) const = 0; + virtual std::string write (std::string const& filename, Std::optional<std::string> dataDir = {}) const = 0; }; } // end namespace Dune diff --git a/dune/vtk/pvdwriter.hh b/dune/vtk/pvdwriter.hh index 3faec46272554844efa38672007df182a3616b8e..38fef8f8055847aec5b8cf98642b8fb11b1337c6 100644 --- a/dune/vtk/pvdwriter.hh +++ b/dune/vtk/pvdwriter.hh @@ -52,7 +52,7 @@ namespace Dune * \param dir (Ignored) Timestep files are already written and their filenames are * stored in \ref timesteps_. **/ - virtual void write (std::string const& fn, Std::optional<std::string> dir = {}) const override; + virtual std::string write (std::string const& fn, Std::optional<std::string> dir = {}) const override; /// Attach point data to the writer, \see VtkFunction for possible arguments template <class Function, class... Args> diff --git a/dune/vtk/pvdwriter.impl.hh b/dune/vtk/pvdwriter.impl.hh index 26ff028ea050bbefc17b30b39420f1daed41c30d..347eaeb53c520e98962e80e8c0729367a19ec46e 100644 --- a/dune/vtk/pvdwriter.impl.hh +++ b/dune/vtk/pvdwriter.impl.hh @@ -49,7 +49,7 @@ void PvdWriter<W> template <class W> -void PvdWriter<W> +std::string PvdWriter<W> ::write (std::string const& fn, Std::optional<std::string> /*dir*/) const { auto p = filesystem::path(fn); @@ -57,9 +57,12 @@ void PvdWriter<W> p.remove_filename(); p /= name.string(); + std::string outputFilename; + int commRank = vtkWriter_.comm().rank(); if (commRank == 0) { - std::ofstream out(p.string() + ".pvd", std::ios_base::ate | std::ios::binary); + outputFilename = p.string() + ".pvd"; + std::ofstream out(outputFilename, std::ios_base::ate | std::ios::binary); assert(out.is_open()); out.imbue(std::locale::classic()); @@ -69,6 +72,8 @@ void PvdWriter<W> writeFile(out); } + + return outputFilename; } diff --git a/dune/vtk/vtktimeserieswriter.hh b/dune/vtk/vtktimeserieswriter.hh index 83a3d4cf539a6d2ead06f6ed0d12f8675c3c61aa..21bcd6c20399d14e1e07bf11fb5ee437420e429f 100644 --- a/dune/vtk/vtktimeserieswriter.hh +++ b/dune/vtk/vtktimeserieswriter.hh @@ -76,8 +76,10 @@ namespace Dune * * \param fn Filename of the Timeseries file. May contain a directory and any file extension. * \param dir The optional parameter specifies the directory of the partition files. + * + * \returns File name of the actual written file **/ - virtual void write (std::string const& fn, Std::optional<std::string> dir = {}) const override; + virtual std::string write (std::string const& fn, Std::optional<std::string> dir = {}) const override; /// Attach point data to the writer, \see VtkFunction for possible arguments template <class Function, class... Args> diff --git a/dune/vtk/vtktimeserieswriter.impl.hh b/dune/vtk/vtktimeserieswriter.impl.hh index 52abdec86b63be238d209aa7a0482f903e75790d..96cacb967f83d1fd1043b8d8ef552e0ce6e5fa61 100644 --- a/dune/vtk/vtktimeserieswriter.impl.hh +++ b/dune/vtk/vtktimeserieswriter.impl.hh @@ -74,7 +74,7 @@ void VtkTimeseriesWriter<W> template <class W> -void VtkTimeseriesWriter<W> +std::string VtkTimeseriesWriter<W> ::write (std::string const& fn, Std::optional<std::string> dir) const { assert( initialized_ ); @@ -96,9 +96,11 @@ void VtkTimeseriesWriter<W> if (commSize > 1) serial_fn += "_p" + std::to_string(commRank); + std::string outputFilename; + { // write serial file - std::ofstream serial_out(serial_fn + "." + vtkWriter_.getFileExtension(), - std::ios_base::ate | std::ios::binary); + outputFilename = serial_fn + "." + vtkWriter_.getFileExtension(); + std::ofstream serial_out(outputFilename, std::ios_base::ate | std::ios::binary); assert(serial_out.is_open()); serial_out.imbue(std::locale::classic()); @@ -111,8 +113,8 @@ void VtkTimeseriesWriter<W> if (commSize > 1 && commRank == 0) { // write parallel file - std::ofstream parallel_out(parallel_fn + ".p" + vtkWriter_.getFileExtension(), - std::ios_base::ate | std::ios::binary); + outputFilename = parallel_fn + ".p" + vtkWriter_.getFileExtension(); + std::ofstream parallel_out(outputFilename, std::ios_base::ate | std::ios::binary); assert(parallel_out.is_open()); parallel_out.imbue(std::locale::classic()); @@ -122,6 +124,8 @@ void VtkTimeseriesWriter<W> vtkWriter_.writeTimeseriesParallelFile(parallel_out, rel_fn, commSize, timesteps_); } + + return outputFilename; } } // end namespace Dune diff --git a/dune/vtk/vtkwriterinterface.hh b/dune/vtk/vtkwriterinterface.hh index 33121d2525c4bdf548fb24dcf8f95273cc72b326..97117ce228db631dee5414c596aad56e6446161c 100644 --- a/dune/vtk/vtkwriterinterface.hh +++ b/dune/vtk/vtkwriterinterface.hh @@ -67,12 +67,7 @@ namespace Dune , format_(format) , datatype_(datatype) { -#if !HAVE_VTK_ZLIB - if (format_ == Vtk::COMPRESSED) { - std::cout << "Dune is compiled without compression. Falling back to BINARY VTK output!\n"; - format_ = Vtk::BINARY; - } -#endif + setFormat(format); } @@ -80,8 +75,10 @@ namespace Dune /** * \param fn Filename of the VTK file. May contain a directory and any file extension. * \param dir The optional parameter specifies the directory of the partition files for parallel writes. + * + * \returns File name that is actually written. **/ - virtual void write (std::string const& fn, Std::optional<std::string> dir = {}) const override; + virtual std::string write (std::string const& fn, Std::optional<std::string> dir = {}) const override; /// \brief Attach point data to the writer /** @@ -112,6 +109,26 @@ namespace Dune return *this; } + + // Sets the VTK file format + void setFormat (Vtk::FormatTypes format) + { + format_ = format; + +#if !HAVE_VTK_ZLIB + if (format_ == Vtk::COMPRESSED) { + std::cout << "Dune is compiled without compression. Falling back to BINARY VTK output!\n"; + format_ = Vtk::BINARY; + } +#endif + } + + // Sets the global datatype used for coordinates and other global float values + void setDatatype (Vtk::DataTypes datatype) + { + datatype_ = datatype; + } + private: /// Write a serial VTK file in Unstructured format virtual void writeSerialFile (std::ofstream& out) const = 0; diff --git a/dune/vtk/vtkwriterinterface.impl.hh b/dune/vtk/vtkwriterinterface.impl.hh index 3e2fe2f3de7656b1b4a3b42d2660ede7438da25b..f0e7715b9d5f98998ad0c89a66763140945ac3ae 100644 --- a/dune/vtk/vtkwriterinterface.impl.hh +++ b/dune/vtk/vtkwriterinterface.impl.hh @@ -23,7 +23,7 @@ namespace Dune { template <class GV, class DC> -void VtkWriterInterface<GV,DC> +std::string VtkWriterInterface<GV,DC> ::write (std::string const& fn, Std::optional<std::string> dir) const { dataCollector_->update(); @@ -43,8 +43,11 @@ void VtkWriterInterface<GV,DC> if (comm().size() > 1) serial_fn += "_p" + std::to_string(comm().rank()); + std::string outputFilename; + { // write serial file - std::ofstream serial_out(serial_fn + "." + fileExtension(), std::ios_base::ate | std::ios::binary); + outputFilename = serial_fn + "." + fileExtension(); + std::ofstream serial_out(outputFilename, std::ios_base::ate | std::ios::binary); assert(serial_out.is_open()); serial_out.imbue(std::locale::classic()); @@ -56,8 +59,9 @@ void VtkWriterInterface<GV,DC> } if (comm().size() > 1 && comm().rank() == 0) { - // write parallel file - std::ofstream parallel_out(parallel_fn + ".p" + fileExtension(), std::ios_base::ate | std::ios::binary); + // write parallel filee + outputFilename = parallel_fn + ".p" + fileExtension(); + std::ofstream parallel_out(outputFilename, std::ios_base::ate | std::ios::binary); assert(parallel_out.is_open()); parallel_out.imbue(std::locale::classic()); @@ -67,6 +71,8 @@ void VtkWriterInterface<GV,DC> writeParallelFile(parallel_out, rel_fn, comm().size()); } + + return outputFilename; } diff --git a/dune/vtk/writers/CMakeLists.txt b/dune/vtk/writers/CMakeLists.txt index 779237467679ed664f32981dfa76537d2d2cb4fb..f32eb18971cfa16f619bd627ce355d5eb0935d99 100644 --- a/dune/vtk/writers/CMakeLists.txt +++ b/dune/vtk/writers/CMakeLists.txt @@ -9,3 +9,5 @@ install(FILES vtkunstructuredgridwriter.hh vtkunstructuredgridwriter.impl.hh DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dune/vtkwriter/writers) + +add_subdirectory(test) \ No newline at end of file diff --git a/dune/vtk/writers/test/CMakeLists.txt b/dune/vtk/writers/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..5197f1d7423166aac3e5a16fde2c0b36ac7eb5c9 --- /dev/null +++ b/dune/vtk/writers/test/CMakeLists.txt @@ -0,0 +1,2 @@ +dune_add_test(SOURCES test-vtkwriter.cc + LINK_LIBRARIES dunevtk) \ No newline at end of file diff --git a/dune/vtk/writers/test/checkvtkfile.hh b/dune/vtk/writers/test/checkvtkfile.hh new file mode 100644 index 0000000000000000000000000000000000000000..413adb278cc3a7140165006df5847e7d565c1278 --- /dev/null +++ b/dune/vtk/writers/test/checkvtkfile.hh @@ -0,0 +1,181 @@ +#ifndef DUNE_VTK_TEST_CHECKVTKFILE_HH +#define DUNE_VTK_TEST_CHECKVTKFILE_HH + +#include <cstddef> +#include <cstdlib> +#include <iostream> +#include <iomanip> +#include <ios> +#include <iostream> +#include <ostream> +#include <set> +#include <sstream> +#include <string> + +#include <sys/wait.h> + +#include <dune/common/exceptions.hh> +#include <dune/common/test/testsuite.hh> + +namespace Dune { +namespace Impl { + +// quote so the result can be used inside '...' in python +// quotes not included in the result +inline std::string pyq(const std::string &s) +{ + std::ostringstream result; + for(std::size_t i = 0; i < s.size(); ++i) + { + char c = s[i]; + switch(c) { + case '\'': result << "\\'"; break; + case '\\': result << "\\\\"; break; + case '\n': result << "\\n"; break; + default: + if(c < 32 || c >= 127) + result << "\\x" << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>(c); + else + result << c; + } + } + return result.str(); +} + +// quote so the result can be used inside '...' in the bourne shell +// quotes not included in the result +inline std::string shq(const std::string &s) +{ + std::ostringstream result; + bool pend = false; + for(std::size_t i = 0; i < s.size(); ++i) + { + char c = s[i]; + switch(c) { + case '\0': DUNE_THROW(Dune::NotImplemented, + "Can't pass \\0 through the shell"); + case '\'': result << (pend ? "" : "'") << "\\'"; pend = true; break; + default: result << (pend ? "'" : "") << c; pend = false; break; + } + } + if(pend) result << "'"; + return result.str(); +} + +inline int runShell(const std::string &code) +{ + int result = std::system(code.c_str()); + // Avoid returning anything that is a multiple of 256, unless the return + // value was 0. This way the return value can be directly used as an + // argument to exit(), which usually interprets its argument modulo 256. + if(WIFEXITED(result)) + return WEXITSTATUS(result); + if(WIFSIGNALED(result)) + return WTERMSIG(result) + 256; + else + return 513; +} + +inline int runPython(const std::string &code) +{ + return runShell("python -c '"+shq(code)+"'"); +} + +inline bool is_suffix(const std::string &s, const std::string &suffix) +{ + return s.size() >= suffix.size() && + s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +inline bool havePythonVTK() +{ + static const bool result = [] { + // This check is invoked only once, even in a multithreading environment, + // since it is invoked in the initializer of a static variable. + if(runPython("from vtk import *") == 0) + return true; + std::cerr << "warning: python or python vtk module not available. This " + << "will result in skipped tests, since we cannot check that " + << "vtk can read the files we wrote." << std::endl; + return false; + } (); + + return result; +} + +inline std::string pythonVTKReader(const std::string& filename) +{ + if (is_suffix(filename, ".vtu")) return "vtkXMLUnstructuredGridReader"; + else if(is_suffix(filename, ".pvtu")) return "vtkXMLPUnstructuredGridReader"; + else if(is_suffix(filename, ".vts")) return "vtkXMLStructuredGridReader"; + else if(is_suffix(filename, ".pvts")) return "vtkXMLPStructuredGridReader"; + else if(is_suffix(filename, ".vtr")) return "vtkXMLRectilinearGridReader"; + else if(is_suffix(filename, ".pvtr")) return "vtkXMLPRectilinearGridReader"; + else if(is_suffix(filename, ".vti")) return "vtkXMLImageDataReader"; + else if(is_suffix(filename, ".pvti")) return "vtkXMLPImageDataReader"; + else DUNE_THROW(Dune::NotImplemented, + "Unknown vtk file extension: " << filename); +} + +} /* namespace Impl */ + +class VTKChecker +{ +public: + void push(const std::string& file) + { + auto res = files_.insert(file); + if (not res.second) { + testSuite_.check(false, "VTKChecker") + << "'" << file << "' was added multiple times"; + } + } + + int check() + { + if (not Impl::havePythonVTK()) { + return 77; + } + else if (not files_.empty()) { + const int result = Impl::runPython(generatePythonCode()); + testSuite_.check(result == 0); + } + return testSuite_.exit(); + } + + const TestSuite& testSuite() const + { + return testSuite_; + } + +private: + std::string generatePythonCode() const + { + std::stringstream code; + + code << "from vtk import *\n" + << "import sys\n" + << "passed = True\n"; + + for (const auto& file : files_) { + code << "reader = " << Impl::pythonVTKReader(file) << "()\n" + << "reader.SetFileName('" << Impl::pyq(file) << "')\n" + << "reader.Update()\n" + << "if (not (reader.GetOutput().GetNumberOfCells() > 0)):\n" + << " print('ERROR in {}'.format('" << Impl::pyq(file) << "'))\n" + << " passed = False\n"; + } + + code << "sys.exit(0 if passed else 1)\n"; + + return code.str(); + } + + std::set< std::string > files_; + TestSuite testSuite_; +}; + +} /* namespace Dune */ + +#endif // DUNE_VTK_TEST_CHECKVTKFILE_HH diff --git a/dune/vtk/writers/test/test-vtkwriter.cc b/dune/vtk/writers/test/test-vtkwriter.cc new file mode 100644 index 0000000000000000000000000000000000000000..127e46cf9ae10c0e109d96dd7c75fc82e6c8a675 --- /dev/null +++ b/dune/vtk/writers/test/test-vtkwriter.cc @@ -0,0 +1,137 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: + +#if HAVE_CONFIG_H +#include "config.h" // autoconf defines, needed by the dune headers +#endif + +#include <algorithm> +#include <iostream> +#include <ostream> +#include <sstream> +#include <string> +#include <vector> + +#include <dune/common/fvector.hh> +#include <dune/common/parallel/mpihelper.hh> +#include <dune/functions/gridfunctions/analyticgridviewfunction.hh> +#include <dune/grid/yaspgrid.hh> +#if HAVE_UG + #include <dune/grid/uggrid.hh> +#endif +#include <dune/vtk/vtkwriter.hh> + +#include "checkvtkfile.hh" + +// accumulate exit status +void acc (int &accresult, int result) +{ + if (accresult == 0 || (accresult == 77 && result != 0)) + accresult = result; +} + +struct Acc +{ + int operator() (int v1, int v2) const + { + acc(v1, v2); + return v1; + } +}; + +template <class GridView> +int testGridView (std::string prefix, Dune::VTKChecker& vtkChecker, GridView const& gridView) +{ + enum { dim = GridView :: dimension }; + + Dune::VtkWriter<GridView> vtk(gridView); + + auto f1 = Dune::Functions::makeAnalyticGridViewFunction([](const auto& x) { return std::sin(x.two_norm()); },gridView); + vtk.addCellData(f1, "scalar-valued lambda"); + + auto f2 = Dune::Functions::makeAnalyticGridViewFunction([](const auto& x) { return x; },gridView); + vtk.addPointData(f2, "vector-valued lambda"); + + int result = 0; + + // ASCII files + { + vtk.setFormat(Dune::Vtk::ASCII); + std::string name = vtk.write(prefix + "_ascii"); + if (gridView.comm().rank() == 0) vtkChecker.push(name); + } + + // BINARY files + { + vtk.setFormat(Dune::Vtk::BINARY); + std::string name = vtk.write(prefix + "_binary"); + if (gridView.comm().rank() == 0) vtkChecker.push(name); + } + + // COMPRESSED files + { + vtk.setFormat(Dune::Vtk::COMPRESSED); + std::string name = vtk.write(prefix + "_compressed"); + if (gridView.comm().rank() == 0) vtkChecker.push(name); + } + + return result; +} + +template <class Grid> +int testGrid (std::string prefix, Dune::VTKChecker& vtkChecker, Grid& grid) +{ + if (grid.comm().rank() == 0) + std::cout << "vtkCheck(" << prefix << ")" << std::endl; + + grid.globalRefine(1); + + int result = 0; + + acc(result, testGridView( prefix + "_leaf", vtkChecker, grid.leafGridView() )); + acc(result, testGridView( prefix + "_level0", vtkChecker, grid.levelGridView(0) )); + acc(result, testGridView( prefix + "_level1", vtkChecker, grid.levelGridView(grid.maxLevel()) )); + + return result; +} + +int main (int argc, char** argv) +{ + using namespace Dune; + const MPIHelper& mpiHelper = MPIHelper::instance(argc, argv); + + if (mpiHelper.rank() == 0) + std::cout << "vtktest: MPI_Comm_size == " << mpiHelper.size() << std::endl; + + int result = 0; // pass by default + + VTKChecker vtkChecker; + + YaspGrid<1> yaspGrid1({1.0}, {8}); + YaspGrid<2> yaspGrid2({1.0,2.0}, {8,4}); + YaspGrid<3> yaspGrid3({1.0,2.0,3.0}, {8,4,4}); + + acc(result, testGrid("yaspgrid_1d", vtkChecker, yaspGrid1)); + acc(result, testGrid("yaspgrid_2d", vtkChecker, yaspGrid2)); + acc(result, testGrid("yaspgrid_3d", vtkChecker, yaspGrid3)); + +#if HAVE_UG + auto ugGrid2a = StructuredGridFactory<UGGrid<2>>::createSimplexGrid({0.0,0.0}, {1.0,2.0}, {2u,4u}); + auto ugGrid3a = StructuredGridFactory<UGGrid<3>>::createSimplexGrid({0.0,0.0,0.0}, {1.0,2.0,3.0}, {2u,4u,6u}); + + acc(result, testGrid("uggrid_simplex_2d", vtkChecker, *ugGrid2a)); + acc(result, testGrid("uggrid_simplex_3d", vtkChecker, *ugGrid3a)); + + auto ugGrid2b = StructuredGridFactory<UGGrid<2>>::createCubeGrid({0.0,0.0}, {1.0,2.0}, {2u,4u}); + auto ugGrid3b = StructuredGridFactory<UGGrid<3>>::createCubeGrid({0.0,0.0,0.0}, {1.0,2.0,3.0}, {2u,4u,6u}); + + acc(result, testGrid("uggrid_cube_2d", vtkChecker, *ugGrid2b)); + acc(result, testGrid("uggrid_cube_3d", vtkChecker, *ugGrid3b)); +#endif + + acc(result, vtkChecker.check()); + + mpiHelper.getCollectiveCommunication().allreduce<Acc>(&result, 1); + + return result; +}