Commit 19c5cac7 authored by Jakub Klinkovský's avatar Jakub Klinkovský Committed by Jakub Klinkovský
Browse files

Added wrappers for Hypre classes: Vector, ParVector, CSRMatrix, ParCSRMatrix...

Added wrappers for Hypre classes: Vector, ParVector, CSRMatrix, ParCSRMatrix and some solvers and preconditioners
parent 60b3d1b6
Loading
Loading
Loading
Loading

cmake/FindHypre.cmake

0 → 100644
+78 −0
Original line number Diff line number Diff line
################################################################################
#
# \file      FindHypre.cmake
# \copyright 2012-2015 J. Bakosi,
#            2016-2018 Los Alamos National Security, LLC.,
#            2019-2021 Triad National Security, LLC.
#            All rights reserved. See the LICENSE file for details.
# \brief     Find the Hypre library from LLNL
#
################################################################################

# Hypre: https://github.com/LLNL/hypre
#
#  HYPRE_FOUND - System has Hypre
#  HYPRE_INCLUDE_DIRS - The Hypre include directory
#  HYPRE_LIBRARIES - The libraries needed to use Hypre
#
#  Set HYPRE_ROOT before calling find_package to a path to add an additional
#  search path, e.g.,
#
#  Usage:
#
#  set(HYPRE_ROOT "/path/to/custom/hypre") # prefer over system
#  find_package(Hypre)
#  if(HYPRE_FOUND)
#    target_link_libraries (TARGET ${HYPRE_LIBRARIES})
#  endif()

function(_HYPRE_GET_VERSION _OUT_ver _version_hdr)
  file(STRINGS ${_version_hdr} _contents REGEX "#define HYPRE_RELEASE_VERSION[ \t]+")
  if(_contents)
    string(REGEX REPLACE "\"" "" _cont "${_contents}")
    string(REGEX REPLACE ".*#define HYPRE_RELEASE_VERSION[ \t]+([0-9.]+).*" "\\1" ${_OUT_ver} "${_cont}")
    if(NOT ${${_OUT_ver}} MATCHES "[0-9]+")
        message(FATAL_ERROR "Version parsing failed for HYPRE_RELEASE_VERSION in ${_version_hdr}!")
    endif()
    set(${_OUT_ver} ${${_OUT_ver}} PARENT_SCOPE)
 elseif(EXISTS ${_version_hdr})
    message(FATAL_ERROR "No HYPRE_RELEASE_VERSION in ${_version_hdr}")
 else()
    message(FATAL_ERROR "Include file ${_version_hdr} does not exist")
  endif()
endfunction()

# If already in cache, be silent
if(HYPRE_INCLUDE_DIRS AND HYPRE_LIBRARIES)
  set (HYPRE_FIND_QUIETLY TRUE)
endif()

if (HYPRE_ROOT)
  set(HYPRE_SEARCH_OPTS NO_DEFAULT_PATH)
else()
  set(HYPRE_ROOT "/usr")
endif()

find_path(HYPRE_INCLUDE_DIR NAMES HYPRE.h
                            PATH_SUFFIXES hypre
                            HINTS ${HYPRE_ROOT}/include ${HYPRE_ROOT}/include/hypre
                            ${HYPRE_SEARCH_OPTS})

if(HYPRE_INCLUDE_DIR)
  _HYPRE_GET_VERSION(HYPRE_VERSION ${HYPRE_INCLUDE_DIR}/HYPRE_config.h)
  set(HYPRE_INCLUDE_DIRS ${HYPRE_INCLUDE_DIR})
else()
  set(HYPRE_VERSION 0.0.0)
  set(HYPRE_INCLUDE_DIRS "")
endif()

find_library(HYPRE_LIBRARY NAMES HYPRE HINTS ${HYPRE_ROOT}/lib)

set(HYPRE_LIBRARIES ${HYPRE_LIBRARY})

# Handle the QUIETLY and REQUIRED arguments and set HYPRE_FOUND to TRUE if
# all listed variables are TRUE.
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Hypre REQUIRED_VARS HYPRE_LIBRARIES HYPRE_INCLUDE_DIRS VERSION_VAR HYPRE_VERSION)

