Program Listing for File operations.hpp#

Return to documentation for file (librapid/include/librapid/array/operations.hpp)

#ifndef LIBRAPID_ARRAY_OPERATIONS_HPP
#define LIBRAPID_ARRAY_OPERATIONS_HPP

#define LIBRAPID_BINARY_FUNCTOR(NAME_, OP_)                                                        \
    struct NAME_ {                                                                                 \
        template<typename T, typename V>                                                           \
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator()(const T &lhs,                    \
                                                                  const V &rhs) const {            \
            return lhs OP_ rhs;                                                                    \
        }                                                                                          \
                                                                                                   \
        template<typename Packet>                                                                  \
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto packet(const Packet &lhs,                   \
                                                              const Packet &rhs) const {           \
            return lhs OP_ rhs;                                                                    \
        }                                                                                          \
    }

#define LIBRAPID_BINARY_COMPARISON_FUNCTOR(NAME_, OP_)                                             \
    struct NAME_ {                                                                                 \
        template<typename T, typename V>                                                           \
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator()(const T &lhs,                    \
                                                                  const V &rhs) const {            \
            return (typename std::common_type_t<T, V>)(lhs OP_ rhs);                               \
        }                                                                                          \
                                                                                                   \
        template<typename Packet>                                                                  \
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto packet(const Packet &lhs,                   \
                                                              const Packet &rhs) const {           \
            auto mask = lhs OP_ rhs;                                                               \
            Packet res(1);                                                                         \
            res.setZero(!mask);                                                                    \
            return res;                                                                            \
        }                                                                                          \
    }

#define LIBRAPID_UNARY_KERNEL_GETTER                                                               \
    template<typename... Args>                                                                     \
    static constexpr const char *getKernelName(std::tuple<Args...> args) {                         \
        static_assert(sizeof...(Args) == 1, "Invalid number of arguments for unary operation");    \
        return kernelName;                                                                         \
    }

#define LIBRAPID_BINARY_KERNEL_GETTER                                                              \
    template<typename T1, typename T2>                                                             \
    static constexpr const char *getKernelNameImpl(std::tuple<T1, T2> args) {                      \
        if constexpr (TypeInfo<std::decay_t<T1>>::type != detail::LibRapidType::Scalar &&          \
                      TypeInfo<std::decay_t<T2>>::type != detail::LibRapidType::Scalar) {          \
            return kernelName;                                                                     \
        } else if constexpr (TypeInfo<std::decay_t<T1>>::type == detail::LibRapidType::Scalar) {   \
            return kernelNameScalarLhs;                                                            \
        } else if constexpr (TypeInfo<std::decay_t<T2>>::type == detail::LibRapidType::Scalar) {   \
            return kernelNameScalarRhs;                                                            \
        } else {                                                                                   \
            return kernelName;                                                                     \
        }                                                                                          \
    }                                                                                              \
                                                                                                   \
    template<typename... Args>                                                                     \
    static constexpr const char *getKernelName(std::tuple<Args...> args) {                         \
        static_assert(sizeof...(Args) == 2, "Invalid number of arguments for binary operation");   \
        return getKernelNameImpl(args);                                                            \
    }

#define LIBRAPID_UNARY_SHAPE_EXTRACTOR                                                             \
    template<typename... Args>                                                                     \
    LIBRAPID_NODISCARD static LIBRAPID_ALWAYS_INLINE auto getShape(                                \
      const std::tuple<Args...> &args) {                                                           \
        static_assert(sizeof...(Args) == 1, "Invalid number of arguments for unary operation");    \
        return std::get<0>(args).shape();                                                          \
    }

