Custom Data Type
The wiki page: https://bitbucket.org/blaze-lib/blaze/wiki/Vector%20and%20Matrix%20Customization#!custom-data-types describes the steps to get a custom type custom::double_t
working with Blaze.
The page says Blaze assumes operator+()
,operator-()
, operator*()
and operator/()
are implemented for such a custom type.
I’ve come up with a few additional operators that seem to be used in Blaze internals (and are sensible for numerical types) to get some basic Blaze functions to work as documented with a custom::double_t
type.
However, some things (like evaluate and type-traits) needed a hack.
Is there support for custom numerical types? If so what other operators/functions are required to get the use of the full Blaze library?
Comments (4)
-
-
-
assigned issue to
-
assigned issue to
-
- changed status to open
-
- changed status to resolved
Summary
The documentation has been updated to reflect the current state of the Blaze library with regard to custom data types. Also, we've introduced several small improvements that make it even simpler to use custom data types (e.g. no need to deal with the
IsNumeric
type trait). The updated tutorial is immediately available via cloning the Blaze repository. The updated wiki will be released together with Blaze 3.8.Custom Data Types
Introduction
The Blaze library is not restricted to integral, floating point and complex data types (called numeric types in Blaze), but it supports custom data types. For instance, the following example demonstrates that it is possible to use
std::string
as data type:blaze::DynamicVector<std::string> a{ "Hello, ", "Blaze " , "Expression" }; blaze::DynamicVector<std::string> b{ "World" , "Library", " Templates" }; const auto c( evaluate( a + b ) ); std::cout << "c =\n" << c << "\n\n"; const std::string maxString( max( c ) ); std::cout << "maxString = " << std::quoted(maxString) << "\n";
Output:
c = ( Hello, World ) ( Blaze Library ) ( Expression Templates ) maxString = "Hello, World"
Blaze tries hard to make the use of custom data types as convenient, easy and intuitive as possible. In order to work flawlessly with Blaze, custom data types are required to provide a certain interface (depending on the operations that the type is used for). The following sections give an overview of the necessary steps to enable the use of the hypothetical custom data type
custom::double_t
for vector and matrix operations.namespace custom { struct double_t { constexpr double_t() = default; constexpr double_t( double i ) : value( i ) {} double value{}; }; } // namespace custom
Arithmetic Operations
The Blaze library assumes that a custom data type provides
operator<<()
for streaming,operator+=()
andoperator+()
for additions (which for instance includes additions inside matrix/vector multiplications, matrix/matrix multiplications, reduction or norm operations),operator-=()
andoperator-()
for subtractions,operator*=()
andoperator*()
for multiplications andoperator/=()
andoperator/()
for divisions:namespace custom { constexpr double_t& operator+=( double_t& lhs, double_t rhs ) noexcept { lhs.value += rhs.value; return lhs; } constexpr double_t& operator-=( double_t& lhs, double_t rhs ) noexcept { lhs.value -= rhs.value; return lhs; } constexpr double_t& operator*=( double_t& lhs, double_t rhs ) noexcept { lhs.value *= rhs.value; return lhs; } constexpr double_t& operator/=( double_t& lhs, double_t rhs ) noexcept { lhs.value /= rhs.value; return lhs; } constexpr double_t operator+( double_t lhs, double_t rhs ) noexcept { return double_t{ lhs.value + rhs.value }; } constexpr double_t operator-( double_t lhs, double_t rhs ) noexcept { return double_t{ lhs.value - rhs.value }; } constexpr double_t operator*( double_t lhs, double_t rhs ) noexcept { return double_t{ lhs.value * rhs.value }; } constexpr double_t operator/( double_t lhs, double_t rhs ) noexcept { return double_t{ lhs.value / rhs.value }; } inline std::ostream& operator<<( std::ostream& os, double_t d ) { return os << d.value; } } // namespace custom
Example:
int main() { blaze::DynamicVector<custom::double_t> a{ 1.0, 2.0, 3.0, 4.0 }; blaze::DynamicVector<custom::double_t> b{ 0.1, 0.2, 0.3, 0.4 }; std::cout << "a + b =\n" << ( a + b ) << "\n"; std::cout << "a * b =\n" << ( a * b ) << "\n"; std::cout << "sum(a) = " << sum(a) << "\n" << "prod(a) = " << prod(a) << "\n"; }
Output:
a + b = ( 1.1 ) ( 2.2 ) ( 3.3 ) ( 4.4 ) a * b = ( 0.1 ) ( 0.4 ) ( 0.9 ) ( 1.6 ) sum(a) = 10 prod(a) = 24
Note that similar steps are necessary if several custom data types are combined (as for instance
custom::double_t
andcustom::float_t
). Note that in this case both permutations need to be taken into account:custom::double_t operator+( const custom::double_t& a, const custom::float_t& b ); custom::double_t operator+( const custom::float_t& a, const custom::double_t& b ); // ...
Please note that only built-in data types apply for vectorization and thus custom data types cannot achieve maximum performance!
Relational Operations
In order to compare the element type, Blaze expects the equality operator (i.e.
operator==()
) and the inequality operator (i.e.operator!=()
). Alternatively it is possible to provide anequal()
function, which distinguishes between strict and relaxed comparison:namespace custom { constexpr bool operator==( double_t lhs, double_t rhs ) noexcept { return lhs.value == rhs.value; } constexpr bool operator!=( double_t lhs, double_t rhs ) noexcept { return !( lhs == rhs ); } template< blaze::RelaxationFlag RF > constexpr bool equal( double_t lhs, double_t rhs ) noexcept { return blaze::equal<RF>( lhs.value, rhs.value ); } } // namespace custom
Example:
int main() { blaze::DynamicVector<custom::double_t> a{ 1.0, 2.0, 3.0, 4.0 }; blaze::DynamicVector<custom::double_t> b{ 0.1, 0.2, 0.3, 0.4 }; std::cout << "a == b: " << ( a == b ) << "\n" << "a != b: " << ( a != b ) << "\n"; }
Output:
a == b: 0 a != b: 1
Elementwise Operations
For the different kinds of elementwise operations on vectors and matrices (
abs()
,sin()
,cos()
,sqrt()
,log()
,exp()
,min()
,max()
, ...), the custom type is required to provide the according function overload. Note that thesqrt()
operation may also be required for several norm computations. Also, for any inversion operation, the type is required to suport theinv()
function:namespace custom { inline double_t abs ( double_t d ) noexcept { return double_t{ std::abs ( d.value ) }; } inline double_t sin ( double_t d ) noexcept { return double_t{ std::sin ( d.value ) }; } inline double_t cos ( double_t d ) noexcept { return double_t{ std::cos ( d.value ) }; } inline double_t sqrt( double_t d ) noexcept { return double_t{ std::sqrt( d.value ) }; } inline double_t log ( double_t d ) noexcept { return double_t{ std::log ( d.value ) }; } inline double_t exp ( double_t d ) noexcept { return double_t{ std::exp ( d.value ) }; } constexpr double_t inv ( double_t d ) noexcept { return double_t{ 1.0/d.value }; } constexpr double_t min( double_t lhs, double_t rhs ) noexcept { return double_t{ blaze::min( lhs.value, rhs.value ) }; } constexpr double_t max( double_t lhs, double_t rhs ) noexcept { return double_t{ blaze::max( lhs.value, rhs.value ) }; } } // namespace custom
Example:
int main() { blaze::DynamicVector<custom::double_t> a{ 1.0, 2.0, 3.0, 4.0 }; blaze::DynamicVector<custom::double_t> b{ 0.1, 0.2, 0.3, 0.4 }; std::cout << "abs(a) =\n" << abs(a) << "\n"; std::cout << "sin(a) =\n" << sin(a) << "\n"; std::cout << "cos(a) =\n" << cos(a) << "\n"; std::cout << "sqrt(a) =\n" << sqrt(a) << "\n"; std::cout << "log(a) =\n" << log(a) << "\n"; std::cout << "exp(a) =\n" << exp(a) << "\n\n"; std::cout << "min(a) =\n" << min(a) << "\n"; std::cout << "max(a) =\n" << max(a) << "\n\n"; std::cout << "min(a,b) =\n" << min(a,b) << "\n"; std::cout << "max(a,b) =\n" << max(a,b) << "\n"; std::cout << "norm(a) = " << norm(a) << "\n"; }
Output:
abs(a) = ( 1 ) ( 2 ) ( 3 ) ( 4 ) sin(a) = ( 0.841471 ) ( 0.909297 ) ( 0.14112 ) ( -0.756802 ) cos(a) = ( 0.540302 ) ( -0.416147 ) ( -0.989992 ) ( -0.653644 ) sqrt(a) = ( 1 ) ( 1.41421 ) ( 1.73205 ) ( 2 ) log(a) = ( 0 ) ( 0.693147 ) ( 1.09861 ) ( 1.38629 ) exp(a) = ( 2.71828 ) ( 7.38906 ) ( 20.0855 ) ( 54.5982 ) min(a) = 1 max(a) = 4 min(a,b) = ( 0.1 ) ( 0.2 ) ( 0.3 ) ( 0.4 ) max(a,b) = ( 1 ) ( 2 ) ( 3 ) ( 4 ) norm(a) = 5.47723
Adaptors
If the custom data type is used in the context of the
HermitianMatrix
,UniLowerMatrix
, orUniUpperMatrix
adaptors, it will be necessary to provide overloads of theisZero()
,isOne()
, andisReal()
functions:namespace custom { template< blaze::RelaxationFlag RF > constexpr bool isZero( double_t d ) { return blaze::isZero<RF>( d.value ); } template< blaze::RelaxationFlag RF > constexpr bool isOne ( double_t d ) { return blaze::isOne<RF> ( d.value ); } template< blaze::RelaxationFlag RF > constexpr bool isReal( double_t d ) { MAYBE_UNUSED( d ); return true; } } // namespace custom
Example:
int main() { blaze::UniLowerMatrix< blaze::DynamicMatrix<custom::double_t> > L { { 1.0, 0.0, 0.0 }, { 2.0, 1.0, 0.0 }, { 3.0, 4.0, 1.0 } }; blaze::UniUpperMatrix< blaze::DynamicMatrix<custom::double_t> > U { { 1.0, 2.0, 3.0 }, { 0.0, 1.0, 4.0 }, { 0.0, 0.0, 1.0 } }; const auto A( evaluate( L * U ) ); std::cout << "A =\n" << A << "\n"; }
Output:
A = ( 1 2 3 ) ( 2 5 10 ) ( 3 10 26 )
- Log in to comment
Hi!
Thanks for creating this issue. Yes, Blaze provides support for custom data types and is even designed to provide as much customizability as possible. However, the wiki obviously does not explain all details and requirements properly. We will improve this as quickly as possible.
Unfortunately you have also encountered a couple of shortcomings. For that we apologize. We have already fixed all problems that you encountered: First, commit bb0c0c7 fixes the
normalize()
functions for dense and sparse vectors, which haven’t been updated in a long time and also haven’t been tested in combination with custom data types. Second, you should not have to provide anyevaluate()
function for a custom type and noconj()
function for a non-complex custom type. Commits 504957c and f3ac893 fix theevaluate()
andconj()
functions, respectively, such that you’re no longer required to add these functions for your custom type.With these changes, the following custom type should provide everything you need to compile and run your minimum example:
Please feel free to report any other problem that you encounter with custom types. We appreciate the help!
We will resolve this issue once the tutorial and wiki have been updated to reflect the current state. Thanks again for creating this issue,
Best regards,
Klaus!