MARK_AS_ADVANCED(HYPRE_INCLUDE_DIRS HYPRE_LIBRARIES)
+373 −0
Original line number Diff line number Diff line
// Copyright (c) 2004-2022 Tomáš Oberhuber et al.
//
// This file is part of TNL - Template Numerical Library (https://tnl-project.org/)
//
// SPDX-License-Identifier: MIT

// Implemented by: Jakub Klinkovský

#pragma once

#ifdef HAVE_HYPRE

   #include <TNL/Containers/HypreVector.h>
   #include <TNL/Containers/DistributedVector.h>

namespace TNL {
namespace Containers {

/**
 * \brief Wrapper for Hypre's parallel vector.
 *
 * Links to upstream sources:
 * - https://github.com/hypre-space/hypre/blob/master/src/parcsr_mv/par_vector.h
 * - https://github.com/hypre-space/hypre/blob/master/src/parcsr_mv/par_vector.c
 * - https://github.com/hypre-space/hypre/blob/master/src/parcsr_mv/_hypre_parcsr_mv.h (catch-all interface)
 */
class HypreParVector
{
public:
   using RealType = HYPRE_Real;
   using ValueType = RealType;
   using DeviceType = HYPRE_Device;
   using IndexType = HYPRE_Int;
   using LocalRangeType = Subrange< IndexType >;

   using VectorType = Containers::DistributedVector< RealType, DeviceType, IndexType >;
   using ViewType = typename VectorType::ViewType;
   using ConstViewType = typename VectorType::ConstViewType;

   using LocalVectorType = Containers::Vector< RealType, DeviceType, IndexType >;
   using LocalViewType = typename LocalVectorType::ViewType;
   using ConstLocalViewType = typename LocalVectorType::ConstViewType;

   using SynchronizerType = typename ViewType::SynchronizerType;

   // default constructor, no underlying \e hypre_ParVector is created.
   HypreParVector() = default;

   // TODO: behavior should depend on "owns_data" (shallow vs deep copy)
   HypreParVector( const HypreParVector& other ) = delete;

   HypreParVector( HypreParVector&& other ) noexcept
   : v( other.v ), owns_handle( other.owns_handle ), localData( std::move( other.localData ) ), ghosts( other.ghosts ),
     synchronizer( std::move( other.synchronizer ) ), valuesPerElement( other.valuesPerElement )
   {
      other.v = nullptr;
   }

   // TODO should do a deep copy
   HypreParVector&
   operator=( const HypreParVector& other ) = delete;

   HypreParVector&
   operator=( HypreParVector&& other ) noexcept
   {
      v = other.v;
      other.v = nullptr;
      owns_handle = other.owns_handle;
      localData = std::move( other.localData );
      ghosts = other.ghosts;
      synchronizer = std::move( other.synchronizer );
      valuesPerElement = other.valuesPerElement;
      return *this;
   }

   //! \brief Initialization by raw data
   HypreParVector( const LocalRangeType& localRange,
                   IndexType ghosts,
                   IndexType globalSize,
                   MPI_Comm communicator,
                   LocalViewType localData )
   {
      bind( localRange, ghosts, globalSize, communicator, localData );
   }

   /**
    * \brief Convert Hypre's format to HypreParVector
    *
    * \param take_ownership indicates if the vector should take ownership of
    * the handle, i.e. whether to call \e hypre_VectorDestroy when it does
    * not need it anymore.
    */
   explicit HypreParVector( hypre_ParVector* handle, bool take_ownership = true )
   {
      bind( handle, take_ownership );
   }

   //! Typecasting to Hypre's \e hypre_ParVector* (which is equivalent to \e HYPRE_ParVector*)
   operator hypre_ParVector*() const
   {
      return v;
   }

   ~HypreParVector()
   {
      reset();
   }

   LocalRangeType
   getLocalRange() const
   {
      if( v == nullptr )
         return {};
      return { hypre_ParVectorPartitioning( v )[ 0 ], hypre_ParVectorPartitioning( v )[ 1 ] };
   }

   IndexType
   getGhosts() const
   {
      return ghosts;
   }

   //! \brief Return the MPI communicator.
   MPI_Comm
   getCommunicator() const
   {
      if( v == nullptr )
         return MPI_COMM_NULL;
      return hypre_ParVectorComm( v );
   }

   //! \brief Returns the global size of the vector.
   HYPRE_Int
   getSize() const
   {
      if( v == nullptr )
         return 0;
      return hypre_ParVectorGlobalSize( v );
   }

