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 6c2df0f5c..542e55eaa 100644 --- a/include/graphblas/base/internalops.hpp +++ b/include/graphblas/base/internalops.hpp @@ -42,6 +42,117 @@ namespace grb { /** 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 OperatorType: The internal::operator type to negate. + */ + template< + typename 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::OperatorType::has_foldl; + + /** Whether this operator has an inplace 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::OperatorType::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::OperatorType::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::OperatorType::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::OperatorType::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::OperatorType::foldl( c, b ); + *c = !*c; + } + }; + /** * Standard argmin operator. * @@ -1128,7 +1239,7 @@ namespace grb { typename IN1, typename IN2, typename OUT, enum Backend implementation = config::default_backend > - class substract { + class subtract { public: @@ -1676,7 +1787,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, @@ -1728,7 +1839,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. */ @@ -1778,7 +1889,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, @@ -1830,6 +1941,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$. * @@ -1881,7 +2095,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, @@ -2838,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. @@ -4179,6 +4383,9 @@ namespace grb { /** The output domain of this operator. */ typedef typename OperatorBase< OP >::D3 D3; + /** The type of the internal::operator OP. */ + typedef OP OperatorType; + /** * 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/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 e5ff732d5..6fe74f78a 100644 --- a/include/graphblas/ops.hpp +++ b/include/graphblas/ops.hpp @@ -39,6 +39,28 @@ 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 @@ -284,7 +306,7 @@ namespace grb { }; /** - * Numerical substraction of two numbers. + * Numerical subtraction of two numbers. * * Mathematical notation: \f$ \odot(x,y)\ \to\ x - y \f$. * @@ -299,7 +321,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: @@ -471,6 +493,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. * @@ -981,6 +1031,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; @@ -1058,6 +1113,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; @@ -1142,6 +1202,17 @@ 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; + }; + template< typename D1, typename D2, typename D3 > struct is_idempotent< operators::min< D1, D2, D3 >, void > { static const constexpr bool value = true; @@ -1162,6 +1233,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; @@ -1197,7 +1273,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 > @@ -1205,7 +1281,15 @@ 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_logically_negated< + operators::logical_not< OP >, + typename std::enable_if< is_operator< OP >::value, void >::type + > { + static constexpr const bool value = not is_logically_negated< OP >::value; }; // internal type traits follow diff --git a/include/graphblas/type_traits.hpp b/include/graphblas/type_traits.hpp index 1c7fdbd7e..066fb3d48 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_logically_negated { + + 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/CMakeLists.txt b/tests/unit/CMakeLists.txt index 16999fd42..074e81cdd 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( logical_operators logical_operators.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/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/unittests.sh b/tests/unit/unittests.sh index 3817164c8..535d35434 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 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)" $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