/* Copyright 2015 The TensorFlow Authors. All Rights Reserved. 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. ==============================================================================*/ #ifndef TENSORFLOW_CORE_FRAMEWORK_TENSOR_TESTUTIL_H_ #define TENSORFLOW_CORE_FRAMEWORK_TENSOR_TESTUTIL_H_ #include #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/lib/gtl/array_slice.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/test.h" namespace tensorflow { namespace test { // Constructs a scalar tensor with 'val'. template Tensor AsScalar(const T& val) { Tensor ret(DataTypeToEnum::value, {}); ret.scalar()() = val; return ret; } // Constructs a flat tensor with 'vals'. template Tensor AsTensor(gtl::ArraySlice vals) { Tensor ret(DataTypeToEnum::value, {static_cast(vals.size())}); std::copy_n(vals.data(), vals.size(), ret.flat().data()); return ret; } // Constructs a tensor of "shape" with values "vals". template Tensor AsTensor(gtl::ArraySlice vals, const TensorShape& shape) { Tensor ret; CHECK(ret.CopyFrom(AsTensor(vals), shape)); return ret; } // Fills in '*tensor' with 'vals'. E.g., // Tensor x(&alloc, DT_FLOAT, TensorShape({2, 2})); // test::FillValues(&x, {11, 21, 21, 22}); template void FillValues(Tensor* tensor, gtl::ArraySlice vals) { auto flat = tensor->flat(); CHECK_EQ(flat.size(), vals.size()); if (flat.size() > 0) { std::copy_n(vals.data(), vals.size(), flat.data()); } } // Fills in '*tensor' with 'vals', converting the types as needed. template void FillValues(Tensor* tensor, std::initializer_list vals) { auto flat = tensor->flat(); CHECK_EQ(flat.size(), vals.size()); if (flat.size() > 0) { size_t i = 0; for (auto itr = vals.begin(); itr != vals.end(); ++itr, ++i) { flat(i) = T(*itr); } } } // Fills in '*tensor' with a sequence of value of val, val+1, val+2, ... // Tensor x(&alloc, DT_FLOAT, TensorShape({2, 2})); // test::FillIota(&x, 1.0); template void FillIota(Tensor* tensor, const T& val) { auto flat = tensor->flat(); std::iota(flat.data(), flat.data() + flat.size(), val); } // Fills in '*tensor' with a sequence of value of fn(0), fn(1), ... // Tensor x(&alloc, DT_FLOAT, TensorShape({2, 2})); // test::FillFn(&x, [](int i)->float { return i*i; }); template void FillFn(Tensor* tensor, std::function fn) { auto flat = tensor->flat(); for (int i = 0; i < flat.size(); ++i) flat(i) = fn(i); } // Expects "x" and "y" are tensors of the same type, same shape, and // identical values. template void ExpectTensorEqual(const Tensor& x, const Tensor& y); // Expects "x" and "y" are tensors of the same type, same shape, and // approximate equal values, each within "abs_err". template void ExpectTensorNear(const Tensor& x, const Tensor& y, const T& abs_err); // Expects "x" and "y" are tensors of the same type (float or double), // same shape and element-wise difference between x and y is no more // than atol + rtol * abs(x). If atol or rtol is negative, it is replaced // with a default tolerance value = data type's epsilon * kSlackFactor. void ExpectClose(const Tensor& x, const Tensor& y, double atol = -1.0, double rtol = -1.0); // Implementation details. namespace internal { template struct is_floating_point_type { static const bool value = std::is_same::value || std::is_same::value || std::is_same::value || std::is_same >::value || std::is_same >::value; }; template inline void ExpectEqual(const T& a, const T& b) { EXPECT_EQ(a, b); } template <> inline void ExpectEqual(const float& a, const float& b) { EXPECT_FLOAT_EQ(a, b); } template <> inline void ExpectEqual(const double& a, const double& b) { EXPECT_DOUBLE_EQ(a, b); } template <> inline void ExpectEqual(const complex64& a, const complex64& b) { EXPECT_FLOAT_EQ(a.real(), b.real()) << a << " vs. " << b; EXPECT_FLOAT_EQ(a.imag(), b.imag()) << a << " vs. " << b; } template <> inline void ExpectEqual(const complex128& a, const complex128& b) { EXPECT_DOUBLE_EQ(a.real(), b.real()) << a << " vs. " << b; EXPECT_DOUBLE_EQ(a.imag(), b.imag()) << a << " vs. " << b; } inline void AssertSameTypeDims(const Tensor& x, const Tensor& y) { ASSERT_EQ(x.dtype(), y.dtype()); ASSERT_TRUE(x.IsSameSize(y)) << "x.shape [" << x.shape().DebugString() << "] vs " << "y.shape [ " << y.shape().DebugString() << "]"; } template ::value> struct Expector; template struct Expector { static void Equal(const T& a, const T& b) { ExpectEqual(a, b); } static void Equal(const Tensor& x, const Tensor& y) { ASSERT_EQ(x.dtype(), DataTypeToEnum::v()); AssertSameTypeDims(x, y); const auto size = x.NumElements(); const T* a = x.flat().data(); const T* b = y.flat().data(); for (int i = 0; i < size; ++i) { ExpectEqual(a[i], b[i]); } } }; // Partial specialization for float and double. template struct Expector { static void Equal(const T& a, const T& b) { ExpectEqual(a, b); } static void Equal(const Tensor& x, const Tensor& y) { ASSERT_EQ(x.dtype(), DataTypeToEnum::v()); AssertSameTypeDims(x, y); const auto size = x.NumElements(); const T* a = x.flat().data(); const T* b = y.flat().data(); for (int i = 0; i < size; ++i) { ExpectEqual(a[i], b[i]); } } static bool Near(const T& a, const T& b, const double abs_err) { // Need a == b so that infinities are close to themselves. return (a == b) || (static_cast(Eigen::numext::abs(a - b)) <= abs_err); } static void Near(const Tensor& x, const Tensor& y, const double abs_err) { ASSERT_EQ(x.dtype(), DataTypeToEnum::v()); AssertSameTypeDims(x, y); const auto size = x.NumElements(); const T* a = x.flat().data(); const T* b = y.flat().data(); for (int i = 0; i < size; ++i) { EXPECT_TRUE(Near(a[i], b[i], abs_err)) << "a = " << a[i] << " b = " << b << " index = " << i; } } }; template struct Helper { // Assumes atol and rtol are nonnegative. static bool IsClose(const T& x, const T& y, const T& atol, const T& rtol) { // Need x == y so that infinities are close to themselves. return (x == y) || (Eigen::numext::abs(x - y) <= atol + rtol * Eigen::numext::abs(x)); } }; template struct Helper> { static bool IsClose(const std::complex& x, const std::complex& y, const T& atol, const T& rtol) { return Helper::IsClose(x.real(), y.real(), atol, rtol) && Helper::IsClose(x.imag(), y.imag(), atol, rtol); } }; } // namespace internal template void ExpectTensorEqual(const Tensor& x, const Tensor& y) { internal::Expector::Equal(x, y); } template void ExpectTensorNear(const Tensor& x, const Tensor& y, const double abs_err) { static_assert(internal::is_floating_point_type::value, "T is not a floating point types."); ASSERT_GE(abs_err, 0.0) << "abs_error is negative" << abs_err; internal::Expector::Near(x, y, abs_err); } } // namespace test } // namespace tensorflow #endif // TENSORFLOW_CORE_FRAMEWORK_TENSOR_TESTUTIL_H_