   ConstLocalViewType
   getConstLocalView() const
   {
      return { localData.getData(), getLocalRange().getSize() };
   }

   LocalViewType
   getLocalView()
   {
      return { localData.getData(), getLocalRange().getSize() };
   }

   ConstLocalViewType
   getConstLocalViewWithGhosts() const
   {
      return localData.getConstView();
   }

   LocalViewType
   getLocalViewWithGhosts()
   {
      return localData.getView();
   }

   ConstViewType
   getConstView() const
   {
      return { getLocalRange(), ghosts, getSize(), getCommunicator(), getConstLocalViewWithGhosts() };
   }

   ViewType
   getView()
   {
      return { getLocalRange(), ghosts, getSize(), getCommunicator(), getLocalViewWithGhosts() };
   }

   /**
    * \brief Drop previously set data (deallocate if the vector was the owner)
    * and bind to the given data (i.e., the vector does not become the owner).
    */
   void
   bind( const LocalRangeType& localRange,
         IndexType ghosts,
         IndexType globalSize,
         MPI_Comm communicator,
         LocalViewType localData )
   {
      TNL_ASSERT_EQ( localData.getSize(),
                     localRange.getSize() + ghosts,
                     "The local array size does not match the local range of the distributed array." );
      TNL_ASSERT_GE( ghosts, 0, "The ghosts count must be non-negative." );

      // drop/deallocate the current data
      reset();

      // create handle for the vector
      IndexType partitioning[ 2 ];
      partitioning[ 0 ] = localRange.getBegin();
      partitioning[ 1 ] = localRange.getEnd();
      v = hypre_ParVectorCreate( communicator, globalSize, partitioning );

      // set view data
      this->localData.bind( localData );
      hypre_ParVectorOwnsData( v ) = 0;
      hypre_ParVectorLocalVector( v ) = this->localData;
      hypre_ParVectorActualLocalSize( v ) = this->localData.getSize();

      this->ghosts = ghosts;
   }

   void
   bind( ViewType view )
   {
      bind( view.getLocalRange(), view.getGhosts(), view.getSize(), view.getCommunicator(), view.getLocalViewWithGhosts() );
   }

   void
   bind( VectorType& vector )
   {
      bind( vector.getView() );
   }

   void
   bind( HypreParVector& vector )
   {
      bind( vector.getLocalRange(),
            vector.getGhosts(),
            vector.getSize(),
            vector.getCommunicator(),
            vector.getLocalViewWithGhosts() );
   }

   /**
    * \brief Convert Hypre's format to HypreParVector
    *
    * \param take_ownership indicates if the vector should take ownership of
    * the handle, i.e. whether to call \e hypre_VectorDestroy when it does
    * not need it anymore.
    */
   void
   bind( hypre_ParVector* handle, bool take_ownership = true )
   {
      // drop/deallocate the current data
      if( v != nullptr )
         hypre_ParVectorDestroy( v );

      // set the handle and ownership flag
      v = handle;
      owns_handle = take_ownership;

      // set view data
      localData.bind( hypre_ParVectorLocalVector( v ) );
      hypre_ParVectorOwnsData( v ) = 0;
      hypre_ParVectorLocalVector( v ) = this->localData;
      hypre_ParVectorActualLocalSize( v ) = this->localData.getSize();

      const HYPRE_Int actual_size = hypre_ParVectorActualLocalSize( v );
      const HYPRE_Int local_size = hypre_ParVectorPartitioning( v )[ 1 ] - hypre_ParVectorPartitioning( v )[ 0 ];
      this->ghosts = actual_size - local_size;
   }

   //! \brief Reset the vector to empty state.
   void
   reset()
   {
      // drop/deallocate the current data
      if( owns_handle && v != nullptr ) {
         hypre_ParVectorDestroy( v );
         v = nullptr;
      }
      else
         v = nullptr;
      owns_handle = true;

      localData.reset();
      ghosts = 0;
      synchronizer = nullptr;
      valuesPerElement = 1;
   }

