Commit 90280727 authored by Jakub Klinkovský's avatar Jakub Klinkovský
Browse files

Refactored PVTUWriter from tnl-decompose-mesh into its own class

parent a98f773f
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -160,6 +160,10 @@ if( DEFINED ENV{CI_JOB_NAME} OR ${CMAKE_GENERATOR} STREQUAL "Ninja" )
   endif()
endif()

# add the filesystem library to all targets
# https://en.cppreference.com/w/cpp/filesystem
link_libraries( stdc++fs )

# gtest has to be built before we add the MPI flags
if( ${WITH_TESTS} OR ${WITH_MATRIX_TESTS} )
   enable_testing()
+107 −0
Original line number Diff line number Diff line
/***************************************************************************
                          PVTUWriter.h  -  description
                             -------------------
    begin                : Apr 17, 2020
    copyright            : (C) 2020 by Tomas Oberhuber et al.
    email                : tomas.oberhuber@fjfi.cvut.cz
 ***************************************************************************/

/* See Copyright Notice in tnl/Copyright */

// Implemented by: Jakub Klinkovský

#pragma once

#include <TNL/Meshes/Writers/VTUWriter.h>
#include <TNL/Meshes/DistributedMeshes/DistributedMesh.h>

namespace TNL {
namespace Meshes {
namespace Writers {

// NOTE: Mesh should be the local mesh type, not DistributedMesh
template< typename Mesh >
class PVTUWriter
{
   using HeaderType = std::uint64_t;
public:
   using MeshRealType = typename Mesh::RealType;
   using IndexType = typename Mesh::GlobalIndexType;

   PVTUWriter() = delete;

   PVTUWriter( std::ostream& str, VTK::FileFormat format = VTK::FileFormat::zlib_compressed )
   : str(str), format(format)
   {}

   // If desired, cycle and time of the simulation can put into the file. This follows the instructions at
   // http://www.visitusers.org/index.php?title=Time_and_Cycle_in_VTK_files
   void writeMetadata( std::int32_t cycle = -1, double time = -1 );

   template< int EntityDimension = Mesh::getMeshDimension() >
   void writeEntities( const DistributedMeshes::DistributedMesh< Mesh >& distributedMesh );

   template< int EntityDimension = Mesh::getMeshDimension() >
   void writeEntities( const Mesh& mesh,
                       const unsigned GhostLevel = 0,
                       const unsigned MinCommonVertices = 0 );

   template< typename ValueType >
   void writePPointData( const String& name,
                         const int numberOfComponents = 1 );

   template< typename ValueType >
   void writePCellData( const String& name,
                        const int numberOfComponents = 1 );

   template< typename ValueType >
   void writePDataArray( const String& name,
                         const int numberOfComponents = 1 );

   // add a single piece and return its source path
   // (useful for sequential writing, e.g. from tnl-decompose-mesh)
   std::string addPiece( const String& mainFileName,
                         const unsigned subdomainIndex );

   // add all pieces and return the source path for the current rank
   // (useful for parallel writing)
   template< typename Communicator >
   std::string addPiece( const String& mainFileName,
                         const typename Communicator::CommunicationGroup group );

   ~PVTUWriter();

protected:
   void writeHeader( const unsigned GhostLevel = 0,
                     const unsigned MinCommonVertices = 0 );

   void writePoints( const Mesh& mesh );

   void writeFooter();

   std::ostream& str;

   VTK::FileFormat format;

   // indicator if the <VTKFile> tag is open
   bool vtkfileOpen = false;

   // indicators if a <PCellData> tag is open or closed
   bool pCellDataOpen = false;
   bool pCellDataClosed = false;

   // indicators if a <PPointData> tag is open or closed
   bool pPointDataOpen = false;
   bool pPointDataClosed = false;

   void openPCellData();
   void closePCellData();
   void openPPointData();
   void closePPointData();
};

} // namespace Writers
} // namespace Meshes
} // namespace TNL

#include <TNL/Meshes/Writers/PVTUWriter.hpp>
+255 −0
Original line number Diff line number Diff line
/***************************************************************************
                          PVTUWriter.hpp  -  description
                             -------------------
    begin                : Apr 17, 2020
    copyright            : (C) 2020 by Tomas Oberhuber et al.
    email                : tomas.oberhuber@fjfi.cvut.cz
 ***************************************************************************/

/* See Copyright Notice in tnl/Copyright */

// Implemented by: Jakub Klinkovský

#pragma once

#include <experimental/filesystem>