#define LIBRAPID_BINARY_SHAPE_EXTRACTOR                                                            \
    template<typename First, typename Second>                                                      \
    LIBRAPID_NODISCARD static LIBRAPID_ALWAYS_INLINE auto getShapeImpl(                            \
      const std::tuple<First, Second> &tup) {                                                      \
        if constexpr (TypeInfo<std::decay_t<First>>::type != detail::LibRapidType::Scalar &&       \
                      TypeInfo<std::decay_t<Second>>::type != detail::LibRapidType::Scalar) {      \
            LIBRAPID_ASSERT(std::get<0>(tup).shape() == std::get<1>(tup).shape(),                  \
                            "Shapes must match for binary operations");                            \
            return std::get<0>(tup).shape();                                                       \
        } else if constexpr (TypeInfo<std::decay_t<First>>::type ==                                \
                             detail::LibRapidType::Scalar) {                                       \
            return std::get<1>(tup).shape();                                                       \
        } else {                                                                                   \
            return std::get<0>(tup).shape();                                                       \
        }                                                                                          \
    }                                                                                              \
                                                                                                   \
    template<typename... Args>                                                                     \
    LIBRAPID_NODISCARD static LIBRAPID_ALWAYS_INLINE auto getShape(                                \
      const std::tuple<Args...> &args) {                                                           \
        static_assert(sizeof...(Args) == 2, "Invalid number of arguments for binary operation");   \
        return getShapeImpl(args);                                                                 \
    }

#define LIBRAPID_UNARY_FUNCTOR(NAME, OP)                                                           \
    struct NAME {                                                                                  \
        template<typename T>                                                                       \
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator()(const T &arg) const {            \
            return (T)(OP(arg));                                                                   \
        }                                                                                          \
                                                                                                   \
        template<typename Packet>                                                                  \
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto packet(const Packet &arg) const {           \
            return OP(arg);                                                                        \
        }                                                                                          \
    };

namespace librapid {
    namespace detail {
        template<typename desc, typename Functor, typename... Args>
        auto makeFunction(Args &&...args) {
            using OperationType = Function<desc, Functor, Args...>;
            return OperationType(Functor(), std::forward<Args>(args)...);
        }

        LIBRAPID_BINARY_FUNCTOR(Plus, +);                            // a + b
        LIBRAPID_BINARY_FUNCTOR(Minus, -);                           // a - b
        LIBRAPID_BINARY_FUNCTOR(Multiply, *);                        // a * b
        LIBRAPID_BINARY_FUNCTOR(Divide, /);                          // a / b

        LIBRAPID_BINARY_COMPARISON_FUNCTOR(LessThan, <);             // a < b
        LIBRAPID_BINARY_COMPARISON_FUNCTOR(GreaterThan, >);          // a > b
        LIBRAPID_BINARY_COMPARISON_FUNCTOR(LessThanEqual, <=);       // a <= b
        LIBRAPID_BINARY_COMPARISON_FUNCTOR(GreaterThanEqual, >=);    // a >= b
        LIBRAPID_BINARY_COMPARISON_FUNCTOR(ElementWiseEqual, ==);    // a == b
        LIBRAPID_BINARY_COMPARISON_FUNCTOR(ElementWiseNotEqual, !=); // a != b

        LIBRAPID_UNARY_FUNCTOR(Neg, -);

        LIBRAPID_UNARY_FUNCTOR(Sin, ::librapid::sin);     // sin(a)
        LIBRAPID_UNARY_FUNCTOR(Cos, ::librapid::cos);     // cos(a)
        LIBRAPID_UNARY_FUNCTOR(Tan, ::librapid::tan);     // tan(a)
        LIBRAPID_UNARY_FUNCTOR(Asin, ::librapid::asin);   // asin(a)
        LIBRAPID_UNARY_FUNCTOR(Acos, ::librapid::acos);   // acos(a)
        LIBRAPID_UNARY_FUNCTOR(Atan, ::librapid::atan);   // atan(a)
        LIBRAPID_UNARY_FUNCTOR(Sinh, ::librapid::sinh);   // sinh(a)
        LIBRAPID_UNARY_FUNCTOR(Cosh, ::librapid::cosh);   // cosh(a)
        LIBRAPID_UNARY_FUNCTOR(Tanh, ::librapid::tanh);   // tanh(a)

        LIBRAPID_UNARY_FUNCTOR(Exp, ::librapid::exp);     // exp(a)
        LIBRAPID_UNARY_FUNCTOR(Log, ::librapid::log);     // log(a)
        LIBRAPID_UNARY_FUNCTOR(Log2, ::librapid::log2);   // log2(a)
        LIBRAPID_UNARY_FUNCTOR(Log10, ::librapid::log10); // log10(a)
        LIBRAPID_UNARY_FUNCTOR(Sqrt, ::librapid::sqrt);   // sqrt(a)
        LIBRAPID_UNARY_FUNCTOR(Cbrt, ::librapid::cbrt);   // cbrt(a)
        LIBRAPID_UNARY_FUNCTOR(Abs, ::librapid::abs);     // abs(a)
        LIBRAPID_UNARY_FUNCTOR(Floor, ::librapid::floor); // floor(a)
        LIBRAPID_UNARY_FUNCTOR(Ceil, ::librapid::ceil);   // ceil(a)

    }                                                     // namespace detail