   void
   setDistribution( LocalRangeType localRange, IndexType ghosts, IndexType globalSize, const MPI::Comm& communicator )
   {
      TNL_ASSERT_LE( localRange.getEnd(), globalSize, "end of the local range is outside of the global range" );

      // drop/deallocate the current data
      reset();

      // create handle for the vector
      IndexType partitioning[ 2 ];
      partitioning[ 0 ] = localRange.getBegin();
      partitioning[ 1 ] = localRange.getEnd();
      v = hypre_ParVectorCreate( communicator, globalSize, partitioning );

      // set view data
      this->localData.setSize( localRange.getSize() + ghosts );
      hypre_ParVectorOwnsData( v ) = 0;
      hypre_ParVectorLocalVector( v ) = this->localData;
      hypre_ParVectorActualLocalSize( v ) = this->localData.getSize();

      this->ghosts = ghosts;
   }

   //! \brief Set all elements of the vector to \e value.
   void
   setValue( RealType value )
   {
      if( v != nullptr )
         hypre_ParVectorSetConstantValues( v, value );
   }

   // synchronizer stuff
   void
   setSynchronizer( std::shared_ptr< SynchronizerType > synchronizer, int valuesPerElement = 1 )
   {
      this->synchronizer = std::move( synchronizer );
      this->valuesPerElement = valuesPerElement;
   }

   std::shared_ptr< SynchronizerType >
   getSynchronizer() const
   {
      return synchronizer;
   }

   int
   getValuesPerElement() const
   {
      return valuesPerElement;
   }

   // Note that this method is not thread-safe - only the thread which created
   // and "owns" the instance of this object can call this method.
   void
   startSynchronization()
   {
      if( ghosts == 0 )
         return;
      // TODO: assert does not play very nice with automatic synchronizations from operations like
      //       assignment of scalars
      // (Maybe we should just drop all automatic syncs? But that's not nice for high-level codes
      // like linear solvers...)
      TNL_ASSERT_TRUE( synchronizer, "the synchronizer was not set" );

      typename SynchronizerType::ByteArrayView bytes;
      bytes.bind( reinterpret_cast< std::uint8_t* >( localData.getData() ), sizeof( ValueType ) * localData.getSize() );
      synchronizer->synchronizeByteArrayAsync( bytes, sizeof( ValueType ) * valuesPerElement );
   }

   void
   waitForSynchronization() const
   {
      if( synchronizer && synchronizer->async_op.valid() ) {
         synchronizer->async_wait_timer.start();
         synchronizer->async_op.wait();
         synchronizer->async_wait_timer.stop();
      }
   }

protected:
   hypre_ParVector* v = nullptr;
   bool owns_handle = true;
   HypreVector localData;
   HYPRE_Int ghosts = 0;

   std::shared_ptr< SynchronizerType > synchronizer = nullptr;
   int valuesPerElement = 1;
};

}  // namespace Containers
}  // namespace TNL

#endif  // HAVE_HYPRE
+263 −0
Original line number Diff line number Diff line
// Copyright (c) 2004-2022 Tomáš Oberhuber et al.
//
// This file is part of TNL - Template Numerical Library (https://tnl-project.org/)
//
// SPDX-License-Identifier: MIT

// Implemented by: Jakub Klinkovský

#pragma once

#ifdef HAVE_HYPRE

   #include <TNL/Hypre.h>

   #include <TNL/Containers/Vector.h>

namespace TNL {
namespace Containers {

/**
 * \brief Wrapper for Hypre's sequential vector.
 *
 * Links to upstream sources:
 * - https://github.com/hypre-space/hypre/blob/master/src/seq_mv/vector.h
 * - https://github.com/hypre-space/hypre/blob/master/src/seq_mv/vector.c
 * - https://github.com/hypre-space/hypre/blob/master/src/seq_mv/seq_mv.h (catch-all interface)
 */
class HypreVector
{
public:
   using RealType = HYPRE_Real;
   using ValueType = RealType;
   using DeviceType = HYPRE_Device;
   using IndexType = HYPRE_Int;

   using VectorType = Containers::Vector< RealType, DeviceType, IndexType >;
   using ViewType = typename VectorType::ViewType;
   using ConstViewType = typename VectorType::ConstViewType;

   HypreVector() = default;

   // TODO: behavior should depend on "owns_data" (shallow vs deep copy)
   HypreVector( const HypreVector& other ) = delete;

   HypreVector( HypreVector&& other ) noexcept : v( other.v ), owns_handle( other.owns_handle )
   {
      other.v = nullptr;
   }

   // TODO should do a deep copy
   HypreVector&
   operator=( const HypreVector& other ) = delete;

