11#ifndef NUMERICAL_TENSOR_OPS_HPP
12#define NUMERICAL_TENSOR_OPS_HPP
39 std::string
out {
"("};
40 for (std::size_t i {0}; i < shape.size(); ++i) {
41 out += (i == 0 ?
"" :
", ") + std::to_string(shape[i]);
43 return out + (shape.size() == 1 ?
",)" :
")");
51 std::vector<std::size_t>
strides(rank, 0);
53 for (std::size_t i {shape.size()}; i-- > 0;) {
54 strides[rank - shape.size() + i] = shape[i] == 1 ? 0 :
stride;
74 if (
axis >= shape.size()) {
75 throw dimension_error(
"axis " + std::to_string(
axis) +
" is out of bounds for a tensor of rank "
76 + std::to_string(shape.size()));
78 const auto before {shape.begin() +
static_cast<std::ptrdiff_t
>(
axis)};
82 if (std::find(shape.begin(),
before, std::size_t {0}) !=
before || std::find(
before + 1, shape.end(), std::size_t {0}) != shape.end()) {
95 template <std::
floating_po
int R>
102 return std::fma(x,
y,
z);
115 template <split_complex C>
120 using R =
typename C::value_type;
121 const R ii {a.imag() * b.imag()};
122 const R ir {a.imag() * b.real()};
131 template <split_complex C>
136 using R =
typename C::value_type;
143 const R ratio {b.imag() / b.real()};
147 const R ratio {b.real() / b.imag()};
156#if defined(__aarch64__) || defined(_M_ARM64)
158#elif defined(NUMERICAL_X86_COMPILED)
170#if defined(__APPLE__) || defined(_WIN32)
182#if defined(__APPLE__) && defined(__aarch64__)
194 template <
typename T>
198 const auto ones {[](
const tensor<T>& t) {
return std::ranges::all_of(t.shape(), [](std::size_t e) { return e == 1; }); }};
199 const std::size_t rank {std::max(a.rank(), b.rank())};
200 const auto broadcast {[rank](
const tensor<T>& t) {
return t.rank() != 0 && t.rank() != rank; }};
212 template <std::
floating_po
int T>
216 using wide = std::common_type_t<T, double>;
217 return static_cast<T>(
static_cast<wide>(value) /
static_cast<wide>(n));
225 template <split_complex T>
229 using R =
typename T::value_type;
230 const std::complex<double>
wide {
static_cast<double>(value.real()),
static_cast<double>(value.imag())};
232 return T {
static_cast<R>(
mean.real()),
static_cast<R>(
mean.imag())};
237 template <
typename T>
242 throw dimension_error(
"a mean over no element has no value in an integer type");
244 return value /
static_cast<T>(n);
253 template <std::floating_point
T,
261 if (value !=
T {0}) {
265 for (std::size_t i {0}; i < count; ++i) {
267 if (x ==
T {0} && std::signbit(x) !=
maximum) {
275 template <
typename T,
290 shape.erase(shape.begin() +
static_cast<std::ptrdiff_t
>(
axis));
298 template <
typename T,
306 for (std::size_t
o {0};
o <
walk.outer; ++
o) {
307 for (std::size_t i {0}; i <
walk.inner; ++i) {
315 template <
typename T>
318 return static_cast<T>(x * x);
323 template <
typename U>
326 return {(x.real() * x.real()) - (x.imag() * x.imag()), (x.real() * x.imag()) + (x.imag() * x.real())};
330 template <
typename T>
339 return std::isnan(x);
344 template <
typename V,
348 const std::greater<T>& )
354 template <
typename V,
358 const std::less<T>& )
364 template <
typename V>
378 template <
typename T,
386 std::size_t done {0};
388 using lane =
typename native_vector<T>::type;
390 constexpr std::size_t
lanes {4};
391 if (count >= width *
lanes) {
393 std::array<lane, lanes>
best {};
394 std::array<mask, lanes>
nans {};
395 for (std::size_t
l {0};
l <
lanes; ++
l) {
396 std::memcpy(&
best[
l], first + (
l * width),
sizeof(
lane));
398 done = count - (count % (width *
lanes));
399 for (std::size_t i {0}; i < done; i += width *
lanes) {
401 for (std::size_t
l {0};
l <
lanes; ++
l) {
403 std::memcpy(&x, first + i + (
l * width),
sizeof(
lane));
407 best[
l] = std::bit_cast<lane>((std::bit_cast<mask>(x) &
take) | (std::bit_cast<mask>(
best[
l]) & ~
take));
411 for (std::size_t
l {0};
l <
lanes; ++
l) {
412 for (std::size_t k {0}; k < width; ++k) {
419 std::array<T, 8>
best {};
420 std::array<unsigned, 8>
nans {};
422 for (std::size_t i {done}; i < count; ++i) {
423 const T x {first[i]};
424 nans[i % 8] |=
static_cast<unsigned>(std::isnan(x));
427 for (std::size_t
l {0};
l < 8; ++
l) {
431 return nan ? std::numeric_limits<T>::quiet_NaN() :
result;
435 template <
typename T,
443 throw dimension_error(
"a zero-size reduction to a maximum or a minimum has no identity");
445 if constexpr (std::is_floating_point_v<T>) {
453 for (std::size_t i {1}; i < count; ++i) {
469 template <
typename T,
477 if (
walk.extent == 0) {
478 throw dimension_error(
"a zero-size reduction to a maximum or a minimum has no identity");
480 if (!std::is_floating_point_v<T> ||
walk.inner == 1) {
486 std::vector<unsigned>
nan(
walk.inner);
487 for (std::size_t
o {0};
o <
walk.outer; ++
o) {
491 std::fill(
nan.begin(),
nan.end(), 0
U);
492 for (std::size_t e {0}; e <
walk.extent; ++e) {
494 for (std::size_t i {0}; i <
walk.inner; ++i) {
496 nan[i] |=
static_cast<unsigned>(std::isnan(x));
500 for (std::size_t i {0}; i <
walk.inner; ++i) {
503 best[i] = std::numeric_limits<T>::quiet_NaN();
516 const std::vector<std::size_t>& b)
518 const std::size_t rank {std::max(a.size(), b.size())};
519 std::vector<std::size_t>
out(rank);
520 for (std::size_t i {0}; i < rank; ++i) {
521 const std::size_t x {i < rank - a.size() ? 1 : a[i - (rank - a.size())]};
522 const std::size_t
y {i < rank - b.size() ? 1 : b[i - (rank - b.size())]};
523 if (x !=
y && x != 1 &&
y != 1) {
527 out[i] = x == 1 ?
y : x;
536 template <
typename T,
548 std::transform(a.
data().begin() +
static_cast<std::ptrdiff_t
>(first), a.
data().begin() +
static_cast<std::ptrdiff_t
>(
last),
549 b.
data().begin() +
static_cast<std::ptrdiff_t
>(first),
out.begin() +
static_cast<std::ptrdiff_t
>(first),
op);
554 const std::size_t rank {shape.size()};
565 const std::size_t inner {shape[rank - 1]};
566 const std::size_t
step_a {
sa[rank - 1]};
567 const std::size_t
step_b {
sb[rank - 1]};
568 const std::size_t rows {count / inner};
573 std::vector<std::size_t> index(rank, 0);
576 for (std::size_t
axis {rank - 1},
rest {first};
axis-- > 0;) {
583 const T*
const pa {a.
data().data() +
ia};
584 const T*
const pb {b.
data().data() +
ib};
585 T*
const po {
out.data() + (
row * inner)};
587 for (std::size_t i {0}; i < inner; ++i) {
593 for (std::size_t i {0}; i < inner; ++i) {
599 for (std::size_t i {0}; i < inner; ++i) {
606 for (std::size_t
axis {rank - 1};
axis-- > 0;) {
627 template <detail::split_complex C>
637 template <detail::split_complex C>
646 template <
typename T>
654 template <
typename T>
662 template <
typename T>
670 template <
typename T>
678 template <
typename T>
682 std::transform(a.
data().begin(), a.
data().end(),
out.begin(), std::negate<T> {});
687 template <
typename T,
692 using R = std::decay_t<decltype(f(std::declval<const T&>()))>;
694 std::transform(t.data().begin(), t.data().end(),
out.begin(),
f);
703 template <
typename T>
707 if constexpr (!std::is_integral_v<T>) {
714 return map(a, [](
const T& x) {
return static_cast<T>(std::sqrt(x)); });
717 return map(a, [](
const T& x) {
return static_cast<T>(
T(1) / x); });
721 return broadcast_apply(a, b, [](
const T& x,
const T&
y) {
return static_cast<T>(std::pow(x,
y)); });
725 template <
typename T>
732 template <
typename T>
745 template <
typename T>
752 template <
typename T>
766 template <std::totally_ordered T>
773 template <std::totally_ordered T>
780 template <std::totally_ordered T>
788 template <std::totally_ordered T>
799 template <
typename T>
801 std::vector<std::size_t> shape)
803 std::size_t count {1};
804 for (
const std::size_t extent : shape) {
807 if (count != t.
size()) {
808 throw dimension_error(
"cannot reshape a tensor of size " + std::to_string(t.
size()) +
" into shape "
815 template <
typename T>
819 std::size_t count {1};
820 for (
const std::size_t extent : shape) {
827 template <
typename T>
830 return full<T>(std::move(shape),
T {});
834 template <
typename T>
837 return full<T>(std::move(shape),
static_cast<T>(1));
841 template <
typename T>
845 for (std::size_t i {0}; i < n; ++i) {
846 out[(i * n) + i] =
static_cast<T>(1);
863 if (
step == 0.0 || !std::isfinite(
step) || !std::isfinite(start) || !std::isfinite(
stop)) {
864 throw dimension_error(
"arange needs finite bounds and a finite, non-zero step");
868 throw dimension_error(
"arange: the length does not fit in std::size_t");
870 const std::size_t count {
length > 0.0 ?
static_cast<std::size_t
>(
length) : 0};
872 const double delta {(start +
step) - start};
873 for (std::size_t i {0}; i < count; ++i) {
874 out[i] = std::fma(
static_cast<double>(i), delta, start);
889 const double step {count > 1 ? (
stop - start) /
static_cast<double>(count - 1) : 0.0};
890 for (std::size_t i {0}; i < count; ++i) {
895 const double offset {std::fma(
static_cast<double>(i),
step, -0.0)};
896 out[i] = start + offset;
Thrown when an operation's operands have incompatible shapes.
A dense, row-major, N-dimensional tensor of T with value semantics.
const std::vector< std::size_t > & shape() const noexcept
The size of each axis.
std::size_t size() const noexcept
The total number of elements.
const buffer< T > & data() const noexcept
The elements, row-major.
bool is_nan(const T &)
A NaN? Never for a scalar without one.
T divided_by_count(T value, std::size_t n)
A mean: value divided by the count n, in the wider of T and double and back to T, the count exact (to...
T pairwise_sum(const T *first, std::size_t count, std::size_t stride)
Sum of count values from first at stride stride, by NumPy's pairwise summation: below 8 values in ord...
axis_walk walk_along(const std::vector< std::size_t > &shape, std::size_t axis)
axis_walk of shape along axis; outer and inner 0 where the result is empty.
bool numpy_fuses_scalar_products() noexcept
Whether NumPy's complex product fuses its multiply-adds in its scalar loop (where numpy_loop_is_scala...
T square(const T &x)
x · x.
T settled_zero(T value, const T *first, std::size_t count, std::size_t stride, const Order &larger)
The extreme value of count elements stride apart from first, its zero's sign settled as IEEE 754-2019...
bool numpy_loop_is_scalar(const tensor< T > &a, const tensor< T > &b) noexcept
Whether NumPy multiplies a by b in its scalar loop rather than its vector one: when the result is one...
bool numpy_fuses_products() noexcept
Whether NumPy's complex product fuses its multiply-adds on this processor: on arm64,...
std::string shape_text(const std::vector< std::size_t > &shape)
A shape as NumPy prints it, (2, 3), for a message.
C numpy_multiply(const C &a, const C &b, bool fused)
a · b of complex numbers as NumPy's array loop computes it: ai·bi and ai·br rounded,...
constexpr std::size_t elementwise_chunk
The elements one task of an element-wise operation takes, a quarter of a thread's work: a tensor of a...
bool numpy_fuses_quotients() noexcept
Whether NumPy's complex quotient fuses its multiply-adds, as its PyPI wheels are built: on macOS arm6...
T contiguous_extreme(const T *first, std::size_t count, const Order &larger)
The largest (larger is std::greater) or smallest of count contiguous elements, or a NaN if one is amo...
T extreme(const T *first, std::size_t count, std::size_t stride, const Order &larger)
The largest (larger is std::greater) or smallest element of a line.
void parallel_for(std::size_t count, std::size_t work_per_index, const Body &body)
Runs body(i) for every i in [0, count).
double real_part(const T &x)
The real part of a real (or general) scalar as a double.
std::size_t checked_mul(std::size_t a, std::size_t b)
a * b, throwing numerical::dimension_error if the product overflows std::size_t. Guards dimension/sha...
std::vector< std::size_t > without_axis(std::vector< std::size_t > shape, std::size_t axis)
The shape with axis axis removed.
C numpy_divide(const C &a, const C &b, bool fused)
a / b of complex numbers as NumPy's array loop computes it: Smith's division by the larger part of b ...
void pairwise_sums(const T *data, std::size_t outer, std::size_t count, std::size_t inner, T *out)
The pairwise sums of outer blocks of count rows of inner elements (row-major), each column of a block...
std::vector< std::size_t > broadcast_strides(const std::vector< std::size_t > &shape, std::size_t rank)
The row-major strides of shape, with a stride of 0 on each axis of extent 1 (it broadcasts),...
std::size_t element_count(It first, It last)
The elements a shape's extents [first, last) hold: 0 where one of them is 0, whatever the others,...
tensor< T > reduce_along(const tensor< T > &t, std::size_t axis, const Reduce &reduce)
Reduces each line of t along axis by reduce, which takes the line's first element,...
tensor< T > extremes_along(const tensor< T > &t, std::size_t axis, const Order &larger)
The largest (larger is std::greater) or smallest elements along axis. Along an inner axis each line i...
@ fused
std::fma, where every target has the instruction.
R numpy_muladd(R x, R y, R z, bool fused)
x · y + z: one fused multiply-add where fused, else the product rounded first (statements apart,...
The NumericAL public API.
std::vector< T, detail::default_init_allocator< T > > buffer
The storage of vector, matrix and tensor, namely a std::vector whose buffer<T>(n) and resize(n) leave...
tensor< T > broadcast_apply(const tensor< T > &a, const tensor< T > &b, const Op &op)
op(a, b) element by element, the operands broadcast against each other.
T sum(const tensor< T > &t)
The sum of every element, by pairwise halving (0 for an empty tensor).
matrix< T > operator+(const matrix< T > &a, const matrix< T > &b)
Element-wise sum.
tensor< double > linspace(double start, double stop, std::size_t count)
count values evenly spaced from start to stop, both included, as NumPy's linspace: element i is start...
tensor< C > numpy_divide(const tensor< C > &a, const tensor< C > &b)
a / b of complex tensors, broadcast, each quotient as NumPy's array loop computes it (detail::numpy_d...
matrix< T > operator*(const T &scalar, const matrix< T > &m)
Scalar times matrix.
tensor< T > operator/(const tensor< T > &a, const tensor< T > &b)
Element-wise quotient, broadcast.
tensor< C > numpy_multiply(const tensor< C > &a, const tensor< C > &b)
a · b of complex tensors, broadcast, each product as NumPy's array loop computes it (detail::numpy_mu...
tensor< T > ones(std::vector< std::size_t > shape)
A tensor of shape full of ones.
std::vector< std::size_t > broadcast_shape(const std::vector< std::size_t > &a, const std::vector< std::size_t > &b)
The shape two shapes broadcast to, as NumPy's broadcast_shapes.
@ amx
The Apple AMX coprocessor, fused multiply-adds; the products it does not suit on the NEON path's tile...
T max(const tensor< T > &t)
The largest element (a NaN, if there is one).
tensor< T > zeros(std::vector< std::size_t > shape)
A tensor of shape full of zeros.
matrix< T > operator-(const matrix< T > &a, const matrix< T > &b)
Element-wise difference.
auto map(const tensor< T > &t, const F &f) -> tensor< std::decay_t< decltype(f(std::declval< const T & >()))> >
f(x) for every element, in a tensor of the same shape and of f's result type.
tensor< T > eye(std::size_t n)
The n × n identity.
tensor< double > arange(double start, double stop, double step)
start, start + step, … below stop (above it for a negative step), as NumPy's arange: its length is ce...
tensor< T > reshape(const tensor< T > &t, std::vector< std::size_t > shape)
The same elements in the same order under another shape.
T mean(const tensor< T > &t)
The mean of every element. An empty tensor's mean is NaN, as NumPy's (which also warns).
T min(const tensor< T > &t)
The smallest element (a NaN, if there is one).
tensor< T > full(std::vector< std::size_t > shape, const T &value)
A tensor of shape with every element value.
tensor< T > pow(const tensor< T > &a, const tensor< T > &b)
a to the power b element by element, broadcast, as std::pow; a single exponent of 2,...
The native vector of float and double the kernels hold their lanes in (GNU vector extensions,...
The extents and strides that walk a tensor of shape with axis axis taken out: outer positions before ...
std::size_t outer
The product of the extents before the axis.
std::size_t inner
The product of the extents after the axis.
std::size_t extent
The axis' extent.
A dense, row-major, N-dimensional tensor and Einstein summation.