/*@HEADER
// ***********************************************************************
//
//       Ifpack2: Templated Object-Oriented Algebraic Preconditioner Package
//                 Copyright (2009) Sandia Corporation
//
// Under terms of Contract DE-AC04-94AL85000, there is a non-exclusive
// license for use of this work by or on behalf of the U.S. Government.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Questions? Contact Michael A. Heroux (maherou@sandia.gov)
//
// ***********************************************************************
//@HEADER
*/
#ifndef IFPACK2_TRIDICONTAINER_DECL_HPP
#define IFPACK2_TRIDICONTAINER_DECL_HPP

/// \file Ifpack2_TriDiContainer_decl.hpp
/// \brief Ifpack2::TriDiContainer class declaration

#include "Ifpack2_Container.hpp"
#include "Tpetra_MultiVector.hpp"
#include "Tpetra_Map.hpp"
#include "Tpetra_RowMatrix.hpp"
#include "Teuchos_SerialDenseVector.hpp"
#include "Teuchos_SerialTriDiMatrix.hpp"
#include <type_traits>
#include <string>

namespace Ifpack2 {

/// \class TriDiContainer
/// \brief Store and solve a local TriDi linear problem.
/// \tparam MatrixType A specialization of Tpetra::RowMatrix.
///
/// Please refer to the documentation of the Container
/// interface. Currently, Containers are used by BlockRelaxation.
/// Block relaxations need to be able to do two things:
/// <ol>
/// <li> Store the diagonal blocks </li>
/// <li> Solve linear systems with each diagonal block </li>
/// </ol>
/// TriDiContainer stores the diagonal blocks as TriDi matrices, and
/// solves them using LAPACK (for the four Scalar types that it
/// supports)
///
/// As with Ifpack2::Container, MatrixType must be a specialization of
/// Tpetra::RowMatrix.  Using a TriDi matrix for each block is a good
/// idea when the blocks are small.  For large and / or sparse blocks,
/// it would probably be better to use an implementation of Container
/// that stores the blocks sparsely, in particular SparseContainer.
/// If your matrix is banded but not tridiagonal, use BandedContainer.
///
/// This class may store the TriDi local matrix using values of a
/// different type (\c LocalScalarType) than those in \c MatrixType.
/// You may mix and match so long as implicit conversions are
/// available between \c LocalScalarType and
/// <tt>MatrixType::scalar_type</tt>.
///
/// This class currently assumes the following about the column and
/// row Maps of the input matrix:
/// <ol>
/// <li> On all processes, the column and row Maps begin with the same
///      set of on-process entries, in the same order.  That is,
///      on-process row and column indices are the same.</li>
/// <li> On all processes, all off-process indices in the column Map
///      of the input matrix occur after that initial set.</li>
/// </ol>
/// These assumptions may be violated if the input matrix is a
/// Tpetra::CrsMatrix specialization that was constructed with a
/// user-provided column Map.  The assumptions are not mathematically
/// necessary and could be relaxed at any time.  Implementers who wish
/// to do so will need to modify the extract() method, so that it
/// translates explicitly between local row and column indices,
/// instead of just assuming that they are the same.

template<class MatrixType, class LocalScalarType>
class TriDiContainer
: public ContainerImpl<MatrixType, LocalScalarType>
{
  //! @name Internal typedefs (private)
  //@{
private:
  /// \brief The first template parameter of this class.
  ///
  /// This must be a Tpetra::RowMatrix specialization.  It may have
  /// entirely different template parameters (e.g., \c scalar_type)
  /// than \c InverseType.
  using matrix_type = MatrixType;
  //! The second template parameter of this class.

  //! The type of entries in the input (global) matrix.
  using typename Container<MatrixType>::SC;
  //! The type of local indices in the input (global) matrix.
  using typename Container<MatrixType>::LO;
  //! The type of global indices in the input (global) matrix.
  using typename Container<MatrixType>::GO;
  //! The Node type of the input (global) matrix.
  using typename Container<MatrixType>::NO;
  using LSC = LocalScalarType;

  using typename Container<MatrixType>::mv_type;
  using typename Container<MatrixType>::map_type;
  using typename Container<MatrixType>::vector_type;
  using typename Container<MatrixType>::import_type;

  using typename Container<MatrixType>::ISC;
  using typename ContainerImpl<MatrixType, LocalScalarType>::LISC;
  using typename Container<MatrixType>::HostView;
  using local_mv_type = Tpetra::MultiVector<LSC, LO, GO, NO>;
  using HostViewLocal = typename Kokkos::View<LSC**, Kokkos::HostSpace>;
  using typename ContainerImpl<MatrixType, LocalScalarType>::HostSubviewLocal;
  using typename ContainerImpl<MatrixType, LocalScalarType>::ConstHostSubviewLocal;
  using typename ContainerImpl<MatrixType, LocalScalarType>::block_crs_matrix_type;
  static_assert (std::is_same<MatrixType, Tpetra::RowMatrix<SC, LO, GO, NO>>::value,
                 "Ifpack2::TriDiContainer: MatrixType must be a Tpetra::RowMatrix specialization.");

  /// \brief The (base class) type of the input matrix.
  ///
  /// The input matrix to the constructor may be either a
  /// Tpetra::RowMatrix specialization or a Tpetra::CrsMatrix
  /// specialization.  However, we want to make the constructor as
  /// general as possible, so we always accept the matrix as a
  /// Tpetra::RowMatrix.  This typedef is the appropriate
  /// specialization of Tpetra::RowMatrix.
  using typename Container<MatrixType>::row_matrix_type;
  //@}
public:
  //! \name Constructor and destructor
  //@{

  /// \brief Constructor.
  ///
  /// \param matrix [in] The original input matrix.  This Container
  ///   will construct local diagonal blocks from its rows according to
  ///   <tt>partitions</tt>.
  /// \param partitioner [in] The Partitioner object that assigns
  ///   local rows of the input matrix to blocks.
  /// \param pointIndexed [in] If the input matrix is a \c Tpetra::BlockCrsMatrix,
  ///    whether elements of \c partitions[k] identify rows within blocks (true) or
  ///    whole blocks (false).
  TriDiContainer (const Teuchos::RCP<const row_matrix_type>& matrix,
                  const Teuchos::Array<Teuchos::Array<LO> >& partitions,
                  const Teuchos::RCP<const import_type>& importer,
                  bool pointIndexed);

  TriDiContainer (const TriDiContainer<MatrixType, LocalScalarType>& rhs) = delete;

  //! Destructor (declared virtual for memory safety of derived classes).
  virtual ~TriDiContainer ();

  //@}
  //! \name Get and set methods
  //@{

  //@}
  //! \name Mathematical functions
  //@{

  //! Do all set-up operations that only require matrix structure.
  virtual void initialize ();

  //! Extract the local diagonal block and prepare the solver.
  virtual void compute ();

  void clearBlocks();

  void solveBlock(ConstHostSubviewLocal X,
                  HostSubviewLocal Y,
                  int blockIndex,
                  Teuchos::ETransp mode,
                  LSC alpha,
                  LSC beta) const;

  //@}
  //! \name Miscellaneous methods
  //@{

  /// \brief Print information about this object to the given output stream.
  ///
  /// operator<< uses this method.
  virtual std::ostream& print (std::ostream& os) const;

  //@}
  //! @name Implementation of Teuchos::Describable
  //@{

  //! A one-line description of this object.
  virtual std::string description () const;

  //! Print the object with some verbosity level to the given FancyOStream.
  virtual void
  describe (Teuchos::FancyOStream &out,
            const Teuchos::EVerbosityLevel verbLevel =
            Teuchos::Describable::verbLevel_default) const;

  //@}

  /// \brief Get the name of this container type for Details::constructContainer()
  static std::string getName();
private:
  //! Populate the diagonal blocks
  void extract();

  /// \brief Factor the extracted submatrix.
  ///
  /// Call this after calling extract().
  void factor ();

  //! The local diagonal blocks, which compute() extracts.
  std::vector<Teuchos::SerialTriDiMatrix<int, LSC>> diagBlocks_;

  //! Permutation array from LAPACK (GETRF).
  mutable Teuchos::Array<int> ipiv_;

  //! Scalar data for all blocks
  Teuchos::Array<LSC> scalars_;

  //! Offsets in scalars_ array for all blocks
  Teuchos::Array<int> scalarOffsets_;
};

} // namespace Ifpack2

#endif // IFPACK2_TRIDICONTAINER_DECL_HPP