   HypreVector&
   operator=( HypreVector&& other ) noexcept
   {
      v = other.v;
      other.v = nullptr;
      owns_handle = other.owns_handle;
      return *this;
   }

   HypreVector( RealType* data, IndexType size )
   {
      bind( data, size );
   }

   HypreVector( ViewType view )
   {
      bind( view );
   }

   /**
    * \brief Convert Hypre's format to HypreVector
    *
    * \param take_ownership indicates if the vector should take ownership of
    * the handle, i.e. whether to call \e hypre_VectorDestroy when it does
    * not need it anymore.
    */
   explicit HypreVector( hypre_Vector* handle, bool take_ownership = true )
   {
      bind( handle, take_ownership );
   }

   operator const hypre_Vector*() const noexcept
   {
      return v;
   }

   operator hypre_Vector*() noexcept
   {
      return v;
   }

   // HYPRE_Vector is "equivalent" to pointer to hypre_Vector, but requires
   // ugly C-style cast on the pointer (which is done even in Hypre itself)
   // https://github.com/hypre-space/hypre/blob/master/src/seq_mv/HYPRE_vector.c
   operator HYPRE_Vector() const noexcept
   {
      return (HYPRE_Vector) v;
   }

   ~HypreVector()
   {
      reset();
   }

   const RealType*
   getData() const noexcept
   {
      if( v == nullptr )
         return nullptr;
      return hypre_VectorData( v );
   }

   RealType*
   getData() noexcept
   {
      if( v == nullptr )
         return nullptr;
      return hypre_VectorData( v );
   }

   IndexType
   getSize() const
   {
      if( v == nullptr )
         return 0;
      return hypre_VectorSize( v );
   }

   ConstViewType
   getConstView() const
   {
      if( v == nullptr )
         return {};
      return { getData(), getSize() };
   }

   ViewType
   getView()
   {
      if( v == nullptr )
         return {};
      return { getData(), getSize() };
   }

   /**
    * \brief Drop previously set data (deallocate if the vector was the owner)
    * and bind to the given data (i.e., the vector does not become the owner).
    */
   void
   bind( RealType* data, IndexType size )
   {
      // drop/deallocate the current data
      reset();

      // create handle for the vector
      v = hypre_SeqVectorCreate( 0 );

      // set view data
      hypre_VectorOwnsData( v ) = 0;
      hypre_VectorData( v ) = data;
      hypre_VectorSize( v ) = size;
      // TODO: v->memory_location
   }

   void
   bind( ViewType view )
   {
      bind( view.getData(), view.getSize() );
   }

   void
   bind( VectorType& vector )
   {
      bind( vector.getView() );
   }

   void
   bind( HypreVector& vector )
   {
      bind( vector.getData(), vector.getSize() );
   }

   /**
    * \brief Convert Hypre's format to HypreSeqVector
    *
    * \param take_ownership indicates if the vector should take ownership of
    * the handle, i.e. whether to call \e hypre_VectorDestroy when it does
    * not need it anymore.
    */
   void
   bind( hypre_Vector* handle, bool take_ownership = true )
   {
      // drop/deallocate the current data
      reset();

      // set the handle and ownership flag
      v = handle;
      owns_handle = take_ownership;
   }

   //! \brief Reset the vector to empty state.
   void
   reset()
   {
      if( owns_handle && v != nullptr ) {
         hypre_SeqVectorDestroy( v );
         v = nullptr;
      }
      else
         v = nullptr;
      owns_handle = true;
   }

   /**
    * \brief Set the new vector size.
    *
    * - if the vector previously owned data, they are deallocated
    * - new size is set
    * - the vector is initialized with \e hypre_SeqVectorInitialize
    *   (i.e., data are allocated)
    */
   void
   setSize( IndexType size )
   {
      hypre_SeqVectorDestroy( v );
      v = hypre_SeqVectorCreate( size );
      hypre_SeqVectorInitialize( v );
   }

   //! \brief Equivalent to \ref setSize.
   void
   resize( IndexType size )
   {
      setSize( size );
   }

   //! \brief Equivalent to \ref setSize followed by \ref setValue.
   void
   resize( IndexType size, RealType value )
   {
      setSize( size );
      setValue( value );
   }

