Pressio / pressio

core C++ library
Other
45 stars 7 forks source link

type traits: need to finalize set of type aliases that all types must have #356

Closed fnrizzi closed 6 months ago

fnrizzi commented 2 years ago

what is the problem ?

We currently lack a well-defined specification/definition for what a "trait" should be for any container-like/algebra type (vectors, MV, matrices, etc). Need to fix this. In the various components of pressio, we currently have this usage of traits:

These are my observations:

goal/discussion

we need to have a minimal (easily expandable) well-defined set of traits that we use to reason about types.

For example, we could imrpove things by making the distinction between value_type and element_type like C++ span does: https://en.cppreference.com/w/cpp/container/span And we need to make the traits richer to support a wider specialization flexibility.

Also, long term potentially we want pressio to handle operations on arbitrary types as fall-back scenario when for example users don't provide ops specializations for these arbitrary types. At the very least we need both things that describe things like value type as well as memory features (e.g. shared mem obj, distributed). If we do this well enough, I envision a scenario like this:

// pressio code
namespace pressio{ namespace ops{
 // specialize  gemv for Eigen 
 // specialize  gemv for Kokkos 
 // ... all specializations for libraries we support

 // fallback case: y = A^T*x
 // this would be called only if no other specialization is pickedup
 template<class T> 
 std::enable_if_t< 
   pressio::is_shared_mem_something && 
   is_host_accessible<T> && 
   is_rank_2<T> && 
   subscriptable<T> >
 product(pressio::transpose, const T & v, Eigen::Vector x, Eigen::Vector & y)
 {
   // we know it is sharedmem so we can potentially handle this 
   // even if not super efficiently. if user wants effieciency they have to specialize product for their types.
 }
}}

// user code
namespace pressio{
struct Traits<MyCustomMatrixType>{
  using scalar_type = float;
  // this naming is bad but just to give an idea 
  static constexpr MemEnum objMemSomething = pressio::MemEnum::SharedMemory; // or DistributedMemory
}
}
int main(){ 
  MyCustomMatrixType A(...);
  Eigen::Vector romState (...).
  pressio::rom::something_that_uses_gemv( ...., A, romState);
}

Let's say we have that trait. Then, if the user provides specializations of ops to operate on that great, otherwise we could envision specializing ops to handle that where we can do basic operations because a sufficient number of features is given by the user in the trait to do so. The more we allow the user to refine the trait, the better we can handle types in a generic fashion.

trait levels

The more information user provides in the traits, the better we can handle things (specialize ops):

solution plan

I like to solve this with these steps:

Immediate items to work on:

mzuzek commented 2 years ago

Current set of container type traits

Generic traits

Package-specific traits

TPL-specific traits - like Kokkos host_mem_space_type or Trilinos communicator_type - are defined in type_traits/traits_tpl.hpp.

Note: Maybe some of these traits can be generalized (defined for all container types) to become useful in this context. I'm not sure what's the advantage over direct overloads (i.e. functions specialized for Kokkos containers) since these traits can't be used without prior type check anyway.

Type indetification

A container type will define one of listed static constexprs to identify itself:

Note: Same information could be carried with simpler and generic container_type enumeration (with values for vector, multivector, matrix, tensor, etc.) combined with already existing package_identifier.

mzuzek commented 2 years ago

Container Traits - idea

@fnrizzi Based on https://github.com/Pressio/pressio/pull/359. Essentially, pretty much only rank, scalar_type and ordinal_type (aliased as size_type) are widely used.

With the container type trait set described by following class:

template< int Rank, typename ScalarType, typename OrdinalType >
struct ContainerTraits
{
  using scalar_type  = ScalarType;
  using ordinal_type = OrdinalType;

  static constexpr int rank = Rank;
};

we could define vector and matrix traits like:

//---------- Eigen vector ----------//
template <typename T>
struct Traits<
    T,
    mpl::enable_if_t<is_vector_eigen<T>::value>
> : public ::pressio::impl::ContainerTraits<
      ::pressio::Vector,
      typename T::Scalar,
      typename T::StorageIndex
> {};

//---------- Kokkos dense matrix ----------//
template <typename T>
struct Traits<
  T,
  ::pressio::mpl::enable_if_t<is_dense_matrix_kokkos<T>::value>
> : public ::pressio::impl::ContainerTraits<
      ::pressio::Matrix,
      typename T::traits::value_type,
      typename T::traits::size_type
> {};

//---------- Tpetra multi-vector ----------//
template<typename T>
struct Traits<
  T,
  ::pressio::mpl::enable_if_t<
    is_multi_vector_tpetra<T>::value
    || is_multi_vector_tpetra_block<T>::value>
> : public ::pressio::impl::ContainerTraits<
      ::pressio::MultiVector,
      typename T::impl_scalar_type,
      typename T::local_ordinal_type
> {};

with the following remarks:

Edit 2022-06-30: plain int for rank, removed size_type and {const_}reference_type

mzuzek commented 2 years ago

Traits and their semantics in different libraries

@fnrizzi

Basic Set

trait Pressio Eigen Kokkos Tpetra [PETSc]()
type of element value scalar_type Scalar value_type
const_value_type
non_const_value_type
scalar_type fixed ³
type of element index ordinal_type StorageIndex arbitrary/templated ¹ local_ordinal_type
global_ordinal_type ²
fixed ³
container rank rank - rank
rank_dynamic
- -

¹ there is size_type which derives from memory_space::size_type and seems to be pointer-like byte index instead of logical element index ² for multi-node distributed computing ³ uses PetscScalar for values and PetscInt for indexes (configured via compile flags) ⁴ static vs dynamic allocation

Extended Set

Candidate traits:

trait semantics purpose
reference_type
const_reference_type
type of reference to container value TODO: explain what gains would it provide over auto-detection (in expressions, where these are used)
fnrizzi commented 2 years ago

Traits

predicates based on trait class

OPS

Stages of work

  1. make list of pressio components and list all traits used inside each of them (most likely this is scalar_type and nothing else excpet for ops)

  2. do changes as said above

mzuzek commented 2 years ago

@fnrizzi

Container identifying predicates

Here are some charts that may help navigating container predicates and their relationships. They can also help in completeness verification if (e.g. test coverage).

eigen.png

kokkos.png

tpetra.png

teuchos+epetra.png

fnrizzi commented 2 years ago

thanks for making this!

mzuzek commented 2 years ago

@fnrizzi Would you see anything blocking v0.13.0 in this task at this point ?

fnrizzi commented 2 years ago

leaving this open for now