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