// ---------------------------------------------------------------------
//
// Copyright (C) 2017 by the deal.II authors
//
// This file is part of the deal.II library.
//
// The deal.II library is free software; you can use it, redistribute
// it, and/or modify it under the terms of the GNU Lesser General
// Public License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// The full text of the license can be found in the file LICENSE at
// the top level of the deal.II distribution.
//
// ---------------------------------------------------------------------

#include "../tests.h"

// adaptation of /scalapack/scalapack_02.cc to a quick test

#include <deal.II/base/logstream.h>
#include <deal.II/base/utilities.h>
#include <deal.II/base/conditional_ostream.h>
#include <deal.II/base/timer.h>
#include <deal.II/base/multithread_info.h>

#include <deal.II/lac/vector.h>

#include <deal.II/lac/scalapack.h>

#include <fstream>
#include <iostream>



template <typename FullMatrix>
void create_spd (FullMatrix &A)
{
  const unsigned int size = A.n();
  Assert (size == A.m(), ExcDimensionMismatch(size,A.m()));

  for (unsigned int i = 0; i < size; ++i)
    for (unsigned int j = i; j < size; ++j)
      {
        const double val = random_value<typename FullMatrix::value_type>();
        Assert (val >= 0. && val <= 1.,
                ExcInternalError());
        if (i==j)
          // since A(i,j) < 1 and
          // a symmetric diagonally dominant matrix is SPD
          A(i,j) = val + size;
        else
          {
            A(i,j) = val;
            A(j,i) = val;
          }
      }
}



template <typename NumberType>
void test(const unsigned int size, const unsigned int block_size)
{
  MPI_Comm mpi_communicator(MPI_COMM_WORLD);
  const unsigned int n_mpi_processes(Utilities::MPI::n_mpi_processes(mpi_communicator));
  const unsigned int this_mpi_process(Utilities::MPI::this_mpi_process(mpi_communicator));

  ConditionalOStream pcout (std::cout, (this_mpi_process ==0));

  // Create SPD matrices of requested size:
  FullMatrix<NumberType> full_in(size), inverse(size), full_out(size), diff(size);

  std::shared_ptr<Utilities::MPI::ProcessGrid> grid = std::make_shared<Utilities::MPI::ProcessGrid>(mpi_communicator,size,size,block_size,block_size);
  ScaLAPACKMatrix<NumberType> scalapack_matrix (size, grid, block_size,
                                                LAPACKSupport::Property::symmetric);

  pcout << size << " " << block_size << " " << grid->get_process_grid_rows() << " " << grid->get_process_grid_columns() << std::endl;

  create_spd (full_in);

  // invert via Lapack
  inverse.cholesky(full_in);

  // invert via ScaLAPACK
  scalapack_matrix = full_in;
  scalapack_matrix.compute_cholesky_factorization();
  scalapack_matrix.copy_to(full_out);

  diff = 0;
  diff.add( 1., inverse);
  diff.add(-1., full_out);

  const double error = diff.frobenius_norm();

  if (error > 1e-10 && this_mpi_process == 0)
    {
      std::cout << "Error!" << std::endl
                << "expected to have:" << std::endl;
      inverse.print_formatted (std::cout);
      std::cout << "but got:" << std::endl;
      full_out.print_formatted(std::cout);
      std::cout << "difference:" << std::endl;
      diff.print_formatted(std::cout);
      AssertThrow (false, dealii::ExcInternalError());
    }
  else
    pcout << "Ok" << std::endl;
}



int main (int argc,char **argv)
{
  Utilities::MPI::MPI_InitFinalize mpi_initialization(argc, argv, numbers::invalid_unsigned_int);

  test<double>(320,64);
}