#include <TNL/Meshes/Writers/PVTUWriter.h>

namespace TNL {
namespace Meshes {
namespace Writers {

template< typename Mesh >
void
PVTUWriter< Mesh >::writeMetadata( int cycle, double time )
{
   if( ! vtkfileOpen )
      throw std::logic_error("writeMetadata has to be called after writeEntities in case of the PVTU format, otherwise header attributes would be left unset." );

   if( cycle >= 0 || time >= 0 )
      str << "<FieldData>\n";

   if( cycle >= 0 ) {
      str << "<DataArray type=\"Int32\" Name=\"CYCLE\" NumberOfTuples=\"1\" format=\"ascii\">"
          << cycle << "</DataArray>\n";
   }
   if( time >= 0 ) {
      str.precision( std::numeric_limits< double >::digits10 );
      str << "<DataArray type=\"Float64\" Name=\"TIME\" NumberOfTuples=\"1\" format=\"ascii\">"
          << time << "</DataArray>\n";
   }

   if( cycle >= 0 || time >= 0 )
      str << "</FieldData>\n";
}

template< typename Mesh >
   template< int EntityDimension >
void
PVTUWriter< Mesh >::writeEntities( const DistributedMeshes::DistributedMesh< Mesh >& distributedMesh )
{
   writeEntities< EntityDimension >( distributedMesh.getLocalMesh(), distributedMesh.getGhostLevels(), Mesh::Config::dualGraphMinCommonVertices );
}

template< typename Mesh >
   template< int EntityDimension >
void
PVTUWriter< Mesh >::writeEntities( const Mesh& mesh,
                                   const unsigned GhostLevel,
                                   const unsigned MinCommonVertices )
{
   if( ! vtkfileOpen )
      writeHeader( GhostLevel, MinCommonVertices );

   // write points
   writePoints( mesh );
}

template< typename Mesh >
   template< typename ValueType >
void
PVTUWriter< Mesh >::writePPointData( const String& name,
                                     const int numberOfComponents )
{
   if( ! vtkfileOpen )
      throw std::logic_error("The VTKFile has not been opened yet - call writeEntities first.");
   openPPointData();
   writePDataArray< ValueType >( name, numberOfComponents );
}

template< typename Mesh >
   template< typename ValueType >
void
PVTUWriter< Mesh >::writePCellData( const String& name,
                                    const int numberOfComponents )
{
   if( ! vtkfileOpen )
      throw std::logic_error("The VTKFile has not been opened yet - call writeEntities first.");
   openPCellData();
   writePDataArray< ValueType >( name, numberOfComponents );
}

template< typename Mesh >
   template< typename ValueType >
void
PVTUWriter< Mesh >::writePDataArray( const String& name,
                                     const int numberOfComponents )
{
   if( numberOfComponents != 0 && numberOfComponents != 1 && numberOfComponents != 3 )
      throw std::logic_error("Unsupported numberOfComponents parameter: " + std::to_string(numberOfComponents));

   str << "<PDataArray type=\"" << VTK::getTypeName( ValueType{} ) << "\" ";
   str << "Name=\"" << name << "\" ";
   str << "NumberOfComponents=\"" << numberOfComponents << "\"/>\n";
}

template< typename Mesh >
std::string
PVTUWriter< Mesh >::addPiece( const String& mainFileName,
                              const unsigned subdomainIndex )
{
   if( ! mainFileName.endsWith( ".pvtu" ) )
      throw std::logic_error("The mainFileName parameter must be the name of the "
                             ".pvtu file (i.e., it must have the .pvtu suffix).");

   // close PCellData and PPointData sections
   closePCellData();
   closePPointData();

   namespace fs = std::experimental::filesystem;

   // get the basename of the main file (filename without extension)
   const fs::path mainPath = mainFileName.getString();
   const fs::path basename = mainPath.stem();

   // create subdirectory for subdomains
   const fs::path subdirectory = mainPath.parent_path() / basename;
   fs::create_directory( subdirectory );

   // write <Piece> tag
   const std::string subfile = "subdomain." + std::to_string(subdomainIndex) + ".vtu";
   const std::string source = basename / subfile;
   str << "<Piece Source=\"" << source << "\"/>\n";

   // return subfile path
   return subdirectory / subfile;
}

template< typename Mesh >
   template< typename Communicator >
std::string
PVTUWriter< Mesh >::addPiece( const String& mainFileName,
                              const typename Communicator::CommunicationGroup group )
{
   std::string source;
   for( int i = 0; i < Communicator::GetSize( group ); i++ ) {
      const std::string s = addPiece( mainFileName, i );
      if( i == Communicator::GetRank( group ) )
         source = s;
   }
   return source;
}

template< typename Mesh >
void
PVTUWriter< Mesh >::writeHeader( const unsigned GhostLevel,
                                 const unsigned MinCommonVertices )
{
   str << "<?xml version=\"1.0\"?>\n";
   str << "<VTKFile type=\"PUnstructuredGrid\" version=\"1.0\"";
   if( isLittleEndian() )
      str << " byte_order=\"LittleEndian\"";
   else
      str << " byte_order=\"BigEndian\"";
   str << " header_type=\"" << VTK::getTypeName( HeaderType{} ) << "\"";
#ifdef HAVE_ZLIB
   if( format == VTK::FileFormat::zlib_compressed )
      str << " compressor=\"vtkZLibDataCompressor\"";
#endif
   str << ">\n";
   str << "<PUnstructuredGrid GhostLevel=\"" << GhostLevel << "\"";
   if( MinCommonVertices > 0 )
      str << " MinCommonVertices=\"" << MinCommonVertices << "\"";
   str << ">\n";

   vtkfileOpen = true;
}

template< typename Mesh >
void
PVTUWriter< Mesh >::writePoints( const Mesh& mesh )
{
   str << "<PPoints>\n";
   writePDataArray< typename Mesh::RealType >( "Points", 3 );
   str << "</PPoints>\n";
}

template< typename Mesh >
void
PVTUWriter< Mesh >::writeFooter()
{
   closePCellData();
   closePPointData();
   str << "</PUnstructuredGrid>\n";
   str << "</VTKFile>\n";
}

template< typename Mesh >
PVTUWriter< Mesh >::~PVTUWriter()
{
   if( vtkfileOpen )
      writeFooter();
}

template< typename Mesh >
void
PVTUWriter< Mesh >::openPCellData()
{
   if( pCellDataClosed )
      throw std::logic_error("The <PCellData> tag has already been closed.");
   closePPointData();
   if( ! pCellDataOpen ) {
      str << "<PCellData>\n";
      pCellDataOpen = true;
   }
}

template< typename Mesh >
void
PVTUWriter< Mesh >::closePCellData()
{
   if( pCellDataOpen ) {
      str << "</PCellData>\n";
      pCellDataClosed = true;
      pCellDataOpen = false;
   }
}

template< typename Mesh >
void
PVTUWriter< Mesh >::openPPointData()
{
   if( pPointDataClosed )
      throw std::logic_error("The <PPointData> tag has already been closed.");
   closePCellData();
   if( ! pPointDataOpen ) {
      str << "<PPointData>\n";
      pPointDataOpen = true;
   }
}

template< typename Mesh >
void
PVTUWriter< Mesh >::closePPointData()
{
   if( pPointDataOpen ) {
      str << "</PPointData>\n";
      pPointDataClosed = true;
      pPointDataOpen = false;
   }
}

} // namespace Writers
} // namespace Meshes
} // namespace TNL
+18 −54
Original line number Diff line number Diff line
@@ -10,10 +10,10 @@

