Closed fnrizzi closed 6 months ago
static constexpr
in ContainersSharedTraits
:
PackageIdentifier package_identifier
bool is_shared_mem
bool is_distributed
int rank
typedef
s in OrdinalTrait
:
ordinal_type
size_type
typedef
s in ScalarTrait
:
scalar_type
reference_type
const_reference_type
static constexpr
in AllocTrait
:
bool is_static
bool is_dynamic = !is_static;
static constexpr
in MatrixDensityTrait
:
bool bool is_sparse
bool is_dense = !is_sparse;
static constexpr
in MatrixLayoutTrait
:
bool is_row_major
bool is_col_major = !is_row_major;
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.
A container type will define one of listed static constexpr
s to identify itself:
VectorIdentifier vector_identifier;
static constexpr MultiVectorIdentifier multi_vector_identifier;
static constexpr MatrixIdentifier matrix_identifier;
static constexpr TensorIdentifier tensor_identifier;
Note: Same information could be carried with simpler and generic
container_type
enumeration (with values for vector, multivector, matrix, tensor, etc.) combined with already existingpackage_identifier
.
@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:
T::memory_space
instead of Traits<T>::memory_space
);package_identifier
(e.g. PackageIdentifier::Eigen
) can be replaced with type detection helpers like is_eigen_vector<T>
;device_type<T>
, execution_space<T>
, have_matching_execution_space<T1, T2>
, have_matching_device_type<T1, T2>
.reference_type
and const_reference_type
, as they seem to carry out expression related logic: https://github.com/Pressio/pressio/blob/a9eb57824bec75953581b6e6cc57d18c1aa9286a/include/pressio/expressions/impl/asdiagonalmatrix_traits.hpp#L86-L92Edit 2022-06-30: plain
int
forrank
, removedsize_type
and{const_}reference_type
@fnrizzi
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
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) |
using pressio::Traits
class
simplify to only have scalar_type
, rank
, get rid of everything else pretty muhc
we decided on:
::pressio::isvector{eigen, kokkos}: this identifies exactly a Kokkos View
::pressio::is_expression_acting_on_kokkos<>
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)
do changes as said above
@fnrizzi
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).
thanks for making this!
@fnrizzi Would you see anything blocking v0.13.0 in this task at this point ?
leaving this open for now
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:
type_traits
: this is where things are defined so obviously mattersexpressions
: only accessscalar_type
andsize_type
of traitsops
: this is where traits are used the most and most completely. this makes sense because ops are specialized for types. However, this approach is not entirely solid because here is a place where we actually know what the concrete types are here, so have common names to query types is not particularly useful. We could in fact extract the info we need from named types directly: for example, if we (partial) specialize an operation for Eigen, we know that we using Eigen, so we could extract from that Eigen type the info needed. One reason to keep the current approach is that if a library changes names for things, we do NOT have to change everything.qr
: similar usage to expressionssolvers_linear, nonlinear
: noneode_steppers
: onlyscalar_type
ode_advancers
: nonerom
: scalar_type andsize_type
These are my observations:
expressions
andops
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
andelement_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:
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):
value_type
, etc) - not enough for us to do any operation ourselves on these types, so user will need to provide implementationsis_subscriptable
, etc)solution plan
I like to solve this with these steps:
Immediate items to work on: