From f8a2e7fc4ec2669f5b9a3bde9bcb3d9578a55e47 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Thu, 13 Jul 2023 16:58:50 +0200 Subject: [PATCH 1/6] Implementation of operators::logical_not< Op > --- include/graphblas/base/internalops.hpp | 121 +++++++++++++++++++++++++ include/graphblas/ops.hpp | 29 ++++++ tests/unit/CMakeLists.txt | 4 + tests/unit/operator_logical_not.cpp | 83 +++++++++++++++++ tests/unit/unittests.sh | 6 ++ 5 files changed, 243 insertions(+) create mode 100644 tests/unit/operator_logical_not.cpp diff --git a/include/graphblas/base/internalops.hpp b/include/graphblas/base/internalops.hpp index 6c2df0f5c..ecee1e0e2 100644 --- a/include/graphblas/base/internalops.hpp +++ b/include/graphblas/base/internalops.hpp @@ -39,9 +39,127 @@ namespace grb { namespace operators { + + /** Core implementations of the standard operators in #grb::operators. */ namespace internal { + /** + * Standard negation operator. + * + * Assumes native availability of ! on the given data types or assumes that + * the relevant operators are properly overloaded. + * + * @tparam Op The Operator class to negate. + * Requires the following typedefs: + * - \b D1: The left-hand input domain. + * - \b D2: The right-hand input domain. + * - \b D3: The output domain, must be convertible to bool. + * - \b operator_type: The internal::operator type to negate. + */ + template< + class Op, + enum Backend implementation = config::default_backend + > + class logical_not { + public: + + /** Alias to the left-hand input data type. */ + typedef typename Op::D1 left_type; + + /** Alias to the right-hand input data type. */ + typedef typename Op::D2 right_type; + + /** Alias to the output data type. */ + typedef typename Op::D3 result_type; + + /** Whether this operator has an inplace foldl. */ + static constexpr bool has_foldl = Op::operator_type::has_foldl; + + /** Whether this operator has an inplace foldr. */ + static constexpr bool has_foldr = Op::operator_type::has_foldr; + + /** + * Whether this operator is \em mathematically associative; that is, + * associative when assuming equivalent data types for \a IN1, \a IN2, + * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. + */ + static constexpr bool is_associative = Op::operator_type::is_associative; + + /** + * Whether this operator is \em mathematically commutative; that is, + * commutative when assuming equivalent data types for \a IN1, \a IN2, + * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. + */ + static constexpr bool is_commutative = Op::operator_type::is_commutative; + + /** + * Out-of-place application of the operator. + * + * @param[in] a The left-hand side input. Must be pre-allocated and + * initialised. + * @param[in] b The right-hand side input. Must be pre-allocated and + * initialised. + * @param[out] c The output. Must be pre-allocated. + */ + static void apply( + const left_type * __restrict__ const a, + const right_type * __restrict__ const b, + result_type * __restrict__ const c, + const typename std::enable_if< + std::is_convertible< result_type, bool >::value, + void + >::type * = nullptr + ) { + + Op::operator_type::apply( a, b, c ); + *c = !*c; + + } + + /** + * In-place left-to-right folding. + * + * @param[in] a Pointer to the left-hand side input data. + * @param[in,out] c Pointer to the right-hand side input data. This also + * dubs as the output memory area. + */ + static void foldr( + const left_type * __restrict__ const a, + result_type * __restrict__ const c, + const typename std::enable_if< + std::is_convertible< result_type, bool >::value, + void + >::type * = nullptr + ) { + + Op::operator_type::foldr( a, c ); + *c = !*c; + + } + + /** + * In-place right-to-left folding. + * + * @param[in,out] c Pointer to the left-hand side input data. This also + * dubs as the output memory area. + * @param[in] b Pointer to the right-hand side input data. + */ + static void foldl( + result_type * __restrict__ const c, + const right_type * __restrict__ const b, + const typename std::enable_if< + std::is_convertible< result_type, bool >::value, + void + >::type * = nullptr + ) { + + Op::operator_type::foldl( c, b ); + *c = !*c; + + } + }; + /** * Standard argmin operator. * @@ -4179,6 +4297,9 @@ namespace grb { /** The output domain of this operator. */ typedef typename OperatorBase< OP >::D3 D3; + /** The type of the operator OP. */ + typedef OP operator_type; + /** * Reduces a vector of type \a InputType into a value in \a IOType * by repeated application of this operator. The \a IOType is cast diff --git a/include/graphblas/ops.hpp b/include/graphblas/ops.hpp index e5ff732d5..6a2539b1f 100644 --- a/include/graphblas/ops.hpp +++ b/include/graphblas/ops.hpp @@ -39,6 +39,25 @@ namespace grb { */ namespace operators { + /** + * Standard negation operator. + * + * Allows to wrap any operator and negate its result. + */ + template< + class Op, + enum Backend implementation = config::default_backend + > + class logical_not : public internal::Operator< internal::logical_not< Op > > { + public: + + template< class A > + using GenericOperator = logical_not< A >; + + logical_not() {} + + }; + /** * This operator discards all right-hand side input and simply copies the * left-hand side input to the output variable. It exposes the complete @@ -981,6 +1000,11 @@ namespace grb { } // namespace operators + template< class Op > + struct is_operator< operators::logical_not< Op > > { + static const constexpr bool value = is_operator< Op >::value; + }; + template< typename D1, typename D2, typename D3, enum Backend implementation > struct is_operator< operators::left_assign_if< D1, D2, D3, implementation > > { static const constexpr bool value = true; @@ -1142,6 +1166,11 @@ namespace grb { static const constexpr bool value = true; }; + template< class Op > + struct is_idempotent< operators::logical_not< Op >, void > { + static const constexpr bool value = is_idempotent< Op >::value; + }; + template< typename D1, typename D2, typename D3 > struct is_idempotent< operators::min< D1, D2, D3 >, void > { static const constexpr bool value = true; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 16999fd42..b724f2537 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -247,6 +247,10 @@ add_grb_executables( mxv mxv.cpp BACKENDS reference_omp bsp1d hybrid hyperdags nonblocking ) +add_grb_executables( operator_logical_not operator_logical_not.cpp + BACKENDS reference reference_omp hyperdags nonblocking bsp1d hybrid +) + # must generate the golden output for other tests force_add_grb_executable( vxm vxm.cpp BACKEND reference diff --git a/tests/unit/operator_logical_not.cpp b/tests/unit/operator_logical_not.cpp new file mode 100644 index 000000000..ed7d31f7f --- /dev/null +++ b/tests/unit/operator_logical_not.cpp @@ -0,0 +1,83 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +using namespace grb; + +void grb_program( const size_t & n, grb::RC & rc ) { + (void)n; + + { // Test: logical_and< bool > just to make sure it works + bool value = true; + rc = rc ? rc : foldl( value, true, operators::logical_and< bool >() ); + + // Check results + if( value != true || rc != SUCCESS ) { + rc = FAILED; + std::cerr << "Test logical_and< bool > FAILED\n"; + return; + } + } + + { // Test: logical_not< logical_and< bool > > + bool value = true; + rc = rc ? rc : foldl( value, true, operators::logical_not< operators::logical_and< bool > >() ); + + // Check results + if( value != false || rc != SUCCESS ) { + rc = FAILED; + std::cerr << "Test logical_not< logical_and< bool > > FAILED\n"; + return; + } + } + + // done + return; +} + +int main( int argc, char ** argv ) { + // defaults + bool printUsage = false; + + // error checking + if( argc > 2 ) { + printUsage = true; + } + if( printUsage ) { + std::cerr << "Usage: " << argv[ 0 ] << "\n"; + return 1; + } + + std::cout << "This is functional test " << argv[ 0 ] << "\n"; + grb::Launcher< AUTOMATIC > launcher; + grb::RC out = SUCCESS; + size_t unused = 0; + if( launcher.exec( &grb_program, unused, out, true ) != SUCCESS ) { + std::cerr << "Launching test FAILED\n"; + return 255; + } + if( out != SUCCESS ) { + std::cerr << "Test FAILED (" << grb::toString( out ) << ")" << std::endl; + } else { + std::cout << "Test OK" << std::endl; + } + return 0; +} diff --git a/tests/unit/unittests.sh b/tests/unit/unittests.sh index 3817164c8..4cbd2d006 100755 --- a/tests/unit/unittests.sh +++ b/tests/unit/unittests.sh @@ -384,6 +384,12 @@ for MODE in ${MODES}; do grep "Test OK" ${TEST_OUT_DIR}/argmax_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" echo " " + echo ">>> [x] [ ] Testing grb::operators::logical_not" + $runner ${TEST_BIN_DIR}/operator_logical_not_${MODE}_${BACKEND} 2> ${TEST_OUT_DIR}/operator_logical_not_${MODE}_${BACKEND}_${P}_${T}.err 1> ${TEST_OUT_DIR}/operator_logical_not_${MODE}_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/operator_logical_not_${MODE}_${BACKEND}_${P}_${T}.log + grep "Test OK" ${TEST_OUT_DIR}/operator_logical_not_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + echo " " + echo ">>> [x] [ ] Testing grb::set (matrices)" $runner ${TEST_BIN_DIR}/matrixSet_${MODE}_${BACKEND} 2> ${TEST_OUT_DIR}/matrixSet_${MODE}_${BACKEND}_${P}_${T}.err 1> ${TEST_OUT_DIR}/matrixSet_${MODE}_${BACKEND}_${P}_${T}.log head -1 ${TEST_OUT_DIR}/matrixSet_${MODE}_${BACKEND}_${P}_${T}.log From e4816acf9507ea4c320c5da6dc1824cc099988a4 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Fri, 14 Jul 2023 12:36:57 +0200 Subject: [PATCH 2/6] Handling double-negated operators associativity --- include/graphblas/base/internalops.hpp | 29 +++++++++++--------------- include/graphblas/ops.hpp | 9 ++++++++ include/graphblas/type_traits.hpp | 21 +++++++++++++++++++ tests/unit/operator_logical_not.cpp | 7 ++++--- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/include/graphblas/base/internalops.hpp b/include/graphblas/base/internalops.hpp index ecee1e0e2..ffd388cae 100644 --- a/include/graphblas/base/internalops.hpp +++ b/include/graphblas/base/internalops.hpp @@ -39,8 +39,6 @@ namespace grb { namespace operators { - - /** Core implementations of the standard operators in #grb::operators. */ namespace internal { @@ -55,13 +53,14 @@ namespace grb { * - \b D1: The left-hand input domain. * - \b D2: The right-hand input domain. * - \b D3: The output domain, must be convertible to bool. - * - \b operator_type: The internal::operator type to negate. + * - \b OperatorType: The internal::operator type to negate. */ template< class Op, enum Backend implementation = config::default_backend > class logical_not { + public: /** Alias to the left-hand input data type. */ @@ -74,24 +73,26 @@ namespace grb { typedef typename Op::D3 result_type; /** Whether this operator has an inplace foldl. */ - static constexpr bool has_foldl = Op::operator_type::has_foldl; + static constexpr bool has_foldl = Op::OperatorType::has_foldl; /** Whether this operator has an inplace foldr. */ - static constexpr bool has_foldr = Op::operator_type::has_foldr; + static constexpr bool has_foldr = Op::OperatorType::has_foldr; /** * Whether this operator is \em mathematically associative; that is, * associative when assuming equivalent data types for \a IN1, \a IN2, * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. */ - static constexpr bool is_associative = Op::operator_type::is_associative; + static constexpr bool is_associative = is_lnegated< Op >::value + ? Op::OperatorType::is_associative + : false; /** * Whether this operator is \em mathematically commutative; that is, * commutative when assuming equivalent data types for \a IN1, \a IN2, * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. */ - static constexpr bool is_commutative = Op::operator_type::is_commutative; + static constexpr bool is_commutative = Op::OperatorType::is_commutative; /** * Out-of-place application of the operator. @@ -111,10 +112,8 @@ namespace grb { void >::type * = nullptr ) { - - Op::operator_type::apply( a, b, c ); + Op::OperatorType::apply( a, b, c ); *c = !*c; - } /** @@ -132,10 +131,8 @@ namespace grb { void >::type * = nullptr ) { - - Op::operator_type::foldr( a, c ); + Op::OperatorType::foldr( a, c ); *c = !*c; - } /** @@ -153,10 +150,8 @@ namespace grb { void >::type * = nullptr ) { - - Op::operator_type::foldl( c, b ); + Op::OperatorType::foldl( c, b ); *c = !*c; - } }; @@ -4298,7 +4293,7 @@ namespace grb { typedef typename OperatorBase< OP >::D3 D3; /** The type of the operator OP. */ - typedef OP operator_type; + typedef OP OperatorType; /** * Reduces a vector of type \a InputType into a value in \a IOType diff --git a/include/graphblas/ops.hpp b/include/graphblas/ops.hpp index 6a2539b1f..02e2a285b 100644 --- a/include/graphblas/ops.hpp +++ b/include/graphblas/ops.hpp @@ -1237,6 +1237,15 @@ namespace grb { static constexpr const bool value = OP::is_commutative(); }; + template< typename OP > + struct is_lnegated< + operators::logical_not< OP >, + typename std::enable_if< is_operator< OP >::value, void >::type + > { + static constexpr const bool value = true; + }; + + // internal type traits follow namespace internal { diff --git a/include/graphblas/type_traits.hpp b/include/graphblas/type_traits.hpp index 1c7fdbd7e..10f9077df 100644 --- a/include/graphblas/type_traits.hpp +++ b/include/graphblas/type_traits.hpp @@ -226,6 +226,27 @@ namespace grb { static const constexpr bool value = true; }; + /** + * Used to inspect whether a given operator is logically negated. + * + * @tparam T The operator to inspect. + * + * An example of a commutative operator is numerical addition, + * #grb::operators::add. + * + * \ingroup typeTraits + */ + template< typename T, typename = void > + struct is_lnegated { + + static_assert( is_operator< T >::value, + "Template argument should be an ALP binary operator." ); + + /** Whether \a T is logically negated. */ + static const constexpr bool value = false; + + }; + /** * Used to inspect whether a given operator or monoid is commutative. * diff --git a/tests/unit/operator_logical_not.cpp b/tests/unit/operator_logical_not.cpp index ed7d31f7f..9b82fb98b 100644 --- a/tests/unit/operator_logical_not.cpp +++ b/tests/unit/operator_logical_not.cpp @@ -22,7 +22,7 @@ using namespace grb; -void grb_program( const size_t & n, grb::RC & rc ) { +void grb_program( const size_t &n, grb::RC &rc ) { (void)n; { // Test: logical_and< bool > just to make sure it works @@ -75,9 +75,10 @@ int main( int argc, char ** argv ) { return 255; } if( out != SUCCESS ) { - std::cerr << "Test FAILED (" << grb::toString( out ) << ")" << std::endl; + std::cerr << std::flush; + std::cout << "Test FAILED (" << grb::toString( out ) << ")\n" << std::flush; } else { - std::cout << "Test OK" << std::endl; + std::cout << "Test OK\n" << std::flush; } return 0; } From 5d3c885f8f83980ab4e52658f14186069486ccac Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Sun, 16 Jul 2023 18:16:40 +0200 Subject: [PATCH 3/6] Implementation of logical_xor operator --- include/graphblas/base/internalops.hpp | 111 ++++++++++++++++++++++++- include/graphblas/ops.hpp | 38 +++++++++ tests/unit/operator_logical_not.cpp | 2 + 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/include/graphblas/base/internalops.hpp b/include/graphblas/base/internalops.hpp index ffd388cae..209f99b8f 100644 --- a/include/graphblas/base/internalops.hpp +++ b/include/graphblas/base/internalops.hpp @@ -1789,7 +1789,7 @@ namespace grb { * @param[in] b The right-hand side input. Must be pre-allocated and initialised. * @param[out] c The output. Must be pre-allocated. * - * At the end of the operation, \f$ c = \min\{a,b\} \f$. + * At the end of the operation, \f$ c = \any_or\{a,b\} \f$. */ static void apply( const left_type * __restrict__ const a, @@ -1841,7 +1841,7 @@ namespace grb { }; /** - * The logical or operator, \f$ x \lor y \f$. + * The logical-or operator, \f$ x \lor y \f$. * * Assumes that the || operator is defined on the given input types. */ @@ -1891,7 +1891,7 @@ namespace grb { * initialised. * @param[out] c The output. Must be pre-allocated. * - * At the end of the operation, \f$ c = \min\{a,b\} \f$. + * At the end of the operation, \f$ c = \or\{a,b\} \f$. */ static void apply( const left_type * __restrict__ const a, @@ -1943,6 +1943,109 @@ namespace grb { }; + /** + * The logical-xor operator, \f$ x \lxor y \f$. + * + * Assumes that the xor operator is defined on the given input types. + */ + template< + typename IN1, typename IN2, typename OUT, + enum Backend implementation = config::default_backend + > + class logical_xor { + + public: + + /** Alias to the left-hand input data type. */ + typedef IN1 left_type; + + /** Alias to the right-hand input data type. */ + typedef IN2 right_type; + + /** Alias to the output data type. */ + typedef OUT result_type; + + /** Whether this operator has an in-place foldl. */ + static constexpr bool has_foldl = true; + + /** Whether this operator has an in-place foldr. */ + static constexpr bool has_foldr = true; + + /** + * Whether this operator is \em mathematically associative; that is, + * associative when assuming equivalent data types for \a IN1, \a IN2, + * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. + */ + static constexpr bool is_associative = true; + + /** + * Whether this operator is \em mathematically commutative; that is, + * commutative when assuming equivalent data types for \a IN1, \a IN2, + * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. + */ + static constexpr bool is_commutative = true; + + /** + * Out-of-place application of this operator. + * + * @param[in] a The left-hand side input. Must be pre-allocated and + * initialised. + * @param[in] b The right-hand side input. Must be pre-allocated and + * initialised. + * @param[out] c The output. Must be pre-allocated. + * + * At the end of the operation, \f$ c = \xor\{a,b\} \f$. + */ + static void apply( + const left_type * __restrict__ const a, + const right_type * __restrict__ const b, + result_type * __restrict__ const c + ) { + if( *a xor *b ) { + *c = static_cast< OUT >( true ); + } else { + *c = static_cast< OUT >( false ); + } + } + + /** + * In-place left-to-right folding. + * + * @param[in] a Pointer to the left-hand side input data. + * @param[in,out] c Pointer to the right-hand side input data. This also + * dubs as the output memory area. + */ + static void foldr( + const left_type * __restrict__ const a, + result_type * __restrict__ const c + ) { + if( *a xor *c ) { + *c = static_cast< result_type >( true ); + } else { + *c = static_cast< result_type >( false ); + } + } + + /** + * In-place right-to-left folding. + * + * @param[in,out] c Pointer to the left-hand side input data. This also + * dubs as the output memory area. + * @param[in] b Pointer to the right-hand side input data. + */ + static void foldl( + result_type * __restrict__ const c, + const right_type * __restrict__ const b + ) { + if( *b xor *c ) { + *c = static_cast< result_type >( true ); + } else { + *c = static_cast< result_type >( false ); + } + } + + }; + /** * The logical-and operator, \f$ x \land y \f$. * @@ -1994,7 +2097,7 @@ namespace grb { * initialised. * @param[out] c The output. Must be pre-allocated. * - * At the end of the operation, \f$ c = \min\{a,b\} \f$. + * At the end of the operation, \f$ c = \and\{a,b\} \f$. */ static void apply( const left_type * __restrict__ const a, diff --git a/include/graphblas/ops.hpp b/include/graphblas/ops.hpp index 02e2a285b..f74d4c3a9 100644 --- a/include/graphblas/ops.hpp +++ b/include/graphblas/ops.hpp @@ -490,6 +490,34 @@ namespace grb { logical_or() {} }; + /** + * The logical xor. + * + * It returns true whenever one and one only of its inputs + * evaluate true, and returns false otherwise. + * + * If the output domain is not Boolean, then the returned value is + * true or false cast to the output domain. + * + * \warning Thus both input domains and the output domain must be + * \em castable to bool. + */ + template< + typename D1, typename D2 = D1, typename D3 = D2, + enum Backend implementation = config::default_backend + > + class logical_xor : public internal::Operator< + internal::logical_xor< D1, D2, D3, implementation > + > { + + public: + + template< typename A, typename B, typename C, enum Backend D > + using GenericOperator = logical_xor< A, B, C, D >; + + logical_xor() {} + }; + /** * The logical and. * @@ -1082,6 +1110,11 @@ namespace grb { static const constexpr bool value = true; }; + template< typename D1, typename D2, typename D3, enum Backend implementation > + struct is_operator< operators::logical_xor< D1, D2, D3, implementation > > { + static const constexpr bool value = true; + }; + template< typename D1, typename D2, typename D3, enum Backend implementation > struct is_operator< operators::logical_and< D1, D2, D3, implementation > > { static const constexpr bool value = true; @@ -1191,6 +1224,11 @@ namespace grb { static const constexpr bool value = true; }; + template< typename D1, typename D2, typename D3 > + struct is_idempotent< operators::logical_xor< D1, D2, D3 >, void > { + static const constexpr bool value = true; + }; + template< typename D1, typename D2, typename D3 > struct is_idempotent< operators::logical_and< D1, D2, D3 >, void > { static const constexpr bool value = true; diff --git a/tests/unit/operator_logical_not.cpp b/tests/unit/operator_logical_not.cpp index 9b82fb98b..ab78f51d8 100644 --- a/tests/unit/operator_logical_not.cpp +++ b/tests/unit/operator_logical_not.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include From 91cc61c7e735ceb9e99e3ad44a6be9adb586c9b0 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Mon, 17 Jul 2023 11:18:17 +0200 Subject: [PATCH 4/6] Typo: subStract -> subtract --- include/graphblas/banshee/blas1.hpp | 2 +- include/graphblas/banshee/internalops.hpp | 2 +- include/graphblas/base/internalops.hpp | 4 ++-- include/graphblas/bsp1d/init.hpp | 2 +- include/graphblas/ops.hpp | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/graphblas/banshee/blas1.hpp b/include/graphblas/banshee/blas1.hpp index d320e30b1..896295641 100644 --- a/include/graphblas/banshee/blas1.hpp +++ b/include/graphblas/banshee/blas1.hpp @@ -3491,7 +3491,7 @@ namespace grb { for( size_t b = 0; b < Ring::blocksize; ++b, ++i ) { // if we end up with a zero value if( sparse && yy[ b ] == ring.template getZero< typename Ring::D4 >() ) { - // then substract it from the set of nonzeroes stored + // then subtract it from the set of nonzeroes stored if( internal::getCoordinates( _z ).assigned( i ) ) { internal::getRaw( _z )[ i ] = ring.template getZero< typename Ring::D4 >(); } diff --git a/include/graphblas/banshee/internalops.hpp b/include/graphblas/banshee/internalops.hpp index 6fc0d6b7b..6ec72e057 100644 --- a/include/graphblas/banshee/internalops.hpp +++ b/include/graphblas/banshee/internalops.hpp @@ -129,7 +129,7 @@ namespace grb { /** \todo add documentation */ template< typename IN1, typename IN2, typename OUT > - class substract< IN1, IN2, OUT, banshee_ssr > { + class subtract< IN1, IN2, OUT, banshee_ssr > { public: /** Alias to the left-hand input data type. */ typedef IN1 left_type; diff --git a/include/graphblas/base/internalops.hpp b/include/graphblas/base/internalops.hpp index 209f99b8f..cfd528b7e 100644 --- a/include/graphblas/base/internalops.hpp +++ b/include/graphblas/base/internalops.hpp @@ -1241,7 +1241,7 @@ namespace grb { typename IN1, typename IN2, typename OUT, enum Backend implementation = config::default_backend > - class substract { + class subtract { public: @@ -4395,7 +4395,7 @@ namespace grb { /** The output domain of this operator. */ typedef typename OperatorBase< OP >::D3 D3; - /** The type of the operator OP. */ + /** The type of the internal::operator OP. */ typedef OP OperatorType; /** diff --git a/include/graphblas/bsp1d/init.hpp b/include/graphblas/bsp1d/init.hpp index da3333cb4..b1efc77e1 100644 --- a/include/graphblas/bsp1d/init.hpp +++ b/include/graphblas/bsp1d/init.hpp @@ -315,7 +315,7 @@ namespace grb { * Decrements \a regs_taken. * * @param[in] count (Optional) The number of memslots that should be - * substracted from \a regs_taken. Default is 1. Passing + * subtracted from \a regs_taken. Default is 1. Passing * zero will turn a call to this function into a no-op. */ void signalMemslotReleased( const unsigned int count = 1 ); diff --git a/include/graphblas/ops.hpp b/include/graphblas/ops.hpp index f74d4c3a9..dc7e4e5ad 100644 --- a/include/graphblas/ops.hpp +++ b/include/graphblas/ops.hpp @@ -41,7 +41,7 @@ namespace grb { /** * Standard negation operator. - * + * * Allows to wrap any operator and negate its result. */ template< @@ -303,7 +303,7 @@ namespace grb { }; /** - * Numerical substraction of two numbers. + * Numerical subtraction of two numbers. * * Mathematical notation: \f$ \odot(x,y)\ \to\ x - y \f$. * @@ -318,7 +318,7 @@ namespace grb { enum Backend implementation = config::default_backend > class subtract : public internal::Operator< - internal::substract< D1, D2, D3, implementation > + internal::subtract< D1, D2, D3, implementation > > { public: @@ -493,13 +493,13 @@ namespace grb { /** * The logical xor. * - * It returns true whenever one and one only of its inputs + * It returns true whenever one and one only of its inputs * evaluate true, and returns false otherwise. * * If the output domain is not Boolean, then the returned value is * true or false cast to the output domain. * - * \warning Thus both input domains and the output domain must be + * \warning Thus both input domains and the output domain must be * \em castable to bool. */ template< From ca03c462c62293238ee7484a1ee54710676923f6 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Mon, 17 Jul 2023 14:03:44 +0200 Subject: [PATCH 5/6] Move double-negation logic to type_traits --- include/graphblas/base/internalops.hpp | 16 +- include/graphblas/ops.hpp | 14 +- include/graphblas/type_traits.hpp | 2 +- tests/unit/CMakeLists.txt | 2 +- tests/unit/logical_operators.cpp | 272 +++++++++++++++++++++++++ tests/unit/operator_logical_not.cpp | 86 -------- tests/unit/unittests.sh | 8 +- 7 files changed, 288 insertions(+), 112 deletions(-) create mode 100644 tests/unit/logical_operators.cpp delete mode 100644 tests/unit/operator_logical_not.cpp diff --git a/include/graphblas/base/internalops.hpp b/include/graphblas/base/internalops.hpp index cfd528b7e..542e55eaa 100644 --- a/include/graphblas/base/internalops.hpp +++ b/include/graphblas/base/internalops.hpp @@ -56,7 +56,7 @@ namespace grb { * - \b OperatorType: The internal::operator type to negate. */ template< - class Op, + typename Op, enum Backend implementation = config::default_backend > class logical_not { @@ -83,9 +83,7 @@ namespace grb { * associative when assuming equivalent data types for \a IN1, \a IN2, * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. */ - static constexpr bool is_associative = is_lnegated< Op >::value - ? Op::OperatorType::is_associative - : false; + static constexpr bool is_associative = Op::OperatorType::is_associative; /** * Whether this operator is \em mathematically commutative; that is, @@ -3054,16 +3052,6 @@ namespace grb { public: - /** @return Whether this operator is mathematically associative. */ - static constexpr bool is_associative() { - return OP::is_associative; - } - - /** @return Whether this operator is mathematically commutative. */ - static constexpr bool is_commutative() { - return OP::is_commutative; - } - /** * Straightforward application of this operator. Computes \f$ x \odot y \f$ * and stores the result in \a z. diff --git a/include/graphblas/ops.hpp b/include/graphblas/ops.hpp index dc7e4e5ad..5d278fff2 100644 --- a/include/graphblas/ops.hpp +++ b/include/graphblas/ops.hpp @@ -48,7 +48,10 @@ namespace grb { class Op, enum Backend implementation = config::default_backend > - class logical_not : public internal::Operator< internal::logical_not< Op > > { + class logical_not : public internal::Operator< + internal::logical_not< Op > + > { + public: template< class A > @@ -1264,7 +1267,7 @@ namespace grb { OP, typename std::enable_if< is_operator< OP >::value, void >::type > { - static constexpr const bool value = OP::is_associative(); + static constexpr const bool value = is_logically_negated::value ? false : OP::OperatorType::is_associative; }; template< typename OP > @@ -1272,18 +1275,17 @@ namespace grb { OP, typename std::enable_if< is_operator< OP >::value, void >::type > { - static constexpr const bool value = OP::is_commutative(); + static constexpr const bool value = OP::OperatorType::is_commutative; }; template< typename OP > - struct is_lnegated< + struct is_logically_negated< operators::logical_not< OP >, typename std::enable_if< is_operator< OP >::value, void >::type > { - static constexpr const bool value = true; + static constexpr const bool value = not is_logically_negated< OP >::value; }; - // internal type traits follow namespace internal { diff --git a/include/graphblas/type_traits.hpp b/include/graphblas/type_traits.hpp index 10f9077df..066fb3d48 100644 --- a/include/graphblas/type_traits.hpp +++ b/include/graphblas/type_traits.hpp @@ -237,7 +237,7 @@ namespace grb { * \ingroup typeTraits */ template< typename T, typename = void > - struct is_lnegated { + struct is_logically_negated { static_assert( is_operator< T >::value, "Template argument should be an ALP binary operator." ); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index b724f2537..074e81cdd 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -247,7 +247,7 @@ add_grb_executables( mxv mxv.cpp BACKENDS reference_omp bsp1d hybrid hyperdags nonblocking ) -add_grb_executables( operator_logical_not operator_logical_not.cpp +add_grb_executables( logical_operators logical_operators.cpp BACKENDS reference reference_omp hyperdags nonblocking bsp1d hybrid ) diff --git a/tests/unit/logical_operators.cpp b/tests/unit/logical_operators.cpp new file mode 100644 index 000000000..c3707f897 --- /dev/null +++ b/tests/unit/logical_operators.cpp @@ -0,0 +1,272 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include + +using namespace grb; +using namespace grb::operators; + +constexpr std::array< std::pair< bool, bool >, 4 > test_values = { + std::make_pair( false, false ), + std::make_pair( false, true ), + std::make_pair( true, false ), + std::make_pair( true, true ) +}; + +template< + Descriptor descr = descriptors::no_operation, + typename OP +> +void test_apply( + RC &rc, + std::array< bool, 4 > &values, + const std::array< bool, 4 > &expected +) { + rc = apply< descr, OP >( values[0], false, false ); + rc = rc ? rc : apply< descr, OP >( values[1], false, true ); + rc = rc ? rc : apply< descr, OP >( values[2], true, false ); + rc = rc ? rc : apply< descr, OP >( values[3], true, true ); + if( ! std::equal( values.cbegin(), values.cend(), expected.cbegin() ) ) { + rc = FAILED; + } +} + +template< + Descriptor descr = descriptors::no_operation, + typename OP +> +void test_foldl( + RC &rc, + std::array< bool, 4 > &values, + const std::array< bool, 4 > &expected +) { + values[0] = false; + rc = foldl< descr, OP >( values[0], false ); + values[1] = false; + rc = rc ? rc : foldl< descr, OP >( values[1], true ); + values[2] = true; + rc = rc ? rc : foldl< descr, OP >( values[2], false ); + values[3] = true; + rc = rc ? rc : foldl< descr, OP >( values[3], true ); + if( ! std::equal( values.cbegin(), values.cend(), expected.cbegin() ) ) { + rc = FAILED; + } +} + +template< + Descriptor descr = descriptors::no_operation, + typename OP +> +void test_foldr( + RC &rc, + std::array< bool, 4 > &values, + const std::array< bool, 4 > &expected +) { + values[0] = false; + rc = foldr< descr, OP >( false, values[0] ); + values[1] = false; + rc = rc ? rc : foldr< descr, OP >( true, values[1] ); + values[2] = true; + rc = rc ? rc : foldr< descr, OP >( false, values[2] ); + values[3] = true; + rc = rc ? rc : foldr< descr, OP >( true, values[3] ); + if( ! std::equal( values.cbegin(), values.cend(), expected.cbegin() ) ) { + rc = FAILED; + } +} + +template< + Descriptor descr = descriptors::no_operation, + typename OP +> +void test_operator( + RC &rc, + const std::array< bool, 4 > &expected, + const bool expected_associative, + const typename std::enable_if< + grb::is_operator< OP >::value, + void + >::type* = nullptr +) { + + if( grb::is_associative< OP >::value != expected_associative ) { + std::cerr << "Operator associtativity property is " + << grb::is_associative< OP >::value << ", should be " + << expected_associative << "\n"; + rc = FAILED; + return; + } + + std::array< bool, 4 > values; + + test_apply< descr, OP >( rc, values, expected ); + if( rc != SUCCESS ) { + std::cerr << "Test_apply FAILED\n"; + std::cerr << "values ?= expected\n"; + for( size_t i = 0; i < 4; i++ ) { + std::cerr << "OP( " << test_values[i].first << ";" + << test_values[i].second << " ): "<< values[i] << " ?= " + << expected[i] << "\n"; + } + return; + } + test_foldl< descr, OP >( rc, values, expected ); + if( rc != SUCCESS ) { + std::cerr << "Test_foldl FAILED\n"; + std::cerr << "values ?= expected\n"; + for( size_t i = 0; i < 4; i++ ) { + std::cerr << "OP( " << test_values[i].first << ";" + << test_values[i].second << " ): "<< values[i] << " ?= " + << expected[i] << "\n"; + } + return; + } + test_foldr< descr, OP >( rc, values, expected ); + if( rc != SUCCESS ) { + std::cerr << "Test_foldr FAILED\n"; + std::cerr << "values ?= expected\n"; + for( size_t i = 0; i < 4; i++ ) { + std::cerr << "OP( " << test_values[i].first << ";" + << test_values[i].second << " ): "<< values[i] << " ?= " + << expected[i] << "\n"; + } + return; + } +} + +void grb_program( const size_t&, grb::RC &rc ) { + rc = SUCCESS; + + // Logical operators + { // logical_and< bool > + std::cout << "Testing operator: logical_and" << std::endl; + const std::array expected = { false, false, false, true }; + bool expected_associative = true; + test_operator< + descriptors::no_operation, + logical_and< bool > + >( rc, expected, expected_associative ); + } + { // logical_or< bool > + std::cout << "Testing operator: logical_or" << std::endl; + const std::array expected = { false, true, true, true }; + bool expected_associative = true; + test_operator< + descriptors::no_operation, + logical_or< bool > + >( rc, expected, expected_associative ); + } + { // logical_xor< bool > + std::cout << "Testing operator: logical_xor" << std::endl; + const std::array expected = { false, true, true, false }; + bool expected_associative = true; + test_operator< + descriptors::no_operation, + logical_xor< bool > + >( rc, expected, expected_associative ); + } + + // Negated operators + { // logical_not< logical_and< bool > > + std::cout << "Testing operator: logical_not< logical_and< bool > >" << std::endl; + const std::array expected = { true, true, true, false }; + bool expected_associative = false; + test_operator< + descriptors::no_operation, + logical_not< logical_and< bool > > + >( rc, expected, expected_associative ); + } + { // logical_not< logical_or< bool > > + std::cout << "Testing operator: logical_not< logical_or< bool > >" << std::endl; + const std::array expected = { true, false, false, false }; + bool expected_associative = false; + test_operator< + descriptors::no_operation, + logical_not< logical_or< bool > > + >( rc, expected, expected_associative ); + } + { // logical_not< logical_xor< bool > > + std::cout << "Testing operator: logical_not< logical_xor< bool > >" << std::endl; + const std::array expected = { true, false, false, true }; + bool expected_associative = false; + test_operator< + descriptors::no_operation, + logical_not< logical_xor< bool > > + >( rc, expected, expected_associative ); + } + + // Double-negated operators + { // logical_not< logical_not < logical_and< bool > > > + std::cout << "Testing operator: logical_not< logical_not < logical_and< bool > > >" << std::endl; + const std::array expected = { false, false, false, true }; + bool expected_associative = true; + test_operator< + descriptors::no_operation, + logical_not< logical_not< logical_and< bool > > > + >( rc, expected, expected_associative ); + } + { // logical_not< logical_not < logical_or< bool > > > + std::cout << "Testing operator: logical_not< logical_not < logical_or< bool > > >" << std::endl; + const std::array expected = { false, true, true, true }; + bool expected_associative = true; + test_operator< + descriptors::no_operation, + logical_not< logical_not< logical_or< bool > > > + >( rc, expected, expected_associative ); + } + { // logical_not< logical_not < logical_xor< bool > > > + std::cout << "Testing operator: logical_not< logical_not < logical_xor< bool > > >" << std::endl; + const std::array expected = { false, true, true, false }; + bool expected_associative = true; + test_operator< + descriptors::no_operation, + logical_not< logical_not< logical_xor< bool > > > + >( rc, expected, expected_associative ); + } +} + +int main( int argc, char ** argv ) { + // error checking + if( argc > 2 ) { + std::cerr << "Usage: " << argv[ 0 ] << "\n"; + return 1; + } + + std::cout << "This is functional test " << argv[ 0 ] << "\n"; + + grb::Launcher< AUTOMATIC > launcher; + RC out = SUCCESS; + size_t unused = 0; + if( launcher.exec( &grb_program, unused, out, true ) != SUCCESS ) { + std::cerr << "Launching test FAILED\n"; + return 255; + } + if( out != SUCCESS ) { + std::cerr << std::flush; + std::cout << "Test FAILED (" << grb::toString( out ) << ")\n" << std::flush; + } else { + std::cout << "Test OK\n" << std::flush; + } + return 0; +} diff --git a/tests/unit/operator_logical_not.cpp b/tests/unit/operator_logical_not.cpp deleted file mode 100644 index ab78f51d8..000000000 --- a/tests/unit/operator_logical_not.cpp +++ /dev/null @@ -1,86 +0,0 @@ - -/* - * Copyright 2021 Huawei Technologies Co., Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -#include - -using namespace grb; - -void grb_program( const size_t &n, grb::RC &rc ) { - (void)n; - - { // Test: logical_and< bool > just to make sure it works - bool value = true; - rc = rc ? rc : foldl( value, true, operators::logical_and< bool >() ); - - // Check results - if( value != true || rc != SUCCESS ) { - rc = FAILED; - std::cerr << "Test logical_and< bool > FAILED\n"; - return; - } - } - - { // Test: logical_not< logical_and< bool > > - bool value = true; - rc = rc ? rc : foldl( value, true, operators::logical_not< operators::logical_and< bool > >() ); - - // Check results - if( value != false || rc != SUCCESS ) { - rc = FAILED; - std::cerr << "Test logical_not< logical_and< bool > > FAILED\n"; - return; - } - } - - // done - return; -} - -int main( int argc, char ** argv ) { - // defaults - bool printUsage = false; - - // error checking - if( argc > 2 ) { - printUsage = true; - } - if( printUsage ) { - std::cerr << "Usage: " << argv[ 0 ] << "\n"; - return 1; - } - - std::cout << "This is functional test " << argv[ 0 ] << "\n"; - grb::Launcher< AUTOMATIC > launcher; - grb::RC out = SUCCESS; - size_t unused = 0; - if( launcher.exec( &grb_program, unused, out, true ) != SUCCESS ) { - std::cerr << "Launching test FAILED\n"; - return 255; - } - if( out != SUCCESS ) { - std::cerr << std::flush; - std::cout << "Test FAILED (" << grb::toString( out ) << ")\n" << std::flush; - } else { - std::cout << "Test OK\n" << std::flush; - } - return 0; -} diff --git a/tests/unit/unittests.sh b/tests/unit/unittests.sh index 4cbd2d006..535d35434 100755 --- a/tests/unit/unittests.sh +++ b/tests/unit/unittests.sh @@ -384,10 +384,10 @@ for MODE in ${MODES}; do grep "Test OK" ${TEST_OUT_DIR}/argmax_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" echo " " - echo ">>> [x] [ ] Testing grb::operators::logical_not" - $runner ${TEST_BIN_DIR}/operator_logical_not_${MODE}_${BACKEND} 2> ${TEST_OUT_DIR}/operator_logical_not_${MODE}_${BACKEND}_${P}_${T}.err 1> ${TEST_OUT_DIR}/operator_logical_not_${MODE}_${BACKEND}_${P}_${T}.log - head -1 ${TEST_OUT_DIR}/operator_logical_not_${MODE}_${BACKEND}_${P}_${T}.log - grep "Test OK" ${TEST_OUT_DIR}/operator_logical_not_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + echo ">>> [x] [ ] Testing logical grb::operators along with grb::logical_not< OP >" + $runner ${TEST_BIN_DIR}/logical_operators_${MODE}_${BACKEND} 2> ${TEST_OUT_DIR}/logical_operators_${MODE}_${BACKEND}_${P}_${T}.err 1> ${TEST_OUT_DIR}/logical_operators_${MODE}_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/logical_operators_${MODE}_${BACKEND}_${P}_${T}.log + grep "Test OK" ${TEST_OUT_DIR}/logical_operators_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" echo " " echo ">>> [x] [ ] Testing grb::set (matrices)" From 94e5065bc68f35ac047075a67e816d23e89c798e Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Tue, 24 Oct 2023 17:01:47 +0200 Subject: [PATCH 6/6] Add documentation to is_idempotent for logical_not --- include/graphblas/ops.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/graphblas/ops.hpp b/include/graphblas/ops.hpp index 5d278fff2..6fe74f78a 100644 --- a/include/graphblas/ops.hpp +++ b/include/graphblas/ops.hpp @@ -1202,6 +1202,12 @@ namespace grb { static const constexpr bool value = true; }; + /** + * This struct template specialization determines if the logical_not operator is + * idempotent by checking if the operator being negated is idempotent. + * If the operator is idempotent, then negating its result will not change the + * result, and the operator remains idempotent. + */ template< class Op > struct is_idempotent< operators::logical_not< Op >, void > { static const constexpr bool value = is_idempotent< Op >::value;