    namespace typetraits {
        template<typename Descriptor1, typename Descriptor2>
        struct DescriptorMerger {
            using Type = ::librapid::detail::descriptor::Combined;
        };

        template<typename Descriptor1>
        struct DescriptorMerger<Descriptor1, Descriptor1> {
            using Type = Descriptor1;
        };

        template<typename T>
        struct DescriptorExtractor {
            using Type = ::librapid::detail::descriptor::Trivial;
        };

        template<typename ShapeType, typename StorageType>
        struct DescriptorExtractor<array::ArrayContainer<ShapeType, StorageType>> {
            using Type = ::librapid::detail::descriptor::Trivial;
        };

        template<typename T>
        struct DescriptorExtractor<array::ArrayView<T>> {
            using Type = ::librapid::detail::descriptor::Trivial;
        };

        template<typename Descriptor, typename Functor, typename... Args>
        struct DescriptorExtractor<::librapid::detail::Function<Descriptor, Functor, Args...>> {
            using Type = Descriptor;
        };

        template<typename First, typename... Rest>
        struct DescriptorType;

        namespace impl {
            template<typename... Rest>
            constexpr auto descriptorExtractor() {
                if constexpr (sizeof...(Rest) > 0) {
                    using ReturnType = typename DescriptorType<Rest...>::Type;
                    return ReturnType {};
                } else {
                    using ReturnType = ::librapid::detail::descriptor::Trivial;
                    return ReturnType {};
                }
            }
        } // namespace impl

        template<typename First, typename... Rest>
        struct DescriptorType {
            using FirstType       = std::decay_t<First>;
            using FirstDescriptor = typename DescriptorExtractor<FirstType>::Type;
            using RestDescriptor  = decltype(impl::descriptorExtractor<Rest...>());

            using Type = typename DescriptorMerger<FirstDescriptor, RestDescriptor>::Type;
        };

        template<typename... Args>
        using DescriptorType_t = typename DescriptorType<Args...>::Type;

