/*
 *    vmg - a versatile multigrid solver
 *    Copyright (C) 2012 Institute for Numerical Simulation, University of Bonn
 *
 *  vmg 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.
 *
 *  vmg 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 this program.  If not, see .
 */
/**
 * @file   comm_mpi.cpp
 * @author Julian Iseringhausen 
 * @date   Wed Jun 16 13:21:06 2010
 *
 * @brief  Class for MPI-based communication.
 *
 */
#ifdef HAVE_CONFIG_H
#include 
#endif
#ifdef HAVE_MPI
#include 
#ifdef HAVE_MARMOT
#include 
#include 
#endif
#ifdef HAVE_BOOST_FILESYSTEM_PATH_HPP
#include 
namespace fs = boost::filesystem;
#endif
#ifdef HAVE_VTK
#include 
#include 
#include 
#include 
#include 
#endif
#include 
#include 
#include "base/helper.hpp"
#include "base/tuple.hpp"
#include "comm/comm_mpi.hpp"
#include "comm/mpi/datatypes_local.hpp"
#include "discretization/boundary_value_setter.hpp"
#include "grid/grid.hpp"
#include "grid/multigrid.hpp"
#include "grid/tempgrid.hpp"
#include "mg.hpp"
#include "base/timer.hpp"
static char print_buffer[512];
using namespace VMG;
void CommMPI::IsendAll(Grid& grid, std::vector& types, const MPI_Comm& comm, const int& tag_start)
{
  for (std::vector::const_iterator iter=types.begin(); iter!=types.end(); ++iter)
    iter->Isend(grid, tag_start, comm, Request());
}
void CommMPI::IrecvAll(Grid& grid, std::vector& types, const MPI_Comm& comm, const int& tag_start)
{
  for (std::vector::const_iterator iter=types.begin(); iter!=types.end(); ++iter)
    iter->Irecv(grid, tag_start, comm, Request());
}
void CommMPI::IsendAllBuffered(const Grid& grid, std::vector& types, const MPI_Comm& comm, const int& tag_start)
{
  for (std::vector::iterator iter=types.begin(); iter!=types.end(); ++iter)
    iter->IsendBuffered(grid, tag_start, comm, Request());
}
void CommMPI::IrecvAllBuffered(std::vector& types, const MPI_Comm& comm, const int& tag_start)
{
  for (std::vector::iterator iter=types.begin(); iter!=types.end(); ++iter)
    iter->IrecvBuffered(tag_start, comm, Request());
}
void CommMPI::ReplaceBufferAll(Grid& grid, const std::vector& types)
{
  for (std::vector::const_iterator iter=types.begin(); iter!= types.end(); ++iter)
    iter->GridReplace(grid);
}
void CommMPI::AddBufferAll(Grid& grid, const std::vector& types)
{
  for (std::vector::const_iterator iter=types.begin(); iter!= types.end(); ++iter)
    iter->GridSum(grid);
}
void CommMPI::CommSubgrid(Grid& grid_old, Grid& grid_new, const int& direction)
{
  MPI_Comm comm = settings.CommunicatorGlobal(grid_old);
  if (comm != MPI_COMM_NULL) {
    VMG::MPI::DatatypesGlobal& datatypes = settings.DatatypesGlobal(grid_old, grid_new, direction);
    IrecvAllBuffered(datatypes.Receive(), comm, 0411);
    IsendAllBuffered(grid_old, datatypes.Send(), comm, 0411);
    WaitAll();
    ReplaceBufferAll(grid_new, datatypes.Receive());
  }
}
void CommMPI::CommAddSubgrid(Grid& grid_old, Grid& grid_new, const int& direction)
{
  MPI_Comm comm = settings.CommunicatorGlobal(grid_old);
  if (comm != MPI_COMM_NULL) {
    VMG::MPI::DatatypesGlobal& datatypes = settings.DatatypesGlobal(grid_old, grid_new, direction);
    IrecvAllBuffered(datatypes.Receive(), comm, 1806);
    IsendAllBuffered(grid_old, datatypes.Send(), comm, 1806);
    WaitAll();
    AddBufferAll(grid_new, datatypes.Receive());
  }
}
std::vector CommMPI::CommBoundaryValues()
{
  MPI_Comm comm = settings.CommunicatorLocal((*MG::GetRhs())(MG::GetRhs()->MaxLevel()));
  if (comm != MPI_COMM_NULL) {
    const std::vector& bvs = MG::GetBoundaryValueSetter()->BoundaryValues();
    std::vector val_buffer; val_buffer.reserve(bvs.size());
    int comm_size, comm_rank;
    MPI_Comm_rank(comm, &comm_rank);
    MPI_Comm_size(comm, &comm_size);
    std::vector< std::vector > bvs_distributed(comm_size);
    for (unsigned int i=0; i();
}
void CommMPI::CommToGhosts(Grid& grid)
{
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  if (comm != MPI_COMM_NULL) {
    VMG::MPI::DatatypesLocal& types = settings.DatatypesLocal(grid);
    IrecvAllBuffered(types.Halo(), comm, 2310);
    IsendAllBuffered(grid, types.NB(), comm, 2310);
    WaitAll();
    ReplaceBufferAll(grid, types.Halo());
  }
}
void CommMPI::CommToGhostsAsyncStart(Grid& grid)
{
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  if (comm != MPI_COMM_NULL) {
    VMG::MPI::DatatypesLocal& types = settings.DatatypesLocal(grid);
    IrecvAllBuffered(types.Halo(), comm, 2412);
    IsendAllBuffered(grid, types.NB(), comm, 2412);
    TestAll();
  }
}
void CommMPI::CommToGhostsAsyncFinish(Grid& grid)
{
  WaitAll();
  ReplaceBufferAll(grid, settings.DatatypesLocal(grid).Halo());
}
void CommMPI::CommFromGhosts(Grid& grid)
{
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  if (comm != MPI_COMM_NULL) {
    VMG::MPI::DatatypesLocal& types = settings.DatatypesLocal(grid);
    IrecvAllBuffered(types.NB(), comm, 1337);
    IsendAllBuffered(grid, types.Halo(), comm, 1337);
    WaitAll();
    AddBufferAll(grid, types.NB());
  }
}
void CommMPI::CommFromGhostsAsyncStart(Grid& grid)
{
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  if (comm != MPI_COMM_NULL) {
    VMG::MPI::DatatypesLocal& types = settings.DatatypesLocal(grid);
    IrecvAllBuffered(types.NB(), comm, 0xc0ffee);
    IsendAllBuffered(grid, types.Halo(), comm, 0xc0ffee);
    TestAll();
  }
}
void CommMPI::CommFromGhostsAsyncFinish(Grid& grid)
{
  WaitAll();
  AddBufferAll(grid, settings.DatatypesLocal(grid).NB());
}
void CommMPI::Barrier()
{
  MPI_Barrier(comm_global);
}
vmg_float CommMPI::GlobalSum(vmg_float value)
{
  vmg_float result = 0;
  MPI_Allreduce(&value, &result, 1, MPI_DOUBLE, MPI_SUM, comm_global);
  return result;
}
vmg_float CommMPI::GlobalSumRoot(vmg_float value)
{
  vmg_float result = 0;
  MPI_Reduce(&value, &result, 1, MPI_DOUBLE, MPI_SUM, 0, comm_global);
  return result;
}
void CommMPI::GlobalSumArray(vmg_float* array, const vmg_int& size)
{
  MPI_Allreduce(MPI_IN_PLACE, array, size, MPI_DOUBLE, MPI_SUM, comm_global);
}
void CommMPI::GlobalBroadcast(vmg_float& value)
{
  MPI_Bcast(&value, 1, MPI_DOUBLE, 0, comm_global);
}
void CommMPI::GlobalGather(vmg_float& value, vmg_float* array)
{
  MPI_Gather(&value, 1, MPI_DOUBLE, array, 1, MPI_DOUBLE, 0, comm_global);
}
vmg_int CommMPI::GlobalSum(vmg_int value)
{
  vmg_int result = 0;
  MPI_Allreduce(&value, &result, 1, MPI_INT, MPI_SUM, comm_global);
  return result;
}
vmg_int CommMPI::GlobalSumRoot(vmg_int value)
{
  vmg_int result = 0;
  MPI_Reduce(&value, &result, 1, MPI_INT, MPI_SUM, 0, comm_global);
  return result;
}
void CommMPI::GlobalSumArray(vmg_int* array, const vmg_int& size)
{
  MPI_Allreduce(MPI_IN_PLACE, array, size, MPI_INT, MPI_SUM, comm_global);
}
vmg_float CommMPI::GlobalMax(vmg_float value)
{
  vmg_float result = 0.0;
  MPI_Allreduce(&value, &result, 1, MPI_DOUBLE, MPI_MAX, comm_global);
  return result;
}
vmg_float CommMPI::GlobalMaxRoot(vmg_float value)
{
  vmg_float result = 0.0;
  MPI_Reduce(&value, &result, 1, MPI_DOUBLE, MPI_MAX, 0, comm_global);
  return result;
}
void CommMPI::GlobalMaxArray(vmg_float* array, const vmg_int& size)
{
  MPI_Allreduce(MPI_IN_PLACE, array, size, MPI_DOUBLE, MPI_MAX, comm_global);
}
vmg_int CommMPI::GlobalMax(vmg_int value)
{
  vmg_int result = 0;
  MPI_Allreduce(&value, &result, 1, MPI_INT, MPI_MAX, comm_global);
  return result;
}
vmg_int CommMPI::GlobalMaxRoot(vmg_int value)
{
  vmg_int result = 0;
  MPI_Reduce(&value, &result, 1, MPI_INT, MPI_MAX, 0, comm_global);
  return result;
}
void CommMPI::GlobalMaxArray(vmg_int* array, const vmg_int& size)
{
  MPI_Allreduce(MPI_IN_PLACE, array, size, MPI_INT, MPI_MAX, comm_global);
}
void CommMPI::GlobalBroadcast(vmg_int& value)
{
  MPI_Bcast(&value, 1, MPI_INT, 0, comm_global);
}
void CommMPI::GlobalGather(vmg_int& value, vmg_int* array)
{
  MPI_Gather(&value, 1, MPI_INT, array, 1, MPI_INT, 0, comm_global);
}
void CommMPI::GlobalBroadcast(char* str)
{
  int size = std::strlen(str) + 1;
  MPI_Bcast(&size, 1, MPI_INT, 0, comm_global);
  MPI_Bcast(str, size, MPI_CHAR, 0, comm_global);
}
vmg_float CommMPI::LevelSum(const Grid& grid, vmg_float value)
{
  vmg_float result = 0.0;
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Allreduce(&value, &result, 1, MPI_DOUBLE, MPI_SUM, comm);
  return result;
}
vmg_float CommMPI::LevelSumRoot(const Grid& grid, vmg_float value)
{
  vmg_float result = 0.0;
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Reduce(&value, &result, 1, MPI_DOUBLE, MPI_SUM, 0, comm);
  return result;
}
void CommMPI::LevelSumArray(const Grid& grid, vmg_float* array, const vmg_int& size)
{
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Allreduce(MPI_IN_PLACE, array, size, MPI_DOUBLE, MPI_SUM, comm);
}
vmg_int CommMPI::LevelSum(const Grid& grid, vmg_int value)
{
  vmg_int result = 0;
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Allreduce(&value, &result, 1, MPI_INT, MPI_SUM, comm);
  return result;
}
vmg_int CommMPI::LevelSumRoot(const Grid& grid, vmg_int value)
{
  vmg_int result = 0;
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Reduce(&value, &result, 1, MPI_INT, MPI_SUM, 0, comm);
  return result;
}
void CommMPI::LevelSumArray(const Grid& grid, vmg_int* array, const vmg_int& size)
{
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Allreduce(MPI_IN_PLACE, array, size, MPI_INT, MPI_SUM, comm);
}
//TODO: Variable length buffer
void CommMPI::Print(const OutputLevel level, const char* format, ...)
{
  bool print = (level == Output);
#ifdef OUTPUT_INFO
  print |= (level == Info);
#endif
#ifdef OUTPUT_DEBUG
  print |= (level == Debug);
#endif
#ifdef OUTPUT_TIMING
  print |= (level == Timing);
#endif
  if (print) {
    va_list args;
    va_start(args, format);
    vsprintf(print_buffer, format, args);
    printf("vmg: Rank %d: %s\n", GlobalRank(), print_buffer);
    va_end(args);
  }
}
void CommMPI::PrintOnce(const OutputLevel level, const char* format, ...)
{
  bool print = (level == Output);
#ifdef OUTPUT_INFO
  print |= (level == Info);
#endif
#ifdef OUTPUT_DEBUG
  print |= (level == Debug);
#endif
#ifdef OUTPUT_TIMING
  print |= (level == Timing);
#endif
  if (GlobalRank() == 0 && print) {
    va_list args;
    va_start(args, format);
    vsprintf(print_buffer, format, args);
    printf("vmg: Rank %d: %s\n", GlobalRank(), print_buffer);
    va_end(args);
  }
}
void CommMPI::PrintXML(const std::string& filename, const std::string& xml_data)
{
  MPI_File file;
  std::stringstream path, xml_header;
  pugi::xml_document doc;
  pugi::xml_node node_data = doc.append_child("Global").append_child("NumProcesses").append_child(pugi::node_pcdata);
  node_data.set_value(Helper::ToString(GlobalProcs().Product()).c_str());
  doc.save(xml_header);
  path << OutputPath() << filename;
  char* filename_array = Helper::GetCharArray(path.str());
  char* xml_header_array = Helper::GetCharArray(xml_header.str());
  char* str_array = Helper::GetCharArray(xml_data);
  MPI_File_open(MPI_COMM_SELF, filename_array, MPI_MODE_WRONLY|MPI_MODE_CREATE, MPI_INFO_NULL, &file);
  MPI_File_set_size(file, 0);
  MPI_File_write(file, xml_header_array, xml_header.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
  MPI_File_write(file, str_array, xml_data.size(), MPI_CHAR, MPI_STATUS_IGNORE);
  MPI_File_close(&file);
  delete [] filename_array;
  delete [] xml_header_array;
  delete [] str_array;
}
void CommMPI::PrintXMLAll(const std::string& filename, const std::string& xml_data)
{
  MPI_File file;
  std::stringstream path;
  path << OutputPath() << filename;
  char* filename_array = Helper::GetCharArray(path.str());
  char* str_array = Helper::GetCharArray(xml_data);
  MPI_File_open(comm_global, filename_array, MPI_MODE_WRONLY|MPI_MODE_CREATE, MPI_INFO_NULL, &file);
  MPI_File_set_size(file, 0);
  if (GlobalRank() == 0) {
    std::stringstream xml_header;
    pugi::xml_document doc;
    pugi::xml_node node_data = doc.append_child("Global").append_child("NumProcesses").append_child(pugi::node_pcdata);
    node_data.set_value(Helper::ToString(GlobalProcs().Product()).c_str());
    doc.save(xml_header);
    char* xml_header_array = Helper::GetCharArray(xml_header.str());
    MPI_File_write_shared(file, xml_header_array, xml_header.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
    delete [] xml_header_array;
  }
  MPI_File_write_ordered(file, str_array, xml_data.size(), MPI_CHAR, MPI_STATUS_IGNORE);
  MPI_File_close(&file);
  delete [] filename_array;
  delete [] str_array;
}
void CommMPI::PrintGridInformation(const Grid& grid, char* filename, const std::string& name)
{
  std::stringstream buf;
  MPI_File file;
  int rank, size;
  int size_local, size_local_max;
  MPI_Comm comm = settings.CommunicatorGlobal(grid);
  MPI_Comm comm_local = settings.CommunicatorLocal(grid);
  if (comm_local != MPI_COMM_NULL)
    MPI_Comm_size(comm_local, &size_local);
  else
    size_local = 0;
  if (comm != MPI_COMM_NULL) {
    MPI_Reduce(&size_local, &size_local_max, 1, MPI_INT, MPI_MAX, 0, comm);
    MPI_File_open(comm, filename, MPI_MODE_WRONLY|MPI_MODE_CREATE|MPI_MODE_APPEND, MPI_INFO_NULL, &file);
    MPI_Comm_rank(comm, &rank);
    MPI_Comm_size(comm, &size);
    if (rank == 0) {
      buf << "###########################################################" << std::endl
          << "GLOBAL INFORMATION:" << std::endl
          << "  NAME:                 " << name << std::endl
          << "  LEVEL:                " << grid.Level() << std::endl
          << "  COMM_SIZE_GLOBAL:     " << size << std::endl
          << "  COMM_SIZE_LOCAL:      " << size_local_max << std::endl
          << "  GLOBAL:" << std::endl
          << "    GLOBAL_BEGIN:            " << grid.Global().GlobalBegin() << std::endl
          << "    GLOBAL_END:              " << grid.Global().GlobalEnd() << std::endl
          << "    GLOBAL_SIZE:             " << grid.Global().GlobalSize() << std::endl
          << "    GLOBAL_BEGIN_FINEST:     " << grid.Global().GlobalBeginFinest() << std::endl
          << "    GLOBAL_END_FINEST:       " << grid.Global().GlobalEndFinest() << std::endl
          << "    GLOBAL_SIZE_FINEST:      " << grid.Global().GlobalSizeFinest() << std::endl
          << "  EXTENT:" << std::endl
          << "    BEGIN:              " << grid.Extent().Begin() << std::endl
          << "    END:                " << grid.Extent().End() << std::endl
          << "    SIZE:               " << grid.Extent().Size() << std::endl
          << "    MESH_WIDTH:         " << grid.Extent().MeshWidth() << std::endl
          << std::endl
          << "LOCAL INFORMATION:" << std::endl;
    }
    buf << "RANK " << rank << ":" << std::endl
        << "  GLOBAL:" << std::endl
        << "    LOCAL_BEGIN:          " << grid.Global().LocalBegin() << std::endl
        << "    LOCAL_END:            " << grid.Global().LocalEnd() << std::endl
        << "    LOCAL_SIZE:           " << grid.Global().LocalSize() << std::endl
        << "    BOUNDARY_TYPE:        " << grid.Global().BoundaryType() << std::endl
        << "  LOCAL:" << std::endl
        << "    BEGIN:                " << grid.Local().Begin() << std::endl
        << "    END:                  " << grid.Local().End() << std::endl
        << "    SIZE:                 " << grid.Local().Size() << std::endl
        << "    SIZE_TOTAL:           " << grid.Local().SizeTotal() << std::endl
        << "    HALO_BEGIN_1:         " << grid.Local().HaloBegin1() << std::endl
        << "    HALO_END_1:           " << grid.Local().HaloEnd1() << std::endl
        << "    HALO_SIZE_1:          " << grid.Local().HaloSize1() << std::endl
        << "    HALO_BEGIN_2:         " << grid.Local().HaloBegin2() << std::endl
        << "    HALO_END_2:           " << grid.Local().HaloEnd2() << std::endl
        << "    HALO_SIZE_2:          " << grid.Local().HaloSize2() << std::endl
        << "    BOUNDARY_BEGIN_1:     " << grid.Local().BoundaryBegin1() << std::endl
        << "    BOUNDARY_END_1:       " << grid.Local().BoundaryEnd1() << std::endl
        << "    BOUNDARY_SIZE_1:      " << grid.Local().BoundarySize1() << std::endl
        << "    BOUNDARY_BEGIN_2:     " << grid.Local().BoundaryBegin2() << std::endl
        << "    BOUNDARY_END_2:       " << grid.Local().BoundaryEnd2() << std::endl
        << "    BOUNDARY_SIZE_2:      " << grid.Local().BoundarySize2() << std::endl;
    if (rank == size-1)
      buf << "###########################################################" << std::endl;
    char* char_buf = Helper::GetCharArray(buf.str());
    MPI_File_write_ordered(file, char_buf, buf.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
    delete [] char_buf;
    MPI_File_close(&file);
  }
}
void CommMPI::PrintDatatypes(char* filename)
{
  std::stringstream buf;
  MPI_File file;
  if (comm_global != MPI_COMM_NULL) {
    MPI_File_open(comm_global, filename, MPI_MODE_WRONLY|MPI_MODE_CREATE|MPI_MODE_APPEND, MPI_INFO_NULL, &file);
    buf << settings << std::endl;
    char* char_buf = Helper::GetCharArray(buf.str());
    MPI_File_write_ordered(file, char_buf, buf.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
    delete [] char_buf;
    MPI_File_close(&file);
  }
}
void CommMPI::PrintAllSettings()
{
  std::stringstream buf;
  MPI_File file;
  const Multigrid& mg = *MG::GetSol();
  buf << OutputPath() << "settings.txt";
  char *filename = Helper::GetCharArray(buf.str());
  MPI_File_open(comm_global, filename, MPI_MODE_WRONLY|MPI_MODE_CREATE, MPI_INFO_NULL, &file);
  MPI_File_set_size(file, 0);
  MPI_File_close(&file);
  for (int i=mg.MinLevel(); i<=mg.MaxLevel(); ++i)
    PrintGridInformation(mg(i), filename, "MULTIGRID");
  for (int i=mg.MinLevel()+1; i<=mg.MaxLevel(); ++i)
    PrintGridInformation(settings.CoarserGrid(mg(i)), filename, "COARSER_GRID");
  for (int i=mg.MinLevel(); i image = vtkSmartPointer::New();
    image->SetExtent(grid.Global().LocalBegin().X(), grid.Global().LocalEnd().X()-1,
                     grid.Global().LocalBegin().Y(), grid.Global().LocalEnd().Y()-1,
                     grid.Global().LocalBegin().Z(), grid.Global().LocalEnd().Z()-1);
    image->SetSpacing(grid.Extent().MeshWidth().vec());
    image->SetOrigin(grid.Extent().Begin().vec());
    image->SetScalarTypeToDouble();
    image->SetNumberOfScalarComponents(1);
    image->AllocateScalars();
    image->GetPointData()->GetScalars()->SetName(information);
    Index i;
    Index begin, end, offset;
    for (int j=0; j<3; ++j) {
      if (grid.Local().BoundarySize1()[j] > 0) {
        begin[j] = grid.Local().BoundaryBegin1()[j];
        offset[j] = grid.Global().LocalBegin()[j];
      } else {
        begin[j] = grid.Local().Begin()[j];
        offset[j] =  grid.Global().LocalBegin()[j] - grid.Local().Begin()[j];
      }
      end[j] = (grid.Local().BoundarySize2()[j] > 0 ? grid.Local().BoundaryEnd2()[j] : grid.Local().End()[j]);
    }
    for (i.X()=begin.X(); i.X()SetScalarComponentFromDouble(i.X() + offset.X(),
                                              i.Y() + offset.Y(),
                                              i.Z() + offset.Z(),
                                              0, grid.GetVal(i));
    image->Update();
    int rank, size;
    MPI_Comm_rank(comm_global, &rank);
    MPI_Comm_size(comm_global, &size);
    std::stringstream filename;
    filename << OutputPath() << std::setw(4) << std::setfill('0') << output_count << "_" << rank << ".vti";
    vtkSmartPointer writer = vtkSmartPointer::New();
    writer->SetFileName(filename.str().c_str());
    writer->SetCompressorTypeToNone();
    writer->SetDataModeToAscii();
    writer->SetInput(image);
    writer->Update();
    writer->Write();
  }
//FIXME
#else /* HAVE_VTK */
  Index i;
  std::stringstream buf;
  Index begin, end;
  Index begin_local, end_local, begin_global, end_global;
  CommToGhosts(grid);
  for (int i=0; i<3; ++i) {
    end[i] = grid.Local().End()[i] + (grid.Global().LocalEnd()[i] == grid.Global().GlobalSize()[i] ? 0 : grid.Local().HaloSize1()[i]);
    end_local[i] = grid.Global().LocalEnd()[i] - (grid.Global().LocalEnd()[i] == grid.Global().GlobalSize()[i] ? 1 : 0);
  }
  begin = grid.Local().Begin();
  begin_local = grid.Global().LocalBegin();
  begin_global = 0;
  end_global = grid.Global().GlobalSize()-1;
  for (i.Z()=begin.Z(); i.Z()SetProperties(sol);
  temp->ImportFromResidual(sol, rhs);
  PrintGrid(*temp, information);
}
void CommMPI::CreateOutputFiles(const Grid& grid, const std::stringstream& serial_data, const char* information,
    const Index& begin_global, const Index& end_global,
    const Index& begin_local, const Index& end_local,
    const int& output_count)
{
  MPI_Comm comm = settings.CommunicatorGlobal(grid);
  if (comm != MPI_COMM_NULL) {
    MPI_File file;
    std::string conv_information = Helper::ReplaceWhitespaces(information, "_");
    CreateParallelOutputFile(grid, comm, output_count, conv_information.c_str(),
        begin_global, end_global, begin_local, end_local);
    file = CreateSerialOutputFile(grid, comm, output_count, conv_information.c_str(),
        begin_global, end_global, begin_local, end_local);
    char *char_buf = Helper::GetCharArray(serial_data.str());
    MPI_File_write(file, char_buf, serial_data.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
    delete [] char_buf;
    FinalizeSerialOutputFile(file);
  }
}
void CommMPI::CreateParallelOutputFile(const Grid& grid, MPI_Comm& comm,
    const int& output_count, const char* information,
    const Index& begin_global, const Index& end_global,
    const Index& begin_local, const Index& end_local)
{
  int rank;
  MPI_File file;
  char parallel_filename[513], serial_filename[513];
  std::stringstream buf;
  MPI_Comm_rank(comm, &rank);
  sprintf(parallel_filename, "%s%04d.pvti", OutputPath().c_str(), output_count);
  sprintf(serial_filename, "%04d_%d.vti", output_count, rank);
  MPI_File_open(comm, parallel_filename, MPI_MODE_WRONLY|MPI_MODE_CREATE, MPI_INFO_NULL, &file);
  MPI_File_set_size(file, 0);
  if (rank == 0) {
    buf << "" << std::endl
        << "" << std::endl
        << "  " << std::endl
        << "    " << std::endl
        << "      " << std::endl
        << "    " << std::endl;
    char* char_buf = Helper::GetCharArray(buf.str());
    MPI_File_write_shared(file, char_buf, buf.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
    delete [] char_buf;
  }
  buf.str("");
  if ((end_local-begin_local).Product() > 0) {
    buf << "    " << std::endl;
  }
  char* char_buf = Helper::GetCharArray(buf.str());
  MPI_File_write_ordered(file, char_buf, buf.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
  delete [] char_buf;
  if (rank == 0) {
    buf.str("");
    buf << "  " << std::endl
        << "" << std::endl;
    char* char_buf = Helper::GetCharArray(buf.str());
    MPI_File_write_shared(file, char_buf, buf.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
    delete [] char_buf;
  }
  MPI_File_close(&file);
}
MPI_File CommMPI::CreateSerialOutputFile(const Grid& grid, MPI_Comm& comm,
    const int& output_count, const char* information,
    const Index& begin_global, const Index& end_global,
    const Index& begin_local, const Index& end_local)
{
  char serial_filename[513];
  int rank;
  MPI_File file;
  std::stringstream buf;
  MPI_Comm_rank(comm_global, &rank);
  sprintf(serial_filename, "%s%04d_%d.vti", OutputPath().c_str(), output_count, rank);
  MPI_File_open(MPI_COMM_SELF, serial_filename, MPI_MODE_WRONLY|MPI_MODE_CREATE, MPI_INFO_NULL, &file);
  buf << "" << std::endl
      << "" << std::endl
      << "  " << std::endl
      << "    " << std::endl
      << "      " << std::endl
      << "        " << std::endl
      << "          ";
  char* char_buf = Helper::GetCharArray(buf.str());
  MPI_File_write(file, char_buf, buf.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
  delete [] char_buf;
  return file;
}
void CommMPI::FinalizeSerialOutputFile(MPI_File& file)
{
  std::stringstream buf;
  buf << std::endl
      << "        " << std::endl
      << "      " << std::endl
      << "    " << std::endl
      << "  " << std::endl
      << "" << std::endl;
  char* char_buf = Helper::GetCharArray(buf.str());
  MPI_File_write(file, char_buf, buf.str().size(), MPI_CHAR, MPI_STATUS_IGNORE);
  delete [] char_buf;
  MPI_File_close(&file);
}
int CommMPI::GlobalRank() const
{
  int rank;
  MPI_Comm_rank(comm_global, &rank);
  return rank;
}
int CommMPI::GlobalSize() const
{
  int size;
  MPI_Comm_size(comm_global, &size);
  return size;
}
Index CommMPI::GlobalPos() const
{
  Index dims, periods, coords;
  MPI_Cart_get(comm_global, 3, dims.vec(), periods.vec(), coords.vec());
  return coords;
}
Index CommMPI::GlobalProcs() const
{
  Index dims, periods, coords;
  MPI_Cart_get(comm_global, 3, dims.vec(), periods.vec(), coords.vec());
  return dims;
}
int CommMPI::Rank(const Grid& grid) const
{
  int rank;
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Comm_rank(comm, &rank);
  return rank;
}
int CommMPI::Size(const Grid& grid) const
{
  int size;
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Comm_size(comm, &size);
  return size;
}
Index CommMPI::Pos(const Grid& grid) const
{
  Index dims, periods, coords;
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Cart_get(comm, 3, dims.vec(), periods.vec(), coords.vec());
  return coords;
}
Index CommMPI::Procs(const Grid& grid) const
{
  Index dims, periods, coords;
  MPI_Comm comm = settings.CommunicatorLocal(grid);
  assert(comm != MPI_COMM_NULL);
  MPI_Cart_get(comm, 3, dims.vec(), periods.vec(), coords.vec());
  return dims;
}
void CommMPI::InitCommMPI(const MPI_Comm& comm)
{
  int status, size, rank;
  int dims[3] = {0, 0, 0};
  int periods[3];
  for (int i=0; i<3; ++i)
    periods[i] = (BoundaryConditions()[i] == Periodic ? 1 : 0);
  MPI_Comm_size(comm, &size);
  MPI_Comm_rank(comm, &rank);
  MPI_Topo_test(comm, &status);
  if (status == MPI_CART) {
    comm_global = comm;
  }else {
    const int log2 = Helper::log_2(size);
    if (Helper::intpow(2, log2) == size) {
      for (int i=0; i<3; ++i)
        dims[i] = Helper::intpow(2, log2 / 3 + (log2%3 > i ? 1 : 0));
    }else {
      MPI_Dims_create(size, 3, dims);
    }
#ifdef OUTPUT_DEBUG
    if (rank == 0)
      std::printf("Process grid: %d %d %d\n", dims[0], dims[1], dims[2]);
#endif
    MPI_Cart_create(comm, 3, dims, periods, 1, &comm_global);
  }
  MPI_Info_create(&info);
  char key[] = "no_locks";
  char val[] = "true";
  MPI_Info_set(info, key, val);
}
CommMPI::~CommMPI()
{
  MPI_Comm_free(&comm_global);
#ifdef VMG_ONE_SIDED
  if (win_created)
    MPI_Win_free(&win);
#endif
  MPI_Info_free(&info);
}
Grid& CommMPI::GetCoarserGrid(Multigrid& multigrid)
{
  return settings.CoarserGrid(multigrid(multigrid.Level()));
}
Grid& CommMPI::GetFinerGrid(Multigrid& multigrid)
{
  return settings.FinerGrid(multigrid(multigrid.Level()));
}
std::string CommMPI::CreateOutputDirectory()
{
#ifdef HAVE_BOOST_FILESYSTEM_PATH_HPP
  std::string path, unique_path;
  std::stringstream unique_suffix;
  int suffix_counter = 0;
  char buffer[129];
  time_t rawtime;
  struct tm *timeinfo;
  int path_size;
  char* path_array;
  if (GlobalRank() == 0) {
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    strftime(buffer, 128, "./output/%Y_%m_%d_%H_%M_%S/", timeinfo);
    path = buffer;
    if (!fs::exists("output"))
      fs::create_directory("output");
    unique_path = path;
    while (fs::exists(unique_path.c_str())) {
      unique_suffix.str("");
      unique_suffix << "_" << suffix_counter++ << "/";
      unique_path = path;
      unique_path.replace(unique_path.size()-1, 1, unique_suffix.str());
    }
    fs::create_directory(unique_path.c_str());
    path_size = unique_path.size() + 1;
    path_array = Helper::GetCharArray(unique_path);
    MPI_Bcast(&path_size, 1, MPI_INT, 0, comm_global);
  }else {
    MPI_Bcast(&path_size, 1, MPI_INT, 0, comm_global);
    path_array = new char[path_size];
  }
  MPI_Bcast(path_array, path_size, MPI_CHAR, 0, comm_global);
  unique_path = path_array;
  delete [] path_array;
  return unique_path;
#else
  return "./";
#endif
}
#endif /* HAVE_MPI */