/*
  Copyright 2014 SINTEF ICT, Applied Mathematics.
  Copyright 2015 Dr. Blatt - HPC-Simulation-Software & Services
  Copyright 2015 NTNU
  Copyright 2015 Statoil AS
  This file is part of the Open Porous Media project (OPM).
  OPM 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.
  OPM 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 OPM.  If not, see .
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#if HAVE_UMFPACK
#include 
#else
#include 
#endif
#include 
namespace Opm
{
    typedef AutoDiffBlock ADB;
    typedef ADB::V V;
    typedef ADB::M M;
    /// Construct a system solver.
    NewtonIterationBlackoilCPR::NewtonIterationBlackoilCPR(const parameter::ParameterGroup& param,
                                                           const boost::any& parallelInformation_arg)
      : cpr_param_( param ),
        iterations_( 0 ),
        parallelInformation_(parallelInformation_arg),
        newton_use_gmres_( param.getDefault("newton_use_gmres", false ) ),
        linear_solver_reduction_( param.getDefault("linear_solver_reduction", 1e-2 ) ),
        linear_solver_maxiter_( param.getDefault("linear_solver_maxiter", 50 ) ),
        linear_solver_restart_( param.getDefault("linear_solver_restart", 40 ) ),
        linear_solver_verbosity_( param.getDefault("linear_solver_verbosity", 0 ))
    {
    }
    /// Solve the linear system Ax = b, with A being the
    /// combined derivative matrix of the residual and b
    /// being the residual itself.
    /// \param[in] residual   residual object containing A and b.
    /// \return               the solution x
    NewtonIterationBlackoilCPR::SolutionVector
    NewtonIterationBlackoilCPR::computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const
    {
        // Build the vector of equations.
        const int np = residual.material_balance_eq.size();
        std::vector eqs;
        eqs.reserve(np + 2);
        for (int phase = 0; phase < np; ++phase) {
            eqs.push_back(residual.material_balance_eq[phase]);
        }
        // check if wells are present
        const bool hasWells = residual.well_flux_eq.size() > 0 ;
        std::vector elim_eqs;
        if( hasWells )
        {
            eqs.push_back(residual.well_flux_eq);
            eqs.push_back(residual.well_eq);
            // Eliminate the well-related unknowns, and corresponding equations.
            elim_eqs.reserve(2);
            elim_eqs.push_back(eqs[np]);
            eqs = eliminateVariable(eqs, np); // Eliminate well flux unknowns.
            elim_eqs.push_back(eqs[np]);
            eqs = eliminateVariable(eqs, np); // Eliminate well bhp unknowns.
            assert(int(eqs.size()) == np);
        }
        // Scale material balance equations.
        for (int phase = 0; phase < np; ++phase) {
            eqs[phase] = eqs[phase] * residual.matbalscale[phase];
        }
        // Add material balance equations (or other manipulations) to
        // form pressure equation in top left of full system.
        Eigen::SparseMatrix A;
        V b;
        formEllipticSystem(np, eqs, A, b);
        // Scale pressure equation.
        const double pscale = 200*unit::barsa;
        const int nc = residual.material_balance_eq[0].size();
        A.topRows(nc) *= pscale;
        b.topRows(nc) *= pscale;
        // Solve reduced system.
        SolutionVector dx(SolutionVector::Zero(b.size()));
        // Create ISTL matrix.
        DuneMatrix istlA( A );
        // Create ISTL matrix for elliptic part.
        DuneMatrix istlAe( A.topLeftCorner(nc, nc) );
        // Right hand side.
        Vector istlb(istlA.N());
        std::copy_n(b.data(), istlb.size(), istlb.begin());
        // System solution
        Vector x(istlA.M());
        x = 0.0;
        Dune::InverseOperatorResult result;
#if HAVE_MPI
        if(parallelInformation_.type()==typeid(ParallelISTLInformation))
        {
            typedef Dune::OwnerOverlapCopyCommunication Comm;
            const ParallelISTLInformation& info =
                boost::any_cast( parallelInformation_);
            Comm istlComm(info.communicator());
            Comm istlAeComm(info.communicator());
            info.copyValuesTo(istlAeComm.indexSet(), istlAeComm.remoteIndices());
            info.copyValuesTo(istlComm.indexSet(), istlComm.remoteIndices(),
                              istlAe.N(), istlA.N()/istlAe.N());
            // Construct operator, scalar product and vectors needed.
            typedef Dune::OverlappingSchwarzOperator Operator;
            Operator opA(istlA, istlComm);
            constructPreconditionerAndSolve(opA, istlAe, x, istlb, istlComm, istlAeComm, result);
        }
        else
#endif
        {
            // Construct operator, scalar product and vectors needed.
            typedef Dune::MatrixAdapter Operator;
            Operator opA(istlA);
            Dune::Amg::SequentialInformation info;
            constructPreconditionerAndSolve(opA, istlAe, x, istlb, info, info, result);
        }
        // store number of iterations
        iterations_ = result.iterations;
        // Check for failure of linear solver.
        if (!result.converged) {
            OPM_THROW(LinearSolverProblem, "Convergence failure for linear solver.");
        }
        // Copy solver output to dx.
        std::copy(x.begin(), x.end(), dx.data());
        if ( hasWells ) {
            // Compute full solution using the eliminated equations.
            // Recovery in inverse order of elimination.
            dx = recoverVariable(elim_eqs[1], dx, np);
            dx = recoverVariable(elim_eqs[0], dx, np);
        }
        return dx;
    }
    const boost::any& NewtonIterationBlackoilCPR::parallelInformation() const
    {
        return parallelInformation_;
    }
} // namespace Opm