#pragma once #include #include #include #include #include #include #include #include #include #include #include // Suppress xtensor warnings if SUPPRESS_XTENSOR_WARNINGS is set #ifdef SUPPRESS_XTENSOR_WARNINGS #ifdef _MSC_VER #pragma warning(push, 0) #else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wall" #pragma GCC diagnostic ignored "-Wextra" #pragma GCC system_header #endif #endif #include #include #include #include #include #include #ifdef SUPPRESS_XTENSOR_WARNINGS #ifdef _MSC_VER #pragma warning(pop) #else #pragma GCC diagnostic pop #endif #endif namespace pkpy { // Type aliases using int8 = int8_t; using int16 = int16_t; using int32 = int32_t; using int64 = int64_t; using uint8 = uint8_t; using uint16 = uint16_t; using uint32 = uint32_t; using uint64 = uint64_t; using int_ = int64; using float32 = float; using float64 = double; using float_ = float64; using bool_ = bool; using complex64 = std::complex; using complex128 = std::complex; using complex_ = complex128; using string = std::string; template struct dtype_traits { constexpr const static char* name = "unknown"; }; #define REGISTER_DTYPE(Type, Name) \ template <> \ struct dtype_traits { \ static constexpr const char* name = Name; \ }; REGISTER_DTYPE(int8_t, "int8"); REGISTER_DTYPE(int16_t, "int16"); REGISTER_DTYPE(int32_t, "int32"); REGISTER_DTYPE(int64_t, "int64"); REGISTER_DTYPE(uint8_t, "uint8"); REGISTER_DTYPE(uint16_t, "uint16"); REGISTER_DTYPE(uint32_t, "uint32"); REGISTER_DTYPE(uint64_t, "uint64"); REGISTER_DTYPE(float, "float32"); REGISTER_DTYPE(float_, "float64"); REGISTER_DTYPE(bool_, "bool"); REGISTER_DTYPE(std::complex, "complex64"); REGISTER_DTYPE(std::complex, "complex128"); using _Dtype = std::string; using _ShapeLike = std::vector; namespace numpy { template class ndarray; template constexpr inline auto is_ndarray_v = false; template constexpr inline auto is_ndarray_v> = true; template class ndarray { public: // Constructor for xtensor xarray ndarray() = default; ndarray(const T scalar) : _array(scalar) {} ndarray(const xt::xarray& arr) : _array(arr) {} // Constructor for mutli-dimensional array ndarray(std::initializer_list init_list) : _array(init_list) {} ndarray(std::initializer_list> init_list) : _array(init_list) {} ndarray(std::initializer_list>> init_list) : _array(init_list) {} ndarray(std::initializer_list>>> init_list) : _array(init_list) {} ndarray(std::initializer_list< std::initializer_list>>>> init_list) : _array(init_list) {} // Accessor function for _array const xt::xarray& get_array() const { return _array; } // Properties _Dtype dtype() const { return dtype_traits::name; } int ndim() const { return static_cast(_array.dimension()); } int size() const { return static_cast(_array.size()); } _ShapeLike shape() const { return _ShapeLike(_array.shape().begin(), _array.shape().end()); } // Dunder Methods template auto operator== (const ndarray& other) const { return ndarray(xt::equal(_array, other.get_array())); } template auto operator!= (const ndarray& other) const { return ndarray(xt::not_equal(_array, other.get_array())); } template auto operator+ (const ndarray& other) const { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) + xt::cast(other.get_array()); return ndarray(result); } template >> auto operator+ (const U& other) const { return binary_operator_add_impl(other); } template auto binary_operator_add_impl(const U& other) const { if constexpr(std::is_same_v) { xt::xarray result = xt::cast(_array) + other; return ndarray(result); } else { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) + other; return ndarray(result); } } template auto operator- (const ndarray& other) const { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) - xt::cast(other.get_array()); return ndarray(result); } template >> auto operator- (const U& other) const { return binary_operator_sub_impl(other); } template auto binary_operator_sub_impl(const U& other) const { if constexpr(std::is_same_v) { xt::xarray result = xt::cast(_array) - other; return ndarray(result); } else { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) - other; return ndarray(result); } } template auto operator* (const ndarray& other) const { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) * xt::cast(other.get_array()); return ndarray(result); } template >> auto operator* (const U& other) const { return binary_operator_mul_impl(other); } template auto binary_operator_mul_impl(const U& other) const { if constexpr(std::is_same_v) { xt::xarray result = xt::cast(_array) * other; return ndarray(result); } else { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) * other; return ndarray(result); } } template auto operator/ (const ndarray& other) const { using result_type = std::conditional_t || std::is_same_v, float64, std::common_type_t>; xt::xarray result = xt::cast(_array) / xt::cast(other.get_array()); return ndarray(result); } template >> auto operator/ (const U& other) const { return binary_operator_truediv_impl(other); } template auto binary_operator_truediv_impl(const U& other) const { xt::xarray result = xt::cast(_array) / static_cast(other); return ndarray(result); } template auto pow(const ndarray& other) const { using result_type = std::common_type_t; xt::xarray result = xt::pow(xt::cast(_array), xt::cast(other.get_array())); return ndarray(result); } template >> auto pow(const U& other) const { return pow_impl(other); } template auto pow_impl(const U& other) const { xt::xarray result = xt::pow(xt::cast(_array), other); return ndarray(result); } template ndarray operator& (const ndarray& other) const { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) & xt::cast(other.get_array()); return ndarray(result); } template >> ndarray operator& (const U& other) const { xt::xarray result = _array & static_cast(other); return ndarray(result); } template ndarray operator| (const ndarray& other) const { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) | xt::cast(other.get_array()); return ndarray(result); } template >> ndarray operator| (const U& other) const { xt::xarray result = _array | static_cast(other); return ndarray(result); } template ndarray operator^ (const ndarray& other) const { using result_type = std::common_type_t; xt::xarray result = xt::cast(_array) ^ xt::cast(other.get_array()); return ndarray(result); } template >> ndarray operator^ (const U& other) const { xt::xarray result = _array ^ static_cast(other); return ndarray(result); } ndarray operator~() const { return ndarray(~(_array)); } ndarray operator!() const { return ndarray(!(_array)); } T operator() (int index) const { return _array(index); } ndarray operator[] (int index) const { return ndarray(xt::view(_array, index, xt::all())); } ndarray operator[] (const std::vector& indices) const { return ndarray(xt::view(_array, xt::keep(indices))); } ndarray operator[] (const std::tuple& slice) const { return ndarray(xt::view(_array, xt::range(std::get<0>(slice), std::get<1>(slice), std::get<2>(slice)))); } template T operator() (Args... args) const { return _array(args...); } void set_item(int index, const ndarray& value) { xt::view(_array, index, xt::all()) = value.get_array(); } void set_item(int i1, int i2, const ndarray& value) { xt::view(_array, i1, i2, xt::all()) = value.get_array(); } void set_item(int i1, int i2, int i3, const ndarray& value) { xt::view(_array, i1, i2, i3, xt::all()) = value.get_array(); } void set_item(int i1, int i2, int i3, int i4, const ndarray& value) { xt::view(_array, i1, i2, i3, i4, xt::all()) = value.get_array(); } void set_item(int i1, int i2, int i3, int i4, int i5, const ndarray& value) { xt::view(_array, i1, i2, i3, i4, i5, xt::all()) = value.get_array(); } void set_item(const std::vector& indices, const ndarray& value) { xt::view(_array, xt::keep(indices)) = value.get_array(); } void set_item(const std::tuple& slice, const ndarray& value) { xt::view(_array, xt::range(std::get<0>(slice), std::get<1>(slice), std::get<2>(slice))) = value.get_array(); } void set_item(int i1, int i2, T value) { xt::view(_array, i1, i2) = value; } void set_item(int i1, int i2, int i3, T value) { xt::view(_array, i1, i2, i3) = value; } void set_item(int i1, int i2, int i3, int i4, T value) { xt::view(_array, i1, i2, i3, i4) = value; } void set_item(int i1, int i2, int i3, int i4, int i5, T value) { xt::view(_array, i1, i2, i3, i4, i5) = value; } // Boolean Functions bool all() const { return xt::all(_array); } bool any() const { return xt::any(_array); } // Aggregate Functions T sum() const { return (xt::sum(_array))[0]; } ndarray sum(int axis) const { xt::xarray result = xt::sum(_array, {axis}); return ndarray(result); } ndarray sum(const _ShapeLike& axis) const { xt::xarray result = xt::sum(_array, axis); return ndarray(result); } T prod() const { return (xt::prod(_array))[0]; } ndarray prod(int axis) const { xt::xarray result = xt::prod(_array, {axis}); return ndarray(result); } ndarray prod(const _ShapeLike& axes) const { xt::xarray result = xt::prod(_array, axes); return ndarray(result); } T min() const { return (xt::amin(_array))[0]; } ndarray min(int axis) const { xt::xarray result = xt::amin(_array, {axis}); return ndarray(result); } ndarray min(const _ShapeLike& axes) const { xt::xarray result = xt::amin(_array, axes); return ndarray(result); } T max() const { return (xt::amax(_array))[0]; } ndarray max(int axis) const { xt::xarray result = xt::amax(_array, {axis}); return ndarray(result); } ndarray max(const _ShapeLike& axes) const { xt::xarray result = xt::amax(_array, axes); return ndarray(result); } pkpy::float64 mean() const { return (xt::mean(_array))[0]; } ndarray mean(int axis) const { return ndarray(xt::mean(_array, {axis})); } ndarray mean(const _ShapeLike& axes) const { return ndarray(xt::mean(_array, axes)); } pkpy::float64 std() const { return (xt::stddev(_array))[0]; } ndarray std(int axis) const { return ndarray(xt::stddev(_array, {axis})); } ndarray std(const _ShapeLike& axes) const { return ndarray(xt::stddev(_array, axes)); } pkpy::float64 var() const { return (xt::variance(_array))[0]; } ndarray var(int axis) const { return ndarray(xt::variance(_array, {axis})); } ndarray var(const _ShapeLike& axes) const { return ndarray(xt::variance(_array, axes)); } // Searching and Sorting Functions pkpy::int64 argmin() const { return (xt::argmin(_array))[0]; } ndarray argmin(int axis) const { xt::xarray result = xt::argmin(_array, {axis}); return ndarray(result); } pkpy::int64 argmax() const { return (xt::argmax(_array))[0]; } ndarray argmax(int axis) const { xt::xarray result = xt::argmax(_array, {axis}); return ndarray(result); } ndarray argsort() const { return ndarray(xt::argsort(_array)); } ndarray argsort(int axis) const { xt::xarray result = xt::argsort(_array, {axis}); return ndarray(result); } ndarray sort() const { return ndarray(xt::sort(_array)); } ndarray sort(int axis) const { xt::xarray result = xt::sort(_array, {axis}); return ndarray(result); } // Shape Manipulation Functions ndarray reshape(const _ShapeLike& shape) const { xt::xarray dummy = _array; dummy.reshape(shape); return ndarray(dummy); } // Does not preserve elements if expected size is not equal to the current size. // https://github.com/xtensor-stack/xtensor/issues/1445 ndarray resize(const _ShapeLike& shape) const { xt::xarray dummy = _array; dummy.resize(shape); return ndarray(dummy); } ndarray squeeze() const { return ndarray(xt::squeeze(_array)); } ndarray squeeze(int axis) const { xt::xarray result = xt::squeeze(_array, {axis}); return ndarray(result); } ndarray transpose() const { return ndarray(xt::transpose(_array)); } ndarray transpose(const _ShapeLike& permutation) const { return ndarray(xt::transpose(_array, permutation)); } template ndarray transpose(Args... args) const { xt::xarray result = xt::transpose(_array, {args...}); return ndarray(result); } ndarray repeat(int repeats, int axis) const { return ndarray(xt::repeat(_array, repeats, axis)); } ndarray repeat(const std::vector& repeats, int axis) const { return ndarray(xt::repeat(_array, repeats, axis)); } ndarray flatten() const { return ndarray(xt::flatten(_array)); } // Miscellaneous Functions ndarray round() const { return ndarray(xt::round(_array)); } template ndarray astype() const { xt::xarray result = xt::cast(_array); return ndarray(result); } ndarray copy() const { ndarray result = *this; return result; } std::vector to_list() const { std::vector vec; for(auto &it : _array) { vec.push_back(it); } return vec; } private: xt::xarray _array; }; class random { public: random() { auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); xt::random::seed(static_cast(seed)); } template static T rand() { random random_instance; return (xt::random::rand(std::vector{1}))[0]; } template static ndarray rand(const _ShapeLike& shape) { random random_instance; return ndarray(xt::random::rand(shape)); } template static T randn() { random random_instance; return (xt::random::randn(std::vector{1}))[0]; } template static ndarray randn(const _ShapeLike& shape) { random random_instance; return ndarray(xt::random::randn(shape)); } template static int randint(T low, T high) { random random_instance; return (xt::random::randint(std::vector{1}, low, high))[0]; } template static ndarray randint(T low, T high, const _ShapeLike& shape) { random random_instance; return ndarray(xt::random::randint(shape, low, high)); } template static ndarray uniform(T low, T high, const _ShapeLike& shape) { random random_instance; return ndarray(xt::random::rand(shape, low, high)); } }; template xt::xarray> matrix_mul(const xt::xarray& a, const xt::xarray& b) { using result_type = std::common_type_t; using Mat = xt::xarray; bool first_is_1d = false; bool second_is_1d = false; xt::xarray a_copy = a; xt::xarray b_copy = b; if (a.dimension() == 1) { first_is_1d = true; a_copy = xt::reshape_view(a_copy, {1, 3}); } if(b_copy.dimension() == 1) { second_is_1d = true; b_copy = xt::reshape_view(b_copy, {3, 1}); } if (a_copy.dimension() == 2 && b_copy.dimension() == 2) { int m = static_cast(a_copy.shape()[0]); int n = static_cast(a_copy.shape()[1]); int p = static_cast(b_copy.shape()[1]); Mat result = xt::zeros({m, p}); for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { for (int k = 0; k < n; k++) { result(i, j) = result(i, j) + a_copy(i, k) * b_copy(k, j); } } } if (first_is_1d) { result = xt::squeeze(result, std::vector{result.dimension()-2}); } if (second_is_1d) { result = xt::squeeze(result, std::vector{result.dimension()-1}); } return result; } else { if (a_copy.dimension() == b_copy.dimension()) { assert(a_copy.shape()[0] == b_copy.shape()[0]); size_t layers = a_copy.shape()[0]; Mat sub; { Mat a0 = xt::view(a_copy, 0); Mat b0 = xt::view(b_copy, 0); sub = matrix_mul(a0, b0); } auto out_shape = sub.shape(); out_shape.insert(out_shape.begin(), layers); auto result = Mat::from_shape(out_shape); xt::view(result, 0) = sub; for (size_t i = 1; i < layers; i++) { Mat ai = xt::view(a_copy, i); Mat bi = xt::view(b_copy, i); xt::view(result, i) = matrix_mul(ai, bi); } if (first_is_1d) { result = xt::squeeze(result, std::vector{result.dimension()-2}); } if (second_is_1d) { result = xt::squeeze(result, std::vector{result.dimension()-1}); } return result; } else if (a_copy.dimension() > b_copy.dimension()) { assert(a_copy.dimension() > b_copy.dimension()); size_t layers = a_copy.shape()[0]; Mat sub; { Mat a0 = xt::view(a_copy, 0); sub = matrix_mul(a0, b_copy); } auto out_shape = sub.shape(); out_shape.insert(out_shape.begin(), layers); auto result = Mat::from_shape(out_shape); xt::view(result, 0) = sub; for (size_t i = 1; i < layers; i++) { Mat ai = xt::view(a_copy, i); xt::view(result, i) = matrix_mul(ai, b_copy); } if (first_is_1d) { result = xt::squeeze(result, std::vector{result.dimension()-2}); } if (second_is_1d) { result = xt::squeeze(result, std::vector{result.dimension()-1}); } return result; } else { assert(a_copy.dimension() < b_copy.dimension()); size_t layers = b_copy.shape()[0]; Mat sub; { Mat b0 = xt::view(b_copy, 0); sub = matrix_mul(a_copy, b0); } auto out_shape = sub.shape(); out_shape.insert(out_shape.begin(), layers); auto result = Mat::from_shape(out_shape); xt::view(result, 0) = sub; for (size_t i = 1; i < layers; i++) { Mat bi = xt::view(b_copy, i); xt::view(result, i) = matrix_mul(a_copy, bi); } if (first_is_1d) { result = xt::squeeze(result, std::vector{result.dimension()-2}); } if (second_is_1d) { result = xt::squeeze(result, std::vector{result.dimension()-1}); } return result; } } } template ndarray> matmul(const ndarray& a, const ndarray& b) { return ndarray>(matrix_mul(a.get_array(), b.get_array())); } template ndarray adapt(const std::vector& init_list) { return ndarray(xt::adapt(init_list)); } template ndarray adapt(const std::vector>& init_list) { std::vector flat_list; for(auto row: init_list) { for(auto elem: row) { flat_list.push_back(elem); } } std::vector sh = {init_list.size(), init_list[0].size()}; return ndarray(xt::adapt(flat_list, sh)); } template ndarray adapt(const std::vector>>& init_list) { std::vector flat_list; for(auto row: init_list) { for(auto elem: row) { for(auto val: elem) { flat_list.push_back(val); } } } std::vector sh = {init_list.size(), init_list[0].size(), init_list[0][0].size()}; return ndarray(xt::adapt(flat_list, sh)); } template ndarray adapt(const std::vector>>>& init_list) { std::vector flat_list; for(auto row: init_list) { for(auto elem: row) { for(auto val: elem) { for(auto v: val) { flat_list.push_back(v); } } } } std::vector sh = {init_list.size(), init_list[0].size(), init_list[0][0].size(), init_list[0][0][0].size()}; return ndarray(xt::adapt(flat_list, sh)); } template ndarray adapt(const std::vector>>>>& init_list) { std::vector flat_list; for(auto row: init_list) { for(auto elem: row) { for(auto val: elem) { for(auto v: val) { for(auto v1: v) { flat_list.push_back(v1); } } } } } std::vector sh = {init_list.size(), init_list[0].size(), init_list[0][0].size(), init_list[0][0][0].size(), init_list[0][0][0][0].size()}; return ndarray(xt::adapt(flat_list, sh)); } // Array Creation template ndarray array(const std::vector& vec, const _ShapeLike& shape = {}) { if(shape.empty()) { return ndarray(xt::cast(xt::adapt(vec))); } else { return ndarray(xt::cast(xt::adapt(vec, shape))); } } template ndarray zeros(const _ShapeLike& shape) { return ndarray(xt::zeros(shape)); } template ndarray ones(const _ShapeLike& shape) { return ndarray(xt::ones(shape)); } template ndarray full(const _ShapeLike& shape, const T& fill_value) { xt::xarray result = xt::ones(shape); for(auto it = result.begin(); it != result.end(); ++it) { *it = fill_value; } return ndarray(result); } template ndarray identity(int n) { return ndarray(xt::eye(n)); } template ndarray arange(const T& stop) { return ndarray(xt::arange(stop)); } template ndarray arange(const T& start, const T& stop) { return ndarray(xt::arange(start, stop)); } template ndarray arange(const T& start, const T& stop, const T& step) { return ndarray(xt::arange(start, stop, step)); } template ndarray linspace(const T& start, const T& stop, int num = 50, bool endpoint = true) { return ndarray(xt::linspace(start, stop, num, endpoint)); } // Trigonometry template ndarray sin(const ndarray& arr) { return ndarray(xt::sin(arr.get_array())); } ndarray sin(const ndarray& arr) { return ndarray(xt::sin(arr.get_array())); } ndarray sin(const ndarray& arr) { return ndarray(xt::sin(arr.get_array())); } template ndarray cos(const ndarray& arr) { return ndarray(xt::cos(arr.get_array())); } ndarray cos(const ndarray& arr) { return ndarray(xt::cos(arr.get_array())); } ndarray cos(const ndarray& arr) { return ndarray(xt::cos(arr.get_array())); } template ndarray tan(const ndarray& arr) { return ndarray(xt::tan(arr.get_array())); } ndarray tan(const ndarray& arr) { return ndarray(xt::tan(arr.get_array())); } ndarray tan(const ndarray& arr) { return ndarray(xt::tan(arr.get_array())); } template ndarray arcsin(const ndarray& arr) { return ndarray(xt::asin(arr.get_array())); } ndarray arcsin(const ndarray& arr) { return ndarray(xt::asin(arr.get_array())); } ndarray arcsin(const ndarray& arr) { return ndarray(xt::asin(arr.get_array())); } template ndarray arccos(const ndarray& arr) { return ndarray(xt::acos(arr.get_array())); } ndarray arccos(const ndarray& arr) { return ndarray(xt::acos(arr.get_array())); } ndarray arccos(const ndarray& arr) { return ndarray(xt::acos(arr.get_array())); } template ndarray arctan(const ndarray& arr) { return ndarray(xt::atan(arr.get_array())); } ndarray arctan(const ndarray& arr) { return ndarray(xt::atan(arr.get_array())); } ndarray arctan(const ndarray& arr) { return ndarray(xt::atan(arr.get_array())); } // Exponents and Logarithms template ndarray exp(const ndarray& arr) { return ndarray(xt::exp(arr.get_array())); } ndarray exp(const ndarray& arr) { return ndarray(xt::exp(arr.get_array())); } ndarray exp(const ndarray& arr) { return ndarray(xt::exp(arr.get_array())); } template ndarray log(const ndarray& arr) { return ndarray(xt::log(arr.get_array())); } ndarray log(const ndarray& arr) { return ndarray(xt::log(arr.get_array())); } ndarray log(const ndarray& arr) { return ndarray(xt::log(arr.get_array())); } template ndarray log2(const ndarray& arr) { return ndarray(xt::log2(arr.get_array())); } template ndarray log10(const ndarray& arr) { return ndarray(xt::log10(arr.get_array())); } // Miscellanous template ndarray round(const ndarray& arr) { return ndarray(xt::round(arr.get_array())); } template ndarray floor(const ndarray& arr) { return ndarray(xt::floor(arr.get_array())); } template ndarray ceil(const ndarray& arr) { return ndarray(xt::ceil(arr.get_array())); } template auto abs(const ndarray& arr) { if constexpr(std::is_same_v || std::is_same_v) { return ndarray(xt::abs(arr.get_array())); } else { return ndarray(xt::abs(arr.get_array())); } } // Xtensor only supports concatenation of initialized objects. // https://github.com/xtensor-stack/xtensor/issues/1450 template auto concatenate(const ndarray& arr1, const ndarray& arr2, int axis = 0) { using result_type = std::common_type_t; xt::xarray xarr1 = xt::cast(arr1.get_array()); xt::xarray xarr2 = xt::cast(arr2.get_array()); return ndarray(xt::concatenate(xt::xtuple(xarr1, xarr2), axis)); } // Constants constexpr float_ pi = xt::numeric_constants::PI; constexpr double inf = std::numeric_limits::infinity(); // Testing Functions template bool allclose(const ndarray& arr1, const ndarray& arr2, float_ rtol = 1e-5, float_ atol = 1e-8) { return xt::allclose(arr1.get_array(), arr2.get_array(), rtol, atol); } // Reverse Dunder Methods template >> auto operator+ (const U& scalar, const ndarray& array) { xt::xarray arr = array.get_array(); if constexpr(std::is_same_v) { xt::xarray result = scalar + xt::cast(arr); return ndarray(result); } else { using result_type = std::common_type_t; xt::xarray result = scalar + xt::cast(arr); return ndarray(result); } } template >> auto operator- (const U& scalar, const ndarray& array) { xt::xarray arr = array.get_array(); if constexpr(std::is_same_v) { xt::xarray result = scalar - xt::cast(arr); return ndarray(result); } else { using result_type = std::common_type_t; xt::xarray result = scalar - xt::cast(arr); return ndarray(result); } } template >> auto operator* (const U& scalar, const ndarray& array) { xt::xarray arr = array.get_array(); if constexpr(std::is_same_v) { xt::xarray result = scalar * xt::cast(arr); return ndarray(result); } else { using result_type = std::common_type_t; xt::xarray result = scalar * xt::cast(arr); return ndarray(result); } } template >> auto operator/ (const U& scalar, const ndarray& array) { xt::xarray arr = array.get_array(); xt::xarray result = static_cast(scalar) / xt::cast(arr); return ndarray(result); } template >> auto pow(const U& scalar, const ndarray& array) { xt::xarray arr = array.get_array(); xt::xarray result = xt::pow(scalar, xt::cast(arr)); return ndarray(result); } template >> auto operator& (const U& scalar, const ndarray& array) { return array & scalar; } template >> auto operator| (const U& scalar, const ndarray& array) { return array | scalar; } template >> auto operator^ (const U& scalar, const ndarray& array) { return array ^ scalar; } template std::ostream& operator<< (std::ostream& os, const ndarray& arr) { os << arr.get_array(); return os; } } // namespace numpy } // namespace pkpy