6#include <nk/value/value.hpp>
7#include <nk/ops/value_factory.hpp>
8#include <nk/value/scratch.hpp>
9#include <nk/value/shape_ops.hpp>
15#include <memory_resource>
25inline std::pair<Value, Value> promoteToComplex(
const Value &a,
const Value &b, std::pmr::memory_resource *mr)
28 if (a.isComplex() && !b.isComplex())
29 cb.promoteToComplex(mr);
30 else if (!a.isComplex() && b.isComplex())
31 ca.promoteToComplex(mr);
40inline bool broadcastDims(
size_t ar,
size_t ac,
size_t br,
size_t bc,
41 size_t &outR,
size_t &outC)
43 if (ar != br && ar != 1 && br != 1)
return false;
44 if (ac != bc && ac != 1 && bc != 1)
return false;
45 outR = std::max(ar, br);
46 outC = std::max(ac, bc);
52inline bool broadcastDims3D(
size_t ar,
size_t ac,
size_t ap,
53 size_t br,
size_t bc,
size_t bp,
54 size_t &outR,
size_t &outC,
size_t &outP)
56 if (ar != br && ar != 1 && br != 1)
return false;
57 if (ac != bc && ac != 1 && bc != 1)
return false;
58 if (ap != bp && ap != 1 && bp != 1)
return false;
59 outR = std::max(ar, br);
60 outC = std::max(ac, bc);
61 outP = std::max(ap, bp);
68inline size_t broadcastOffset3D(
size_t r,
size_t c,
size_t p,
69 size_t aR,
size_t aC,
size_t aP)
71 const size_t rr = (aR == 1) ? 0 : r;
72 const size_t cc = (aC == 1) ? 0 : c;
73 const size_t pp = (aP == 1) ? 0 : p;
75 return pp * aR * aC + cc * aR + rr;
91using ops::createMatrix;
92using ops::createForDims;
100inline Value absIntegerSaturate(
const Value &x, std::pmr::memory_resource *mr)
102 Value r = createLike(x, x.type(), mr);
103 const size_t n = x.numel();
104 auto fill = [&](
auto *
dst) {
105 using T = std::remove_pointer_t<std::decay_t<
decltype(
dst)>>;
106 const double lo =
static_cast<double>(std::numeric_limits<T>::min());
107 const double hi =
static_cast<double>(std::numeric_limits<T>::max());
108 for (
size_t i = 0; i < n; ++i) {
109 double v = std::fabs(x.elemAsDouble(i));
111 else if (v > hi) v = hi;
112 dst[i] =
static_cast<T
>(v);
116 case ValueType::INT8: fill(r.int8DataMut());
break;
117 case ValueType::INT16: fill(r.int16DataMut());
break;
118 case ValueType::INT32: fill(r.int32DataMut());
break;
119 case ValueType::INT64: fill(r.int64DataMut());
break;
120 case ValueType::UINT8: fill(r.uint8DataMut());
break;
121 case ValueType::UINT16: fill(r.uint16DataMut());
break;
122 case ValueType::UINT32: fill(r.uint32DataMut());
break;
123 case ValueType::UINT64: fill(r.uint64DataMut());
break;
135inline Value doubleToIntegerExact(
const Value &d, ValueType vt,
136 std::pmr::memory_resource *mr)
138 Value r = createForDims(d.dims(), vt, mr);
139 const size_t n = d.numel();
140 auto fill = [&](
auto *
dst) {
141 using T = std::remove_pointer_t<std::decay_t<
decltype(
dst)>>;
142 const double lo =
static_cast<double>(std::numeric_limits<T>::min());
143 const double hi =
static_cast<double>(std::numeric_limits<T>::max());
144 for (
size_t i = 0; i < n; ++i) {
145 double v = d.elemAsDouble(i);
147 else if (v > hi) v = hi;
148 dst[i] =
static_cast<T
>(v);
152 case ValueType::INT8: fill(r.int8DataMut());
break;
153 case ValueType::INT16: fill(r.int16DataMut());
break;
154 case ValueType::INT32: fill(r.int32DataMut());
break;
155 case ValueType::INT64: fill(r.int64DataMut());
break;
156 case ValueType::UINT8: fill(r.uint8DataMut());
break;
157 case ValueType::UINT16: fill(r.uint16DataMut());
break;
158 case ValueType::UINT32: fill(r.uint32DataMut());
break;
159 case ValueType::UINT64: fill(r.uint64DataMut());
break;
169inline Value toDoubleValue(
const Value &x, std::pmr::memory_resource *mr)
171 if (x.type() == ValueType::DOUBLE)
return x;
172 Value r = createForDims(x.dims(), ValueType::DOUBLE, mr);
173 double *
dst = r.doubleDataMut();
174 const size_t n = x.numel();
175 for (
size_t i = 0; i < n; ++i) dst[i] = x.elemAsDouble(i);
183inline Value copyIntegerSameClass(
const Value &x, std::pmr::memory_resource *mr)
185 Value r = createLike(x, x.type(), mr);
186 const size_t n = x.numel();
188 case ValueType::INT8: std::copy(x.int8Data(), x.int8Data() + n, r.int8DataMut());
break;
189 case ValueType::INT16: std::copy(x.int16Data(), x.int16Data() + n, r.int16DataMut());
break;
190 case ValueType::INT32: std::copy(x.int32Data(), x.int32Data() + n, r.int32DataMut());
break;
191 case ValueType::INT64: std::copy(x.int64Data(), x.int64Data() + n, r.int64DataMut());
break;
192 case ValueType::UINT8: std::copy(x.uint8Data(), x.uint8Data() + n, r.uint8DataMut());
break;
193 case ValueType::UINT16: std::copy(x.uint16Data(), x.uint16Data() + n, r.uint16DataMut());
break;
194 case ValueType::UINT32: std::copy(x.uint32Data(), x.uint32Data() + n, r.uint32DataMut());
break;
195 case ValueType::UINT64: std::copy(x.uint64Data(), x.uint64Data() + n, r.uint64DataMut());
break;
206 ValueType outType, std::pmr::memory_resource *mr)
208 const bool aShaper = !a.isScalar();
209 const bool bShaper = !b.isScalar();
210 if (aShaper && bShaper && a.dims() != b.dims())
211 throw std::runtime_error(
"Matrix dimensions must agree. a=[" + std::to_string(a.dims().rows()) +
"x" + std::to_string(a.dims().cols()) +
"], b=[" + std::to_string(b.dims().rows()) +
"x" + std::to_string(b.dims().cols()) +
"] (line 211)");
212 const Value &shape = aShaper ? a : (bShaper ? b : a);
213 return createLike(shape, outType, mr);
219inline ValueType arithOutType(
const Value &a,
const Value &b)
221 if (a.isComplex() || b.isComplex())
return ValueType::COMPLEX;
222 if (a.type() == ValueType::SINGLE || b.type() == ValueType::SINGLE)
return ValueType::SINGLE;
223 if (isIntegerType(a.type()))
return a.type();
224 if (isIntegerType(b.type()))
return b.type();
225 return ValueType::DOUBLE;
230inline Value emptyArithResult(
const Value &a,
const Value &b, std::pmr::memory_resource *mr)
232 return emptyResultForBinary(a, b, arithOutType(a, b), mr);
239Value elementwiseDouble(
const Value &a,
const Value &b, Op op, std::pmr::memory_resource *mr)
241 if (a.isEmpty() || b.isEmpty())
242 return emptyResultForBinary(a, b, ValueType::DOUBLE, mr);
243 if (a.isScalar() && b.isScalar())
244 return Value::scalar(op(a.toScalar(), b.toScalar()), mr);
249 if (a.dims().ndim() >= 4 || b.dims().ndim() >= 4) {
251 auto r = createLike(b, ValueType::DOUBLE, mr);
252 const double s = a.toScalar();
253 const double *
db = b.doubleData();
254 double *
dst = r.doubleDataMut();
255 for (
size_t i = 0; i < b.numel(); ++i) dst[i] = op(s, db[i]);
259 auto r = createLike(a, ValueType::DOUBLE, mr);
260 const double s = b.toScalar();
261 const double *da = a.doubleData();
262 double *
dst = r.doubleDataMut();
263 for (
size_t i = 0; i < a.numel(); ++i) dst[i] = op(da[i], s);
268 if (!broadcastDimsND(a.dims(), b.dims(), outD))
269 throw std::runtime_error(
"ND dimensions must broadcast: each axis must match or be 1");
270 auto r = createForDims(outD, ValueType::DOUBLE, mr);
271 const double *da = a.doubleData(), *
db = b.doubleData();
272 double *
dst = r.doubleDataMut();
273 forEachNDPair(a.dims(), b.dims(), outD,
274 [&](
size_t outIdx,
size_t aOff,
size_t bOff) {
275 dst[outIdx] = op(da[aOff], db[bOff]);
282 if (a.dims().is3D() || b.dims().is3D()) {
284 auto r = createLike(b, ValueType::DOUBLE, mr);
285 double s = a.toScalar();
286 for (
size_t i = 0; i < b.numel(); ++i)
287 r.doubleDataMut()[i] = op(s, b.doubleData()[i]);
291 auto r = createLike(a, ValueType::DOUBLE, mr);
292 double s = b.toScalar();
293 for (
size_t i = 0; i < a.numel(); ++i)
294 r.doubleDataMut()[i] = op(a.doubleData()[i], s);
297 const size_t aR = a.dims().rows(), aC = a.dims().cols();
298 const size_t aP = a.dims().is3D() ? a.dims().pages() : 1;
299 const size_t bR = b.dims().rows(), bC = b.dims().cols();
300 const size_t bP = b.dims().is3D() ? b.dims().pages() : 1;
301 size_t outR, outC, outP;
302 if (!broadcastDims3D(aR, aC, aP, bR, bC, bP, outR, outC, outP))
303 throw std::runtime_error(
304 "3D dimensions must broadcast: each axis must match or be 1");
307 if (aR == bR && aC == bC && aP == bP) {
308 auto r = (outP > 1) ? Value::matrix3d(outR, outC, outP, ValueType::DOUBLE, mr)
309 : Value::matrix(outR, outC, ValueType::DOUBLE, mr);
310 for (
size_t i = 0; i < a.numel(); ++i)
311 r.doubleDataMut()[i] = op(a.doubleData()[i], b.doubleData()[i]);
314 auto r = (outP > 1) ? Value::matrix3d(outR, outC, outP, ValueType::DOUBLE, mr)
315 : Value::matrix(outR, outC, ValueType::DOUBLE, mr);
316 double *
dst = r.doubleDataMut();
317 const double *da = a.doubleData(), *
db = b.doubleData();
318 for (
size_t pp = 0; pp < outP; ++pp)
319 for (
size_t cc = 0; cc < outC; ++cc)
320 for (
size_t rr = 0; rr < outR; ++rr) {
321 const size_t aOff = broadcastOffset3D(rr, cc, pp, aR, aC, aP);
322 const size_t bOff = broadcastOffset3D(rr, cc, pp, bR, bC, bP);
323 dst[pp * outR * outC + cc * outR + rr] = op(da[aOff], db[bOff]);
328 size_t ar = a.dims().rows(), ac = a.dims().cols();
329 size_t br = b.dims().rows(), bc = b.dims().cols();
331 if (!broadcastDims(ar, ac, br, bc, outR, outC))
332 throw std::runtime_error(
"Matrix dimensions must agree. a=[" + std::to_string(ar) +
"x" + std::to_string(ac) +
"], b=[" + std::to_string(br) +
"x" + std::to_string(bc) +
"] (line 332)");
335 if (ar == br && ac == bc) {
336 auto r = Value::matrix(outR, outC, ValueType::DOUBLE, mr);
337 for (
size_t i = 0; i < a.numel(); ++i)
338 r.doubleDataMut()[i] = op(a.doubleData()[i], b.doubleData()[i]);
343 auto r = Value::matrix(outR, outC, ValueType::DOUBLE, mr);
344 double *
dst = r.doubleDataMut();
345 const double *da = a.doubleData(), *
db = b.doubleData();
346 for (
size_t c = 0; c < outC; ++c) {
347 size_t ca = (ac == 1) ? 0 : c;
348 size_t cb = (bc == 1) ? 0 : c;
349 for (
size_t row = 0; row < outR; ++row) {
350 size_t ra = (ar == 1) ? 0 : row;
351 size_t rb = (br == 1) ? 0 : row;
352 dst[c * outR + row] = op(da[ca * ar + ra], db[cb * br + rb]);
367Value elementwiseComplexImpl(
const Value &a,
const Value &b, Op op, std::pmr::memory_resource *mr)
369 if (a.isEmpty() || b.isEmpty())
370 return emptyResultForBinary(a, b, ValueType::COMPLEX, mr);
371 auto [ca, cb] = promoteToComplex(a, b, mr);
372 if (ca.isScalar() && cb.isScalar())
373 return Value::complexScalar(op(ca.toComplex(), cb.toComplex()), mr);
376 if (ca.dims().ndim() >= 4 || cb.dims().ndim() >= 4) {
378 auto r = createLike(cb, ValueType::COMPLEX, mr);
379 Complex s = ca.toComplex();
380 const Complex *
db = cb.complexData();
381 Complex *
dst = r.complexDataMut();
382 for (
size_t i = 0; i < cb.numel(); ++i) dst[i] = op(s, db[i]);
386 auto r = createLike(ca, ValueType::COMPLEX, mr);
387 Complex s = cb.toComplex();
388 const Complex *da = ca.complexData();
389 Complex *
dst = r.complexDataMut();
390 for (
size_t i = 0; i < ca.numel(); ++i) dst[i] = op(da[i], s);
394 if (!broadcastDimsND(ca.dims(), cb.dims(), outD))
395 throw std::runtime_error(
"ND dimensions must broadcast: each axis must match or be 1");
396 auto r = createForDims(outD, ValueType::COMPLEX, mr);
397 const Complex *da = ca.complexData(), *
db = cb.complexData();
398 Complex *
dst = r.complexDataMut();
399 forEachNDPair(ca.dims(), cb.dims(), outD,
400 [&](
size_t outIdx,
size_t aOff,
size_t bOff) {
401 dst[outIdx] = op(da[aOff], db[bOff]);
406 if (ca.dims().is3D() || cb.dims().is3D()) {
408 auto r = createLike(cb, ValueType::COMPLEX, mr);
409 Complex s = ca.toComplex();
410 for (
size_t i = 0; i < cb.numel(); ++i)
411 r.complexDataMut()[i] = op(s, cb.complexData()[i]);
415 auto r = createLike(ca, ValueType::COMPLEX, mr);
416 Complex s = cb.toComplex();
417 for (
size_t i = 0; i < ca.numel(); ++i)
418 r.complexDataMut()[i] = op(ca.complexData()[i], s);
421 const size_t aR = ca.dims().rows(), aC = ca.dims().cols();
422 const size_t aP = ca.dims().is3D() ? ca.dims().pages() : 1;
423 const size_t bR = cb.dims().rows(), bC = cb.dims().cols();
424 const size_t bP = cb.dims().is3D() ? cb.dims().pages() : 1;
425 size_t outR, outC, outP;
426 if (!broadcastDims3D(aR, aC, aP, bR, bC, bP, outR, outC, outP))
427 throw std::runtime_error(
428 "3D dimensions must broadcast: each axis must match or be 1");
430 if (aR == bR && aC == bC && aP == bP) {
431 auto r = createLike(ca, ValueType::COMPLEX, mr);
432 for (
size_t i = 0; i < ca.numel(); ++i)
433 r.complexDataMut()[i] = op(ca.complexData()[i], cb.complexData()[i]);
436 auto r = (outP > 1) ? Value::matrix3d(outR, outC, outP, ValueType::COMPLEX, mr)
437 : Value::matrix(outR, outC, ValueType::COMPLEX, mr);
438 Complex *
dst = r.complexDataMut();
439 const Complex *da = ca.complexData(), *
db = cb.complexData();
440 for (
size_t pp = 0; pp < outP; ++pp)
441 for (
size_t cc = 0; cc < outC; ++cc)
442 for (
size_t rr = 0; rr < outR; ++rr) {
443 const size_t aOff = broadcastOffset3D(rr, cc, pp, aR, aC, aP);
444 const size_t bOff = broadcastOffset3D(rr, cc, pp, bR, bC, bP);
445 dst[pp * outR * outC + cc * outR + rr] = op(da[aOff], db[bOff]);
450 size_t ar = ca.dims().rows(), ac = ca.dims().cols();
451 size_t br = cb.dims().rows(), bc = cb.dims().cols();
453 if (!broadcastDims(ar, ac, br, bc, outR, outC))
454 throw std::runtime_error(
"Matrix dimensions must agree. a=[" + std::to_string(ar) +
"x" + std::to_string(ac) +
"], b=[" + std::to_string(br) +
"x" + std::to_string(bc) +
"] (line 454)");
456 if (ar == br && ac == bc) {
457 auto r = Value::complexMatrix(outR, outC, mr);
458 for (
size_t i = 0; i < ca.numel(); ++i)
459 r.complexDataMut()[i] = op(ca.complexData()[i], cb.complexData()[i]);
463 auto r = Value::complexMatrix(outR, outC, mr);
464 Complex *
dst = r.complexDataMut();
465 const Complex *da = ca.complexData(), *
db = cb.complexData();
466 for (
size_t c = 0; c < outC; ++c) {
467 size_t cca = (ac == 1) ? 0 : c;
468 size_t ccb = (bc == 1) ? 0 : c;
469 for (
size_t row = 0; row < outR; ++row) {
470 size_t ra = (ar == 1) ? 0 : row;
471 size_t rb = (br == 1) ? 0 : row;
472 dst[c * outR + row] = op(da[cca * ar + ra], db[ccb * br + rb]);
481Value elementwiseComplex(
const Value &a,
const Value &b, Op op, std::pmr::memory_resource *mr)
483 return narrowComplex(elementwiseComplexImpl(a, b, std::move(op), mr), mr);
490Value unaryDouble(
const Value &a, Op op, std::pmr::memory_resource *mr)
493 return Value::scalar(op(a.toScalar()), mr);
494 auto r = createLike(a, ValueType::DOUBLE, mr);
495 for (
size_t i = 0; i < a.numel(); ++i)
496 r.doubleDataMut()[i] = op(a.doubleData()[i]);
505Value unaryComplexImpl(
const Value &a, Op op, std::pmr::memory_resource *mr)
508 return Value::complexScalar(op(a.toComplex()), mr);
509 auto r = createLike(a, ValueType::COMPLEX, mr);
510 for (
size_t i = 0; i < a.numel(); ++i)
511 r.complexDataMut()[i] = op(a.complexData()[i]);
515Value unaryComplex(
const Value &a, Op op, std::pmr::memory_resource *mr)
517 return narrowComplex(unaryComplexImpl(a, std::move(op), mr), mr);
534inline size_t toDim(
double d)
536 if (!std::isfinite(d) || d != std::floor(d))
537 throw std::runtime_error(
"Size inputs must be integers.");
540 if (d >=
static_cast<double>(std::numeric_limits<size_t>::max()))
541 throw std::runtime_error(
"Requested array size is too large.");
542 return static_cast<size_t>(d);
551 if (args.size() == 1 && !args[0].isScalar() && args[0].numel() >= 2) {
552 const double *d = args[0].doubleData();
553 size_t n = args[0].numel();
554 size_t r = toDim(d[0]);
555 size_t c = toDim(d[1]);
556 size_t p = (n >= 3) ? toDim(d[2]) : 0;
561 if (args.size() == 1) {
562 size_t n = toDim(args[0].toScalar());
567 size_t r = toDim(args[0].toScalar());
568 size_t c = toDim(args[1].toScalar());
571 if (args.size() >= 3) {
572 size_t p = toDim(args[2].toScalar());
598 if (args.size() == 1 && !args[0].isScalar() && args[0].numel() >= 2) {
599 const double *d = args[0].doubleData();
600 const size_t n = args[0].numel();
602 for (
size_t i = 0; i < n; ++i)
603 out.push_back(toDim(d[i]));
607 if (args.size() == 1) {
608 const size_t n = toDim(args[0].toScalar());
613 out.reserve(args.size());
614 for (
const auto &a : args)
615 out.push_back(toDim(a.toScalar()));
624 while (dims.size() > 2 && dims.back() == 1)
632inline bool valueTypeFromName(std::string_view name, ValueType &out)
634 if (name ==
"double") { out = ValueType::DOUBLE;
return true; }
635 else if (name ==
"single") { out = ValueType::SINGLE;
return true; }
636 else if (name ==
"int8") { out = ValueType::INT8;
return true; }
637 else if (name ==
"int16") { out = ValueType::INT16;
return true; }
638 else if (name ==
"int32") { out = ValueType::INT32;
return true; }
639 else if (name ==
"int64") { out = ValueType::INT64;
return true; }
640 else if (name ==
"uint8") { out = ValueType::UINT8;
return true; }
641 else if (name ==
"uint16") { out = ValueType::UINT16;
return true; }
642 else if (name ==
"uint32") { out = ValueType::UINT32;
return true; }
643 else if (name ==
"uint64") { out = ValueType::UINT64;
return true; }
644 else if (name ==
"logical") { out = ValueType::LOGICAL;
return true; }
658 outType = ValueType::DOUBLE;
659 if (args.empty())
return args;
662 if (args.size() >= 2) {
663 const Value &maybeLike = args[args.size() - 2];
664 if ((maybeLike.isChar() || maybeLike.isString())
665 && maybeLike.toString() ==
"like") {
666 outType = args[args.size() - 1].type();
672 const Value &last = args[args.size() - 1];
673 if (last.isChar() || last.isString()) {
675 if (valueTypeFromName(last.toString(), t)) {
687inline Value createMatrixND(
const size_t *dims, std::size_t nDims,
688 ValueType type, std::pmr::memory_resource *mr)
690 const int nd =
static_cast<int>(nDims);
691 if (nd <= 1)
return Value::matrix(nd == 1 ? dims[0] : 0, 1, type, mr);
692 if (nd == 2)
return Value::matrix(dims[0], dims[1], type, mr);
693 if (nd == 3)
return Value::matrix3d(dims[0], dims[1], dims[2], type, mr);
694 return Value::matrixND(dims, nd, type, mr);
702inline T saturateAdd(T a, T b)
704 if constexpr (std::is_floating_point_v<T>) {
706 }
else if constexpr (std::is_unsigned_v<T>) {
708 return (r < a) ? std::numeric_limits<T>::max() : r;
711 W r =
static_cast<W
>(a) +
static_cast<W
>(b);
712 if (r > std::numeric_limits<T>::max())
return std::numeric_limits<T>::max();
713 if (r < std::numeric_limits<T>::min())
return std::numeric_limits<T>::min();
714 return static_cast<T
>(r);
719inline T saturateSub(T a, T b)
721 if constexpr (std::is_floating_point_v<T>) {
723 }
else if constexpr (std::is_unsigned_v<T>) {
724 return (b > a) ? T(0) : a - b;
727 W r =
static_cast<W
>(a) -
static_cast<W
>(b);
728 if (r > std::numeric_limits<T>::max())
return std::numeric_limits<T>::max();
729 if (r < std::numeric_limits<T>::min())
return std::numeric_limits<T>::min();
730 return static_cast<T
>(r);
735inline T saturateMul(T a, T b)
737 if constexpr (std::is_floating_point_v<T>) {
739 }
else if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>) {
740 double r =
static_cast<double>(a) *
static_cast<double>(b);
741 if (r >
static_cast<double>(std::numeric_limits<T>::max()))
return std::numeric_limits<T>::max();
742 if (r <
static_cast<double>(std::numeric_limits<T>::min()))
return std::numeric_limits<T>::min();
746 W r =
static_cast<W
>(a) *
static_cast<W
>(b);
747 if (r > std::numeric_limits<T>::max())
return std::numeric_limits<T>::max();
748 if (r < std::numeric_limits<T>::min())
return std::numeric_limits<T>::min();
749 return static_cast<T
>(r);
754inline T saturateDiv(T a, T b)
756 if constexpr (std::is_floating_point_v<T>) {
760 if constexpr (std::is_unsigned_v<T>)
761 return (a == 0) ? T(0) : std::numeric_limits<T>::
max();
763 return (a > 0) ? std::numeric_limits<T>::max()
764 : (a < 0) ? std::numeric_limits<T>::min()
767 if constexpr (!std::is_unsigned_v<T>) {
768 if (a == std::numeric_limits<T>::min() && b == T(-1))
769 return std::numeric_limits<T>::max();
772 double r =
static_cast<double>(a) /
static_cast<double>(b);
774 if (r >
static_cast<double>(std::numeric_limits<T>::max()))
return std::numeric_limits<T>::max();
775 if (r <
static_cast<double>(std::numeric_limits<T>::min()))
return std::numeric_limits<T>::min();
776 return static_cast<T
>(r);
781inline T saturateNeg(T a)
783 if constexpr (std::is_floating_point_v<T>) {
785 }
else if constexpr (std::is_unsigned_v<T>) {
788 if (a == std::numeric_limits<T>::min())
return std::numeric_limits<T>::max();
797template <
typename T,
typename Op>
798Value elementwiseTyped(
const Value &a,
const Value &b, ValueType targetType, Op op, std::pmr::memory_resource *mr)
800 if (a.isEmpty() || b.isEmpty())
801 return emptyResultForBinary(a, b, targetType, mr);
804 auto readAt = [](
const Value &v, ValueType tgt,
size_t r,
size_t c) -> T {
805 size_t idx = c * v.dims().rows() + r;
806 if (v.type() == tgt)
return static_cast<const T *
>(v.rawData())[idx];
807 double d = v.doubleData()[idx];
808 if constexpr (std::is_integral_v<T>)
809 return static_cast<T
>(std::clamp(std::round(d),
810 static_cast<double>(std::numeric_limits<T>::min()),
811 static_cast<double>(std::numeric_limits<T>::max())));
813 return static_cast<T
>(d);
815 auto readLinearTyped = [](
const Value &v, ValueType tgt,
size_t i) -> T {
816 if (v.type() == tgt)
return static_cast<const T *
>(v.rawData())[i];
817 double d = v.elemAsDouble(i);
818 if constexpr (std::is_integral_v<T>)
819 return static_cast<T
>(std::clamp(std::round(d),
820 static_cast<double>(std::numeric_limits<T>::min()),
821 static_cast<double>(std::numeric_limits<T>::max())));
823 return static_cast<T
>(d);
827 if (a.dims().ndim() >= 4 || b.dims().ndim() >= 4) {
829 auto r = createLike(b, targetType, mr);
830 T *
dst =
static_cast<T *
>(r.rawDataMut());
831 T sa = readLinearTyped(a, targetType, 0);
832 for (
size_t i = 0; i < b.numel(); ++i)
833 dst[i] = op(sa, readLinearTyped(b, targetType, i));
837 auto r = createLike(a, targetType, mr);
838 T *
dst =
static_cast<T *
>(r.rawDataMut());
839 T sb = readLinearTyped(b, targetType, 0);
840 for (
size_t i = 0; i < a.numel(); ++i)
841 dst[i] = op(readLinearTyped(a, targetType, i), sb);
845 if (!broadcastDimsND(a.dims(), b.dims(), outD))
846 throw std::runtime_error(
"ND dimensions must broadcast: each axis must match or be 1");
847 auto r = createForDims(outD, targetType, mr);
848 T *
dst =
static_cast<T *
>(r.rawDataMut());
849 forEachNDPair(a.dims(), b.dims(), outD,
850 [&](
size_t outIdx,
size_t aOff,
size_t bOff) {
851 dst[outIdx] = op(readLinearTyped(a, targetType, aOff),
852 readLinearTyped(b, targetType, bOff));
860 if (a.dims().is3D() || b.dims().is3D()) {
862 auto r = createLike(b, targetType, mr);
863 T *
dst =
static_cast<T *
>(r.rawDataMut());
864 T sa = readLinearTyped(a, targetType, 0);
865 for (
size_t i = 0; i < b.numel(); ++i)
866 dst[i] = op(sa, readLinearTyped(b, targetType, i));
870 auto r = createLike(a, targetType, mr);
871 T *
dst =
static_cast<T *
>(r.rawDataMut());
872 T sb = readLinearTyped(b, targetType, 0);
873 for (
size_t i = 0; i < a.numel(); ++i)
874 dst[i] = op(readLinearTyped(a, targetType, i), sb);
877 const size_t aR = a.dims().rows(), aC = a.dims().cols();
878 const size_t aP = a.dims().is3D() ? a.dims().pages() : 1;
879 const size_t bR = b.dims().rows(), bC = b.dims().cols();
880 const size_t bP = b.dims().is3D() ? b.dims().pages() : 1;
881 size_t outR, outC, outP;
882 if (!broadcastDims3D(aR, aC, aP, bR, bC, bP, outR, outC, outP))
883 throw std::runtime_error(
884 "3D dimensions must broadcast: each axis must match or be 1");
886 if (aR == bR && aC == bC && aP == bP) {
887 auto r = createLike(a, targetType, mr);
888 T *
dst =
static_cast<T *
>(r.rawDataMut());
889 for (
size_t i = 0; i < a.numel(); ++i)
890 dst[i] = op(readLinearTyped(a, targetType, i),
891 readLinearTyped(b, targetType, i));
894 auto r = (outP > 1) ? Value::matrix3d(outR, outC, outP, targetType, mr)
895 : Value::matrix(outR, outC, targetType, mr);
896 T *
dst =
static_cast<T *
>(r.rawDataMut());
897 for (
size_t pp = 0; pp < outP; ++pp)
898 for (
size_t cc = 0; cc < outC; ++cc)
899 for (
size_t rr = 0; rr < outR; ++rr) {
900 const size_t aOff = broadcastOffset3D(rr, cc, pp, aR, aC, aP);
901 const size_t bOff = broadcastOffset3D(rr, cc, pp, bR, bC, bP);
902 dst[pp * outR * outC + cc * outR + rr] =
903 op(readLinearTyped(a, targetType, aOff),
904 readLinearTyped(b, targetType, bOff));
909 size_t ar = a.dims().rows(), ac = a.dims().cols();
910 size_t br = b.dims().rows(), bc = b.dims().cols();
912 if (!broadcastDims(ar, ac, br, bc, outR, outC))
913 throw std::runtime_error(
"Matrix dimensions must agree. a=[" + std::to_string(ar) +
"x" + std::to_string(ac) +
"], b=[" + std::to_string(br) +
"x" + std::to_string(bc) +
"] (line 913)");
915 auto r = Value::matrix(outR, outC, targetType, mr);
916 T *
dst =
static_cast<T *
>(r.rawDataMut());
917 for (
size_t c = 0; c < outC; ++c) {
918 size_t ca = (ac == 1) ? 0 : c;
919 size_t cb = (bc == 1) ? 0 : c;
920 for (
size_t row = 0; row < outR; ++row) {
921 size_t ra = (ar == 1) ? 0 : row;
922 size_t rb = (br == 1) ? 0 : row;
923 dst[c * outR + row] = op(readAt(a, targetType, ra, ca),
924 readAt(b, targetType, rb, cb));
936inline ValueType resolveIntegerPairType(
const Value &a,
const Value &b)
938 ValueType ta = a.type(), tb = b.type();
939 bool ai = isIntegerType(ta), bi = isIntegerType(tb);
940 bool af = isFloatType(ta), bf = isFloatType(tb);
941 bool aS = (ta == ValueType::SINGLE), bS = (tb == ValueType::SINGLE);
945 throw std::runtime_error(
"Integers can only be combined with integers of the same class");
948 if (ai && (bf || tb == ValueType::LOGICAL))
return ta;
949 if (bi && (af || ta == ValueType::LOGICAL))
return tb;
950 if (aS || bS)
return ValueType::SINGLE;
951 return ValueType::EMPTY;
954template <
typename Op>
955Value dispatchIntegerBinaryOp(
const Value &a,
const Value &b, Op op, std::pmr::memory_resource *mr)
957 ValueType target = resolveIntegerPairType(a, b);
958 if (target == ValueType::EMPTY)
return Value();
961 case ValueType::INT8:
return elementwiseTyped<int8_t>(a, b, target, [&](int8_t x, int8_t y) {
return op(x, y); }, mr);
962 case ValueType::INT16:
return elementwiseTyped<int16_t>(a, b, target, [&](int16_t x, int16_t y) {
return op(x, y); }, mr);
963 case ValueType::INT32:
return elementwiseTyped<int32_t>(a, b, target, [&](int32_t x, int32_t y) {
return op(x, y); }, mr);
964 case ValueType::INT64:
return elementwiseTyped<int64_t>(a, b, target, [&](int64_t x, int64_t y) {
return op(x, y); }, mr);
965 case ValueType::UINT8:
return elementwiseTyped<uint8_t>(a, b, target, [&](uint8_t x, uint8_t y) {
return op(x, y); }, mr);
966 case ValueType::UINT16:
return elementwiseTyped<uint16_t>(a, b, target, [&](uint16_t x, uint16_t y) {
return op(x, y); }, mr);
967 case ValueType::UINT32:
return elementwiseTyped<uint32_t>(a, b, target, [&](uint32_t x, uint32_t y) {
return op(x, y); }, mr);
968 case ValueType::UINT64:
return elementwiseTyped<uint64_t>(a, b, target, [&](uint64_t x, uint64_t y) {
return op(x, y); }, mr);
969 case ValueType::SINGLE:
return elementwiseTyped<float>(a, b, target, [&](
float x,
float y) {
return op(x, y); }, mr);
970 default:
return Value();
978template <
typename T,
typename Op>
979Value unaryTyped(
const Value &a, ValueType targetType, Op op, std::pmr::memory_resource *mr)
981 auto r = createLike(a, targetType, mr);
982 const T *src =
static_cast<const T *
>(a.rawData());
983 T *
dst =
static_cast<T *
>(r.rawDataMut());
984 for (
size_t i = 0; i < a.numel(); ++i)
Core MATLAB-compatible multidimensional array and scalar value type.
std::tuple< Value, Value > max(const Value &x, std::pmr::memory_resource *mr=nullptr)
Largest elements in array.
Value dst(const Value &x, std::pmr::memory_resource *mr=nullptr)
Type-II Discrete Sine Transform.
Value db(const Value &x, const std::string &signalType="voltage", std::pmr::memory_resource *mr=nullptr)
Convert magnitude or power to decibels.