// Implemented by: Jakub Klinkovský

#include <TNL/FileName.h>
#include <TNL/Config/parseCommandLine.h>
#include <TNL/Meshes/TypeResolver/TypeResolver.h>
#include <TNL/Meshes/Writers/VTUWriter.h>
#include <TNL/Meshes/Writers/PVTUWriter.h>

#include <metis.h>

@@ -549,15 +549,24 @@ struct DecomposeMesh
      for( unsigned p = 1; p < nparts; p++ )
         points_offsets[p] = points_offsets[p-1] + points_counts[p-1];

      // prepare output file name
      const String outputFile = parameters.template getParameter< String >( "output-file" );
      FileName outputFileName( removeFileNameExtension( outputFile ) + ".subdomain", "vtu" );
      outputFileName.setDigitsCount( std::floor(std::log10( nparts - 1 )) + 1 );
      // write a .pvtu file
      using PVTU = Meshes::Writers::PVTUWriter< Mesh >;
      const std::string pvtuFileName = parameters.template getParameter< String >( "output-file" );
      std::ofstream file( pvtuFileName );
      PVTU pvtu( file );
      pvtu.template writeEntities< Mesh::getMeshDimension() >( Mesh{}, ghost_levels, ncommon );
      if( ghost_levels > 0 ) {
         // the PointData and CellData from the individual files should be added here
         pvtu.template writePPointData< std::uint8_t >( Meshes::VTK::ghostArrayName() );
         pvtu.template writePPointData< Index >( "GlobalIndex" );
         pvtu.template writePCellData< std::uint8_t >( Meshes::VTK::ghostArrayName() );
         pvtu.template writePCellData< Index >( "GlobalIndex" );
      }

      std::cout << "Writing subdomains..." << std::endl;
      for( unsigned p = 0; p < nparts; p++ ) {
         outputFileName.setIndex( p );
         std::cout << outputFileName.getFileName() << std::endl;
         const std::string outputFileName = pvtu.addPiece( pvtuFileName, p );
         std::cout << outputFileName << std::endl;

         // Due to ghost levels, we don't know the number of cells, let alone points, in each
         // subdomain ahead of time. Hence, we use dynamic data structures instead of MeshBuilder.
@@ -673,7 +682,7 @@ struct DecomposeMesh

         // write the subdomain
         using Writer = Meshes::Writers::VTUWriter< Mesh >;
         std::ofstream file( outputFileName.getFileName() );
         std::ofstream file( outputFileName );
         Writer writer( file );
         writer.template writeEntities< Mesh::getMeshDimension() >( subdomain );
         if( ghost_levels > 0 ) {
@@ -684,51 +693,6 @@ struct DecomposeMesh
         }
      }

      // write a .pvtu file
      std::ofstream file( outputFile.getString() );
      // TODO: refactor into a PVTU writer
      using HeaderType = std::uint64_t;
      using namespace Meshes;
      file << "<?xml version=\"1.0\"?>\n";
      file << "<VTKFile type=\"PUnstructuredGrid\" version=\"1.0\"";
      if( isLittleEndian() )
         file << " byte_order=\"LittleEndian\"";
      else
         file << " byte_order=\"BigEndian\"";
      file << " header_type=\"" << VTK::getTypeName( HeaderType{} ) << "\"";