   //! \brief Set all elements of the vector to \e value.
   void
   setValue( RealType value )
   {
      hypre_SeqVectorSetConstantValues( v, value );
   }

protected:
   hypre_Vector* v = nullptr;
   bool owns_handle = true;
};

}  // namespace Containers
}  // namespace TNL

#endif  // HAVE_HYPRE

src/TNL/Hypre.h

0 → 100644
+114 −0
Original line number Diff line number Diff line
// Copyright (c) 2004-2022 Tomáš Oberhuber et al.
//
// This file is part of TNL - Template Numerical Library (https://tnl-project.org/)
//
// SPDX-License-Identifier: MIT

// Implemented by: Jakub Klinkovský

#pragma once

#ifdef HAVE_HYPRE

   #include <mpi.h>

   // Hypre header files
   #include <seq_mv.h>
   #include <_hypre_parcsr_mv.h>
   #include <_hypre_parcsr_ls.h>

   #ifdef HYPRE_MIXEDINT
      #error "TNL does not work with HYPRE's mixed-int support (i.e. when HYPRE_Int and HYPRE_BigInt are different types)"
   #endif
   #ifdef HYPRE_COMPLEX
      #error "TNL does not work with HYPRE's complex numbers support"
   #endif

   #if defined( HYPRE_USING_GPU ) && ! ( defined( HYPRE_USING_CUDA ) || defined( HYPRE_USING_HIP ) )
      #error "Unsupported GPU build of HYPRE! Only CUDA and HIP builds are supported."
   #endif
   #if defined( HYPRE_USING_CUDA ) && ! defined( HAVE_CUDA )
      #error "HAVE_CUDA is required when HYPRE is built with CUDA!"
   #endif
   #if defined( HYPRE_USING_HIP ) && ! defined( HAVE_HIP )
      #error "HAVE_HIP is required when HYPRE is built with HIP!"
   #endif

namespace TNL {

/**
 * \brief A simple RAII wrapper for Hypre's initialization and finalization.
 *
 * When the object is constructed, it calls \e HYPRE_Init() and sets some
 * GPU-relevant options. The \e HYPRE_Finalize() function is called
 * automatically from the object's destructor.
 */
struct Hypre
{
   //! \brief Constructor initializes Hypre by calling \e HYPRE_Init() and set default options.
   Hypre()
   {
      HYPRE_Init();

      setDefaultOptions();
   }

   //! \brief Sets the default Hypre global options (mostly GPU-relevant).
   void
   setDefaultOptions()
   {
      // Global Hypre options, see
      // https://hypre.readthedocs.io/en/latest/solvers-boomeramg.html#gpu-supported-options

   #ifdef HYPRE_USING_CUDA
      // Use hypre's SpGEMM instead of cuSPARSE for performance reasons
      HYPRE_SetSpGemmUseCusparse( 0 );
   #elif defined( HYPRE_USING_HIP )
      // Use rocSPARSE instead of hypre's SpGEMM for performance reasons (default)
      HYPRE_SetSpGemmUseCusparse( 1 );
   #endif

      // The following options are Hypre's defaults as of version 2.24

      // Allocate Hypre objects in GPU memory (default)
      // HYPRE_SetMemoryLocation(HYPRE_MEMORY_DEVICE);

      // Where to execute when using UVM (default)
      // HYPRE_SetExecutionPolicy(HYPRE_EXEC_DEVICE);

      // Use GPU-based random number generator (default)
      // HYPRE_SetUseGpuRand(1);
   }

   //! \brief Destructor that finalizes Hypre when the object goes out of scope.
   ~Hypre()
   {
      HYPRE_Finalize();
   }
};

}  // namespace TNL

// clang-format off
   #ifdef HYPRE_USING_CUDA
      #include <TNL/Devices/Cuda.h>
      namespace TNL {
         using HYPRE_Device = Devices::Cuda;
      }
   #else
      #include <TNL/Devices/Host.h>
      namespace TNL {
         /**
          * \brief The \ref TNL::Devices "device" compatible with Hypre's data
          * structures.
          *
          * The type depends on how the Hypre library was configured. By
          * default, it is \ref Devices::Host. When using Hypre built with CUDA
          * support, it is \ref Devices::Cuda.
          */
         using HYPRE_Device = Devices::Host;
      }
   #endif
// clang-format on

#endif  // HAVE_HYPRE
+408 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading