/* * Copyright (c) 2017 ARM Limited. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef __ARM_COMPUTE_TEST_UTILS_H__ #define __ARM_COMPUTE_TEST_UTILS_H__ #include "arm_compute/core/Coordinates.h" #include "arm_compute/core/Error.h" #include "arm_compute/core/FixedPoint.h" #include "arm_compute/core/TensorShape.h" #include "arm_compute/core/Types.h" #include #include #include #include #include #include #include #if ARM_COMPUTE_ENABLE_FP16 #include // needed for float16_t #endif namespace arm_compute { namespace test { namespace cpp11 { #ifdef __ANDROID__ /** Convert integer and float values to string. * * @note This function implements the same behaviour as std::to_string. The * latter is missing in some Android toolchains. * * @param[in] value Value to be converted to string. * * @return String representation of @p value. */ template ::type>::value, int>::type = 0> std::string to_string(T && value) { std::stringstream stream; stream << std::forward(value); return stream.str(); } /** Convert string values to integer. * * @note This function implements the same behaviour as std::stoi. The latter * is missing in some Android toolchains. * * @param[in] str String to be converted to int. * * @return Integer representation of @p str. */ inline int stoi(const std::string &str) { std::stringstream stream(str); int value = 0; stream >> value; return value; } /** Convert string values to unsigned long. * * @note This function implements the same behaviour as std::stoul. The latter * is missing in some Android toolchains. * * @param[in] str String to be converted to unsigned long. * * @return Unsigned long representation of @p str. */ inline unsigned long stoul(const std::string &str) { std::stringstream stream(str); unsigned long value = 0; stream >> value; return value; } /** Convert string values to float. * * @note This function implements the same behaviour as std::stof. The latter * is missing in some Android toolchains. * * @param[in] str String to be converted to float. * * @return Float representation of @p str. */ inline float stof(const std::string &str) { std::stringstream stream(str); float value = 0.f; stream >> value; return value; } /** Round floating-point value with half value rounding away from zero. * * @note This function implements the same behaviour as std::round except that it doesn't * support Integral type. The latter is not in the namespace std in some Android toolchains. * * @param[in] value floating-point value to be rounded. * * @return Floating-point value of rounded @p value. */ template ::value>::type> inline T round(T value) { return ::round(value); } /** Truncate floating-point value. * * @note This function implements the same behaviour as std::truncate except that it doesn't * support Integral type. The latter is not in the namespace std in some Android toolchains. * * @param[in] value floating-point value to be truncated. * * @return Floating-point value of truncated @p value. */ template ::value>::type> inline T trunc(T value) { return ::trunc(value); } /** Composes a floating point value with the magnitude of @p x and the sign of @p y. * * @note This function implements the same behaviour as std::copysign except that it doesn't * support Integral type. The latter is not in the namespace std in some Android toolchains. * * @param[in] x value that contains the magnitued to be used in constructing the result. * @param[in] y value that contains the sign to be used in constructin the result. * * @return Floating-point value with magnitude of @p x and sign of @p y. */ template ::value>::type> inline T copysign(T x, T y) { return ::copysign(x, y); } #else /** Convert integer and float values to string. * * @note This function acts as a convenience wrapper around std::to_string. The * latter is missing in some Android toolchains. * * @param[in] value Value to be converted to string. * * @return String representation of @p value. */ template std::string to_string(T &&value) { return ::std::to_string(std::forward(value)); } /** Convert string values to integer. * * @note This function acts as a convenience wrapper around std::stoi. The * latter is missing in some Android toolchains. * * @param[in] args Arguments forwarded to std::stoi. * * @return Integer representation of input string. */ template int stoi(Ts &&... args) { return ::std::stoi(std::forward(args)...); } /** Convert string values to unsigned long. * * @note This function acts as a convenience wrapper around std::stoul. The * latter is missing in some Android toolchains. * * @param[in] args Arguments forwarded to std::stoul. * * @return Unsigned long representation of input string. */ template int stoul(Ts &&... args) { return ::std::stoul(std::forward(args)...); } /** Convert string values to float. * * @note This function acts as a convenience wrapper around std::stof. The * latter is missing in some Android toolchains. * * @param[in] args Arguments forwarded to std::stof. * * @return Float representation of input string. */ template int stof(Ts &&... args) { return ::std::stof(std::forward(args)...); } /** Round floating-point value with half value rounding away from zero. * * @note This function implements the same behaviour as std::round except that it doesn't * support Integral type. The latter is not in the namespace std in some Android toolchains. * * @param[in] value floating-point value to be rounded. * * @return Floating-point value of rounded @p value. */ template ::value>::type> inline T round(T value) { return std::round(value); } /** Truncate floating-point value. * * @note This function implements the same behaviour as std::truncate except that it doesn't * support Integral type. The latter is not in the namespace std in some Android toolchains. * * @param[in] value floating-point value to be truncated. * * @return Floating-point value of truncated @p value. */ template ::value>::type> inline T trunc(T value) { return std::trunc(value); } /** Composes a floating point value with the magnitude of @p x and the sign of @p y. * * @note This function implements the same behaviour as std::copysign except that it doesn't * support Integral type. The latter is not in the namespace std in some Android toolchains. * * @param[in] x value that contains the magnitued to be used in constructing the result. * @param[in] y value that contains the sign to be used in constructin the result. * * @return Floating-point value with magnitude of @p x and sign of @p y. */ template ::value>::type> inline T copysign(T x, T y) { return std::copysign(x, y); } #endif /** Round floating-point value with half value rounding to positive infinity. * * @param[in] value floating-point value to be rounded. * * @return Floating-point value of rounded @p value. */ template ::value>::type> inline T round_half_up(T value) { return std::floor(value + 0.5f); } /** Round floating-point value with half value rounding to nearest even. * * @param[in] value floating-point value to be rounded. * @param[in] epsilon precision. * * @return Floating-point value of rounded @p value. */ template ::value>::type> inline T round_half_even(T value, T epsilon = std::numeric_limits::epsilon()) { T positive_value = std::abs(value); T ipart = 0; std::modf(positive_value, &ipart); // If 'value' is exactly halfway between two integers if(std::abs(positive_value - (ipart + 0.5f)) < epsilon) { // If 'ipart' is even then return 'ipart' if(std::fmod(ipart, 2.f) < epsilon) { return cpp11::copysign(ipart, value); } // Else return the nearest even integer return cpp11::copysign(std::ceil(ipart + 0.5f), value); } // Otherwise use the usual round to closest return cpp11::copysign(cpp11::round(positive_value), value); } } // namespace cpp11 namespace cpp14 { /** make_unqiue is missing in CPP11. Reimplement it according to the standard * proposal. */ template struct _Unique_if { typedef std::unique_ptr _Single_object; }; template struct _Unique_if { typedef std::unique_ptr _Unknown_bound; }; template struct _Unique_if { typedef void _Known_bound; }; template typename _Unique_if::_Single_object make_unique(Args &&... args) { return std::unique_ptr(new T(std::forward(args)...)); } template typename _Unique_if::_Unknown_bound make_unique(size_t n) { typedef typename std::remove_extent::type U; return std::unique_ptr(new U[n]()); } template typename _Unique_if::_Known_bound make_unique(Args &&...) = delete; } // namespace cpp14 namespace traits { // *INDENT-OFF* // clang-format off template struct promote { }; template <> struct promote { using type = uint16_t; }; template <> struct promote { using type = int16_t; }; template <> struct promote { using type = uint32_t; }; template <> struct promote { using type = int32_t; }; template <> struct promote { using type = uint64_t; }; template <> struct promote { using type = int64_t; }; template <> struct promote { using type = float; }; #ifdef ARM_COMPUTE_ENABLE_FP16 template <> struct promote { using type = float16_t; }; #endif template using promote_t = typename promote::type; template using make_signed_conditional_t = typename std::conditional::value, std::make_signed, std::common_type>::type; // clang-format on // *INDENT-ON* } /** Look up the format corresponding to a channel. * * @param[in] channel Channel type. * * @return Format that contains the given channel. */ inline Format get_format_for_channel(Channel channel) { switch(channel) { case Channel::R: case Channel::G: case Channel::B: return Format::RGB888; default: throw std::runtime_error("Unsupported channel"); } } /** Return the format of a channel. * * @param[in] channel Channel type. * * @return Format of the given channel. */ inline Format get_channel_format(Channel channel) { switch(channel) { case Channel::R: case Channel::G: case Channel::B: return Format::U8; default: throw std::runtime_error("Unsupported channel"); } } /** Base case of foldl. * * @return value. */ template inline T foldl(F &&, const T &value) { return value; } /** Base case of foldl. * * @return func(value1, value2). */ template inline auto foldl(F &&func, T &&value1, U &&value2) -> decltype(func(value1, value2)) { return func(value1, value2); } /** Fold left. * * @param[in] func Binary function to be called. * @param[in] initial Initial value. * @param[in] value Argument passed to the function. * @param[in] values Remaining arguments. */ template inline I foldl(F &&func, I &&initial, T &&value, Vs &&... values) { return foldl(std::forward(func), func(std::forward(initial), std::forward(value)), std::forward(values)...); } /** Create a valid region based on tensor shape, border mode and border size * * @param[in] shape Shape used as size of the valid region. * @param[in] border_undefined (Optional) Boolean indicating if the border mode is undefined. * @param[in] border_size (Optional) Border size used to specify the region to exclude. * * @return A valid region starting at (0, 0, ...) with size of @p shape if @p border_undefined is false; otherwise * return A valid region starting at (@p border_size.left, @p border_size.top, ...) with reduced size of @p shape. */ inline ValidRegion shape_to_valid_region(TensorShape shape, bool border_undefined = false, BorderSize border_size = BorderSize(0)) { Coordinates anchor; anchor.set(std::max(0, static_cast(shape.num_dimensions()) - 1), 0); if(border_undefined) { ARM_COMPUTE_ERROR_ON(shape.num_dimensions() < 2); anchor.set(0, border_size.left); anchor.set(1, border_size.top); shape.set(0, shape.x() - border_size.left - border_size.right); shape.set(1, shape.y() - border_size.top - border_size.bottom); } return ValidRegion(std::move(anchor), std::move(shape)); } /** Create a valid region covering the tensor shape with UNDEFINED border mode and specified border size. * * @param[in] shape Shape used as size of the valid region. * @param[in] border_size Border size used to specify the region to exclude. * * @return A valid region starting at (@p border_size.left, @p border_size.top, ...) with reduced size of @p shape. */ inline ValidRegion shape_to_valid_region_undefined_border(TensorShape shape, BorderSize border_size) { ARM_COMPUTE_ERROR_ON(shape.num_dimensions() < 2); Coordinates anchor; anchor.set(std::max(0, shape.num_dimensions() - 1), 0); anchor.set(0, border_size.left); anchor.set(1, border_size.top); shape.set(0, shape.x() - border_size.left - border_size.right); shape.set(1, shape.y() - border_size.top - border_size.bottom); return ValidRegion(std::move(anchor), shape); } /** Write the value after casting the pointer according to @p data_type. * * @warning The type of the value must match the specified data type. * * @param[out] ptr Pointer to memory where the @p value will be written. * @param[in] value Value that will be written. * @param[in] data_type Data type that will be written. */ template void store_value_with_data_type(void *ptr, T value, DataType data_type) { switch(data_type) { case DataType::U8: *reinterpret_cast(ptr) = value; break; case DataType::S8: case DataType::QS8: *reinterpret_cast(ptr) = value; break; case DataType::U16: *reinterpret_cast(ptr) = value; break; case DataType::S16: *reinterpret_cast(ptr) = value; break; case DataType::U32: *reinterpret_cast(ptr) = value; break; case DataType::S32: *reinterpret_cast(ptr) = value; break; case DataType::U64: *reinterpret_cast(ptr) = value; break; case DataType::S64: *reinterpret_cast(ptr) = value; break; #if ARM_COMPUTE_ENABLE_FP16 case DataType::F16: *reinterpret_cast(ptr) = value; break; #endif /* ARM_COMPUTE_ENABLE_FP16 */ case DataType::F32: *reinterpret_cast(ptr) = value; break; case DataType::F64: *reinterpret_cast(ptr) = value; break; case DataType::SIZET: *reinterpret_cast(ptr) = value; break; default: ARM_COMPUTE_ERROR("NOT SUPPORTED!"); } } /** Saturate a value of type T against the numeric limits of type U. * * @param[in] val Value to be saturated. * * @return saturated value. */ template T saturate_cast(T val) { if(val > static_cast(std::numeric_limits::max())) { val = static_cast(std::numeric_limits::max()); } if(val < static_cast(std::numeric_limits::lowest())) { val = static_cast(std::numeric_limits::lowest()); } return val; } /** Find the signed promoted common type. */ template struct common_promoted_signed_type { using common_type = typename std::common_type::type; using promoted_type = traits::promote_t; using intermediate_type = typename traits::make_signed_conditional_t::type; }; /** Convert a linear index into n-dimensional coordinates. * * @param[in] shape Shape of the n-dimensional tensor. * @param[in] index Linear index specifying the i-th element. * * @return n-dimensional coordinates. */ inline Coordinates index2coord(const TensorShape &shape, int index) { int num_elements = shape.total_size(); ARM_COMPUTE_ERROR_ON_MSG(index < 0 || index >= num_elements, "Index has to be in [0, num_elements]"); ARM_COMPUTE_ERROR_ON_MSG(num_elements == 0, "Cannot create coordinate from empty shape"); Coordinates coord{ 0 }; for(int d = shape.num_dimensions() - 1; d >= 0; --d) { num_elements /= shape[d]; coord.set(d, index / num_elements); index %= num_elements; } return coord; } /** Linearise the given coordinate. * * Transforms the given coordinate into a linear offset in terms of * elements. * * @param[in] shape Shape of the n-dimensional tensor. * @param[in] coord The to be converted coordinate. * * @return Linear offset to the element. */ inline int coord2index(const TensorShape &shape, const Coordinates &coord) { ARM_COMPUTE_ERROR_ON_MSG(shape.total_size() == 0, "Cannot get index from empty shape"); ARM_COMPUTE_ERROR_ON_MSG(coord.num_dimensions() == 0, "Cannot get index of empty coordinate"); int index = 0; int dim_size = 1; for(unsigned int i = 0; i < coord.num_dimensions(); ++i) { index += coord[i] * dim_size; dim_size *= shape[i]; } return index; } /** Check if Coordinates dimensionality can match the respective shape one. * * @param coords Coordinates * @param shape Shape to match dimensionality * * @return True if Coordinates can match the dimensionality of the shape else false. */ inline bool match_shape(Coordinates &coords, const TensorShape &shape) { auto check_nz = [](unsigned int i) { return i != 0; }; unsigned int coords_dims = coords.num_dimensions(); unsigned int shape_dims = shape.num_dimensions(); // Increase coordinates scenario if(coords_dims < shape_dims) { coords.set_num_dimensions(shape_dims); return true; } // Decrease coordinates scenario if(coords_dims > shape_dims && !std::any_of(coords.begin() + shape_dims, coords.end(), check_nz)) { coords.set_num_dimensions(shape_dims); return true; } return (coords_dims == shape_dims); } /** Check if a coordinate is within a valid region */ inline bool is_in_valid_region(const ValidRegion &valid_region, const Coordinates &coord) { Coordinates coords(coord); ARM_COMPUTE_ERROR_ON_MSG(!match_shape(coords, valid_region.shape), "Shapes of valid region and coordinates do not agree"); for(int d = 0; static_cast(d) < coords.num_dimensions(); ++d) { if(coords[d] < valid_region.start(d) || coords[d] >= valid_region.end(d)) { return false; } } return true; } } // namespace test } // namespace arm_compute #endif