        template<>
        struct TypeInfo<::librapid::detail::Plus> {
            static constexpr const char *name                = "plus";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "addArrays";
            static constexpr const char *kernelNameScalarRhs = "addArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "addArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Minus> {
            static constexpr const char *name                = "minus";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "subArrays";
            static constexpr const char *kernelNameScalarRhs = "subArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "subArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Multiply> {
            static constexpr const char *name                = "multiply";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "mulArrays";
            static constexpr const char *kernelNameScalarRhs = "mulArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "mulArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Divide> {
            static constexpr const char *name                = "divide";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "divArrays";
            static constexpr const char *kernelNameScalarRhs = "divArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "divArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::LessThan> {
            static constexpr const char *name                = "less than";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "lessThanArrays";
            static constexpr const char *kernelNameScalarRhs = "lessThanArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "lessThanArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::GreaterThan> {
            static constexpr const char *name                = "greater than";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "greaterThanArrays";
            static constexpr const char *kernelNameScalarRhs = "greaterThanArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "greaterThanArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::LessThanEqual> {
            static constexpr const char *name                = "less than or equal";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "lessThanEqualArrays";
            static constexpr const char *kernelNameScalarRhs = "lessThanEqualArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "lessThanEqualArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::GreaterThanEqual> {
            static constexpr const char *name                = "greater than or equal";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "greaterThanEqualArrays";
            static constexpr const char *kernelNameScalarRhs = "greaterThanEqualArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "greaterThanEqualArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::ElementWiseEqual> {
            static constexpr const char *name                = "element wise equal";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "elementWiseEqualArrays";
            static constexpr const char *kernelNameScalarRhs = "elementWiseEqualArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "elementWiseEqualArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::ElementWiseNotEqual> {
            static constexpr const char *name                = "element wise not equal";
            static constexpr const char *filename            = "arithmetic";
            static constexpr const char *kernelName          = "elementWiseNotEqualArrays";
            static constexpr const char *kernelNameScalarRhs = "elementWiseNotEqualArraysScalarRhs";
            static constexpr const char *kernelNameScalarLhs = "elementWiseNotEqualArraysScalarLhs";
            LIBRAPID_BINARY_KERNEL_GETTER
            LIBRAPID_BINARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Neg> {
            static constexpr const char *name       = "negate";
            static constexpr const char *filename   = "negate";
            static constexpr const char *kernelName = "negateArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Sin> {
            static constexpr const char *name       = "sin";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "sinArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Cos> {
            static constexpr const char *name       = "cos";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "cosArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Tan> {
            static constexpr const char *name       = "tan";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "tanArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Asin> {
            static constexpr const char *name       = "arcsin";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "asinArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Acos> {
            static constexpr const char *name       = "arcos";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "acosArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Atan> {
            static constexpr const char *name       = "arctan";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "atanArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Sinh> {
            static constexpr const char *name       = "hyperbolic sine";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "sinhArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Cosh> {
            static constexpr const char *name       = "hyperbolic cosine";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "coshArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Tanh> {
            static constexpr const char *name       = "hyperbolic tangent";
            static constexpr const char *filename   = "trigonometry";
            static constexpr const char *kernelName = "tanhArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Exp> {
            static constexpr const char *name       = "exponent";
            static constexpr const char *filename   = "expLogPow";
            static constexpr const char *kernelName = "expArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Log> {
            static constexpr const char *name       = "logarithm";
            static constexpr const char *filename   = "expLogPow";
            static constexpr const char *kernelName = "logArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Log2> {
            static constexpr const char *name       = "logarithm base 2";
            static constexpr const char *filename   = "expLogPow";
            static constexpr const char *kernelName = "log2Arrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Log10> {
            static constexpr const char *name       = "logarithm base 10";
            static constexpr const char *filename   = "expLogPow";
            static constexpr const char *kernelName = "log10Arrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Sqrt> {
            static constexpr const char *name       = "square root";
            static constexpr const char *filename   = "expLogPow";
            static constexpr const char *kernelName = "sqrtArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Cbrt> {
            static constexpr const char *name       = "cube root";
            static constexpr const char *filename   = "expLogPow";
            static constexpr const char *kernelName = "cbrtArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Abs> {
            static constexpr const char *name       = "absolute value";
            static constexpr const char *filename   = "abs";
            static constexpr const char *kernelName = "absArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Floor> {
            static constexpr const char *name       = "floor";
            static constexpr const char *filename   = "floorCeilRound";
            static constexpr const char *kernelName = "floorArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };

        template<>
        struct TypeInfo<::librapid::detail::Ceil> {
            static constexpr const char *name       = "ceiling";
            static constexpr const char *filename   = "floorCeilRound";
            static constexpr const char *kernelName = "ceilArrays";
            LIBRAPID_UNARY_KERNEL_GETTER
            LIBRAPID_UNARY_SHAPE_EXTRACTOR
        };
    } // namespace typetraits

    namespace detail {
        template<typename VAL>
        constexpr bool isArrayOp() {
            return (typetraits::IsArrayContainer<std::decay_t<VAL>>::value ||
                    typetraits::IsLibRapidType<std::decay_t<VAL>>::value);
        }

        template<typename LHS, typename RHS>
        constexpr bool isArrayOpArray() {
            return (typetraits::TypeInfo<std::decay_t<LHS>>::type != LibRapidType::Scalar) &&
                   (typetraits::TypeInfo<std::decay_t<RHS>>::type != LibRapidType::Scalar) &&
                   typetraits::IsLibRapidType<std::decay_t<LHS>>::value &&
                   typetraits::IsLibRapidType<std::decay_t<RHS>>::value;
        }

        template<typename LHS, typename RHS>
        constexpr bool isArrayOpWithScalar() {
            return (typetraits::IsLibRapidType<std::decay_t<LHS>>::value &&
                    typetraits::TypeInfo<std::decay_t<RHS>>::type == LibRapidType::Scalar) ||
                   (typetraits::TypeInfo<std::decay_t<LHS>>::type == LibRapidType::Scalar &&
                    typetraits::IsLibRapidType<std::decay_t<RHS>>::value);
        }
    } // namespace detail

    namespace array {
#define IS_ARRAY_OP             detail::isArrayOp<VAL>()
#define IS_ARRAY_OP_ARRAY       detail::isArrayOpArray<LHS, RHS>()
#define IS_ARRAY_OP_WITH_SCALAR detail::isArrayOpWithScalar<LHS, RHS>()

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator+(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::Plus, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::Plus>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator+(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::Plus, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::Plus>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator-(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::Minus, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::Minus>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator-(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::Minus, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::Minus>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator*(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::Multiply, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::Multiply>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator*(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::Multiply, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::Multiply>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator/(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::Divide, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::Divide>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator/(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::Divide, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::Divide>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator<(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::LessThan, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::LessThan>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator<(LHS &&lhs, RHS &&rhs) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<LHS, RHS>, detail::LessThan, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>, detail::LessThan>(
              std::forward<LHS>(lhs), std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator>(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::GreaterThan, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::GreaterThan>(std::forward<LHS>(lhs),
                                                             std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator>(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::GreaterThan, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::GreaterThan>(std::forward<LHS>(lhs),
                                                             std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator<=(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::LessThanEqual, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::LessThanEqual>(std::forward<LHS>(lhs),
                                                               std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator<=(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::LessThanEqual, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::LessThanEqual>(std::forward<LHS>(lhs),
                                                               std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator>=(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::GreaterThanEqual, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::GreaterThanEqual>(std::forward<LHS>(lhs),
                                                                  std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator>=(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::GreaterThanEqual, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::GreaterThanEqual>(std::forward<LHS>(lhs),
                                                                  std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator==(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::ElementWiseEqual, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::ElementWiseEqual>(std::forward<LHS>(lhs),
                                                                  std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator==(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::ElementWiseEqual, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::ElementWiseEqual>(std::forward<LHS>(lhs),
                                                                  std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_ARRAY, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator!=(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::ElementWiseNotEqual, LHS, RHS> {
            LIBRAPID_ASSERT(lhs.shape().operator==(rhs.shape()), "Shapes must be equal");
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::ElementWiseNotEqual>(std::forward<LHS>(lhs),
                                                                     std::forward<RHS>(rhs));
        }

        template<class LHS, class RHS, typename std::enable_if_t<IS_ARRAY_OP_WITH_SCALAR, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator!=(LHS &&lhs, RHS &&rhs)
          LIBRAPID_RELEASE_NOEXCEPT->detail::Function<typetraits::DescriptorType_t<LHS, RHS>,
                                                      detail::ElementWiseNotEqual, LHS, RHS> {
            return detail::makeFunction<typetraits::DescriptorType_t<LHS, RHS>,
                                        detail::ElementWiseNotEqual>(std::forward<LHS>(lhs),
                                                                     std::forward<RHS>(rhs));
        }

        template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto
        operator-(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
          ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Neg, VAL> {
            return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Neg>(
              std::forward<VAL>(val));
        }
    } // namespace array

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto sin(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Sin, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Sin>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto cos(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Cos, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Cos>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto tan(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Tan, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Tan>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto asin(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Asin, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Asin>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto acos(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Acos, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Acos>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto atan(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Atan, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Atan>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto sinh(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Sinh, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Sinh>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto cosh(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Cosh, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Cosh>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto tanh(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Tanh, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Tanh>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto exp(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Exp, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Exp>(
          std::forward<VAL>(val));
    }

    // \brief Compute the natural logarithm of each element in the array
    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto log(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Log, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Log>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto log10(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Log10, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Log10>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto log2(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Log2, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Log2>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto sqrt(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Sqrt, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Sqrt>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto cbrt(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Cbrt, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Cbrt>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto abs(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Abs, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Abs>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto floor(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Floor, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Floor>(
          std::forward<VAL>(val));
    }

    template<class VAL, typename std::enable_if_t<IS_ARRAY_OP, int> = 0>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto ceil(VAL &&val) LIBRAPID_RELEASE_NOEXCEPT
      ->detail::Function<typetraits::DescriptorType_t<VAL>, detail::Ceil, VAL> {
        return detail::makeFunction<typetraits::DescriptorType_t<VAL>, detail::Ceil>(
          std::forward<VAL>(val));
    }
} // namespace librapid

#endif // LIBRAPID_ARRAY_OPERATIONS_HPP