#ifdef HAVE_ZLIB
//      if( format == VTK::FileFormat::zlib_compressed )
         file << " compressor=\"vtkZLibDataCompressor\"";
#endif
      file << ">\n";
      file << "<PUnstructuredGrid GhostLevel=\"" << ghost_levels << "\" MinCommonVertices=\"" << ncommon << "\">\n";
      file << "<PPoints>\n";
      file << "<PDataArray type=\"" << VTK::getTypeName( typename Mesh::RealType{} ) << "\" Name=\"Points\" NumberOfComponents=\"3\"/>\n";
      file << "</PPoints>\n";
      if( ghost_levels > 0 ) {
         // the PointData and CellData from the individual files should be added here
         file << "<PPointData>\n";
         file << "<PDataArray type=\"" << VTK::getTypeName( std::uint8_t{} ) << "\" Name=\"" << Meshes::VTK::ghostArrayName() << "\" NumberOfComponents=\"1\"/>\n";
         file << "<PDataArray type=\"" << VTK::getTypeName( Index{} ) << "\" Name=\"GlobalIndex\" NumberOfComponents=\"1\"/>\n";
         file << "</PPointData>\n";
         file << "<PCellData>\n";
         file << "<PDataArray type=\"" << VTK::getTypeName( std::uint8_t{} ) << "\" Name=\"" << Meshes::VTK::ghostArrayName() << "\" NumberOfComponents=\"1\"/>\n";
         file << "<PDataArray type=\"" << VTK::getTypeName( Index{} ) << "\" Name=\"GlobalIndex\" NumberOfComponents=\"1\"/>\n";
         file << "</PCellData>\n";
      }

      for( unsigned p = 0; p < nparts; p++ ) {
         outputFileName.setIndex( p );
         // make sure the source path is relative to the main .pvtu file
         // TODO: use proper path library
         const auto parts = outputFileName.getFileName().split('/');
         const std::string basename = parts.back();
         file << "<Piece Source=\"" << basename << "\"/>\n";
      }

      file << "</PUnstructuredGrid>\n";
      file << "</VTKFile>\n";

      return true;
   }
};
@@ -745,7 +709,7 @@ int main( int argc, char* argv[] )

   const String inputFileName = parameters.getParameter< String >( "input-file" );
   const String outputFile = parameters.template getParameter< String >( "output-file" );
   if( getFileExtension( outputFile ) != "pvtu" ) {
   if( ! outputFile.endsWith( ".pvtu" ) ) {
      std::cerr << "Error: the output file must have a '.pvtu' extension." << std::endl;
      return EXIT_FAILURE;
   }