28 if (b && a > std::numeric_limits<size_t>::max() / b)
29 throw std::length_error(
"Tensor dimensions overflow size_t");
38 explicit TensorAxisMask(
size_t rank) : extended(rank > local.size() ? rank : 0, 0)
44 return extended.empty() ? local[i] : extended[i];
49 return extended.empty() ? local[i] : extended[i];
53 std::array<unsigned char, 32> local{};
54 std::vector<unsigned char> extended;
65 std::vector<TensorAxis>
axes;
68 void Add(
size_t extent,
size_t stride)
76 axes.push_back({extent, stride});
81 return axes.size() <= 1;
86 return axes.empty() ? 0 :
axes[0].stride;
106 offsets.resize(pattern.
count);
108 for (
const auto &axis : pattern.
axes)
110 for (size_t x = 1; x < axis.extent; ++x)
111 for (size_t i = 0; i < n; ++i)
112 offsets[i + x * n] = offsets[i] + x * axis.stride;
119 return offsets.empty() ? i * stride : offsets[i];
124 return offsets.capacity() *
sizeof(size_t);
129 std::vector<size_t> offsets;
132template <
class Pairs>
136 for (
const auto &pair : pairs)
138 if (pair.first >= da.size() || pair.second >= db.size())
139 throw std::invalid_argument(
"Contraction axis is out of range");
140 if (usedA[pair.first] || usedB[pair.second])
141 throw std::invalid_argument(
"Contraction axes must be unique");
142 if (da[pair.first] != db[pair.second])
143 throw std::invalid_argument(
"Contracted dimensions must match");
144 usedA[pair.first] = usedB[pair.second] = 1;
146 std::vector<size_t> dimensions;
147 dimensions.reserve(da.size() + db.size() - 2 * pairs.size());
148 for (
size_t i = 0; i < da.size(); ++i)
150 dimensions.push_back(da[i]);
151 for (
size_t i = 0; i < db.size(); ++i)
153 dimensions.push_back(db[i]);
154 if (dimensions.empty())
155 dimensions.push_back(1);
159template <
class Pairs>
163 std::vector<size_t> sa(da.size()), sb(db.size());
165 for (
size_t i = 0; i < da.size(); ++i)
170 fa.
Add(da[i], sa[i]);
173 for (
size_t i = 0; i < db.size(); ++i)
178 fb.
Add(db[i], sb[i]);
181 std::vector<std::pair<size_t, size_t>> ordered(pairs.begin(), pairs.end());
182 std::sort(ordered.begin(), ordered.end());
183 for (
const auto &pair : ordered)
185 ka.
Add(da[pair.first], sa[pair.first]);
186 kb.
Add(db[pair.second], sb[pair.second]);
192inline int TensorGateSize(
const std::vector<size_t> &da,
const std::vector<size_t> &db,
size_t pairs)
194 if (pairs != 1 && pairs != 2)
196 auto isGate = [pairs](
const auto &dims) {
return dims.size() == 2 * pairs && std::all_of(dims.begin(), dims.end(), [](
size_t d) { return d == 2; }); };
197 if (da.size() > 4 && isGate(db))
199 if (db.size() > 4 && isGate(da))
200 return -(1 << pairs);
205inline bool TensorAddBytes(
size_t &used,
size_t count,
size_t elementSize,
size_t limit)
207 if (used > limit || count > (limit - used) / elementSize)
209 used += count * elementSize;
220 for (
size_t i = 0; i < indices.size(); ++i)
222 indices[i] = linear % pattern.axes[i].extent;
223 linear /= pattern.axes[i].extent;
224 offset += indices[i] * pattern.axes[i].stride;
230 for (
size_t i = 0; i < indices.size(); ++i)
232 const auto &axis = pattern.axes[i];
233 if (++indices[i] < axis.extent)
239 offset -= (axis.extent - 1) * axis.stride;
247 std::vector<size_t> indices;
253 if (allow && !omp_in_parallel())
257 const double useful = std::min(
static_cast<double>(jobs), std::max(1.0, work / 65536.0));
258 return std::max(1,
static_cast<int>(std::min<double>(omp_get_max_threads(), useful)));
273#pragma omp parallel num_threads(threads)
275 const size_t count =
static_cast<size_t>(omp_get_num_threads());
276 const size_t tid =
static_cast<size_t>(omp_get_thread_num());
277 const size_t block = jobs / count;
278 const size_t extra = jobs % count;
279 const size_t begin = tid * block + std::min(tid, extra);
280 function(begin, begin + block + (tid < extra ? 1 : 0), tid);
287 function(0, jobs, 0);
290template <
class T,
class Storage>
292 (std::is_same_v<T, float> || std::is_same_v<T, double> || std::is_same_v<T, std::complex<float>> ||
293 std::is_same_v<T, std::complex<double>>)&&(std::is_same_v<Storage, std::valarray<T>> || std::is_same_v<Storage, std::vector<T>> ||
294 std::is_same_v<Storage, const T *> || std::is_same_v<Storage, T *>);
296template <
class T,
class Storage>
300 auto reduce = [&](
size_t begin,
size_t end) -> T {
305 using Vector = Eigen::Matrix<T, Eigen::Dynamic, 1>;
306 const Eigen::Map<const Vector> av(&a[begin], end - begin);
307 const Eigen::Map<const Vector> bv(&b[begin], end - begin);
309 return av.cwiseProduct(bv).sum();
314 for (
size_t k = begin; k < end; ++k)
316 sum = sum + a[ai.offset] * b[bi.
offset];
323 return reduce(0, ka.
count);
324 std::vector<T> partials(threads, T{});
325 TensorParallelRanges(ka.
count, threads, [&](
size_t begin,
size_t end,
size_t tid) { partials[tid] = reduce(begin, end); });
327 for (
const auto &partial : partials)
332template <
class T,
class Storage,
class Pairs>
333bool TensorSmallContraction(
const Storage &a,
const Storage &b, Storage &out,
const std::vector<size_t> &da,
const std::vector<size_t> &db,
336 if (da.size() > 4 || db.size() > 4)
344 std::array<size_t, 4> sa{}, sb{};
346 for (
size_t i = 0; i < da.size(); ++i)
352 for (
size_t i = 0; i < db.size(); ++i)
357 std::array<size_t, 16> fa{}, fb{}, ka{}, kb{};
358 auto append = [](
auto &offsets,
size_t &count,
size_t d,
size_t s) {
360 for (
size_t i = 0; i < count; ++i)
361 offsets[i + count] = offsets[i] + s;
364 size_t m = 1, n = 1, k = 1, kbCount = 1;
365 for (
size_t i = 0; i < da.size(); ++i)
367 append(fa, m, da[i], sa[i]);
368 for (
size_t i = 0; i < db.size(); ++i)
370 append(fb, n, db[i], sb[i]);
371 for (
const auto &pair : pairs)
373 append(ka, k, da[pair.first], sa[pair.first]);
374 append(kb, kbCount, db[pair.second], sb[pair.second]);
376 for (
size_t j = 0; j < n; ++j)
377 for (
size_t i = 0; i < m; ++i)
380 for (
size_t x = 0; x < k; ++x)
381 sum = sum + a[fa[i] + ka[x]] * b[fb[j] + kb[x]];
382 out[i + m * j] = sum;
387template <
size_t G,
class T,
size_t... X> T
TensorGateProduct(
const std::array<T, G> &values,
const T *coefficients, std::index_sequence<X...>)
389 return ((values[X] * coefficients[X]) + ...);
392template <
size_t G,
bool SmallLeft,
class T,
class Input,
class Output>
396 const auto &big = SmallLeft ? b : a;
397 const auto &gate = SmallLeft ? a : b;
398 const auto &free = SmallLeft ? bf : af;
399 const auto &contract = SmallLeft ? bc : ac;
400 const auto &gateFree = SmallLeft ? af : bf;
401 const auto &gateContract = SmallLeft ? ac : bc;
402 std::array<T, G * G> coefficients;
403 std::array<size_t, G> offsets;
404 for (
size_t x = 0; x < G; ++x)
406 offsets[x] = contract[x];
407 for (
size_t j = 0; j < G; ++j)
408 coefficients[j * G + x] = gate[gateFree[j] + gateContract[x]];
412 for (
size_t i = begin; i < end; ++i)
414 const size_t base = free[i];
415 std::array<T, G> values;
416 for (
size_t x = 0; x < G; ++x)
417 values[x] = big[base + offsets[x]];
418 for (
size_t j = 0; j < G; ++j)
419 out[SmallLeft ? j + G * i : i + count * j] =
TensorGateProduct<G>(values, coefficients.data() + j * G, std::make_index_sequence<G>{});
425template <
class T,
class Input,
class Output>
431 if (m == 1 && n == 1)
436 const double work = double(m) * double(n) * double(k);
442 TensorGateContraction<2, false, T>(a, b, out, m, af, bf, ac, bc, allow);
445 TensorGateContraction<4, false, T>(a, b, out, m, af, bf, ac, bc, allow);
448 TensorGateContraction<2, true, T>(a, b, out, n, af, bf, ac, bc, allow);
451 TensorGateContraction<4, true, T>(a, b, out, n, af, bf, ac, bc, allow);
457 if (m >= 8 && n >= 8 && k >= 8)
459 using Matrix = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;
460 const T *ap = &a[0], *bp = &b[0];
461 Matrix packedA, packedB;
469 packedA.resize(m, k);
470 packed = packedA.data();
472 for (
size_t x = 0; x < k; ++x)
473 for (
size_t i = 0; i < m; ++i)
474 packed[i + m * x] = a[af[i] + ac[x]];
482 packedB.resize(k, n);
483 packed = packedB.data();
485 for (
size_t j = 0; j < n; ++j)
486 for (
size_t x = 0; x < k; ++x)
487 packed[x + k * j] = b[bf[j] + bc[x]];
492 constexpr size_t rowsPerTile = 32, colsPerTile = 128;
493 using Tile = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor, 32, Eigen::Dynamic>;
494 const size_t rowTiles = (m - 1) / rowsPerTile + 1;
495 const size_t colTiles = (n - 1) / colsPerTile + 1;
499 for (
size_t job = begin; job < end; ++job)
501 const size_t row = (job % rowTiles) * rowsPerTile;
502 const size_t col = (job / rowTiles) * colsPerTile;
503 const size_t rows = std::min(rowsPerTile, m - row);
504 const size_t cols = std::min(colsPerTile, n - col);
505 const Eigen::Map<const Matrix, 0, Eigen::OuterStride<>> left(ap + row, rows, k, Eigen::OuterStride<>(m));
506 const Eigen::Map<const Matrix> right(bp + k * col, k, cols);
507 Eigen::Map<Tile, 0, Eigen::OuterStride<>> dest(&out[row + m * col], rows, cols, Eigen::OuterStride<>(m));
508#if defined(EIGEN_USE_BLAS) || defined(EIGEN_GEMM_THREADPOOL)
511 dest.noalias() = left.lazyProduct(right);
513 dest.noalias() = left * right;
523 size_t i = begin % m, j = begin / m;
524 for (
size_t offset = begin; offset < end; ++offset)
526 const size_t baseA = af[i], baseB = bf[j];
528 for (
size_t x = 0; x < k; ++x)
529 sum = sum + a[baseA + ac[x]] * b[baseB + bc[x]];
540template <
class T,
class Storage>
550 TensorExecuteContraction<T>(a, b, out, fa, fb, ka, kb, af, bf, ac, bc, gateSize, allow);
558 template <
class Pairs>
bool Prepare(
const std::vector<size_t> &da,
const std::vector<size_t> &db,
const Pairs &pairs,
size_t byteLimit)
561 if (!candidate.Initialize(da, db, pairs, byteLimit))
563 *
this = std::move(candidate);
580 const size_t m = fa.count, n = fb.count, k = ka.count;
581 if (gateSize || m < 8 || n < 8 || k < 8)
584 const size_t limit = std::numeric_limits<size_t>::max();
593 return dimensions.capacity() *
sizeof(size_t) +
594 (fa.axes.capacity() + fb.axes.capacity() + ka.axes.capacity() + kb.axes.capacity()) *
sizeof(
TensorAxis) + af.Bytes() + bf.Bytes() + ac.Bytes() +
600 template <
class T>
void Execute(
const T *a,
const T *b, T *out,
bool allow, T *packing =
nullptr)
const
602 TensorExecuteContraction<T>(a, b, out, fa, fb, ka, kb, af, bf, ac, bc, gateSize, allow, packing);
606 template <
class Pairs>
bool Initialize(
const std::vector<size_t> &da,
const std::vector<size_t> &db,
const Pairs &pairs,
size_t byteLimit)
610 if (std::find(da.begin(), da.end(), 0) != da.end() || std::find(db.begin(), db.end(), 0) != db.end())
616 if (bytes > byteLimit)
621 for (
const auto *pattern : {&fa, &fb, &ka, &kb})
622 if (!pattern->Linear() && !
TensorAddBytes(bytes, pattern->count,
sizeof(
size_t), byteLimit))
624 af = TensorAxisOffsets(fa);
625 bf = TensorAxisOffsets(fb);
626 ac = TensorAxisOffsets(ka);
627 bc = TensorAxisOffsets(kb);
631 std::vector<size_t> dimensions;
632 TensorAxisPattern fa, fb, ka, kb;
633 TensorAxisOffsets af, bf, ac, bc;
void Output(const json::object &result, bool framed)
TensorAxisCursor(const TensorAxisPattern &pattern, size_t linear)
TensorAxisMask(size_t rank)
unsigned char operator[](size_t i) const
unsigned char & operator[](size_t i)
TensorAxisOffsets(const TensorAxisPattern &pattern)
size_t operator[](size_t i) const
TensorAxisOffsets()=default
size_t ExtraBytes() const
bool Prepare(const std::vector< size_t > &da, const std::vector< size_t > &db, const Pairs &pairs, size_t byteLimit)
const std::vector< size_t > & GetDims() const
void Execute(const T *a, const T *b, T *out, bool allow, T *packing=nullptr) const
size_t GetPackingSize() const
bool TensorMatrixContiguous(const TensorAxisPattern &rows, const TensorAxisPattern &columns)
void TensorGateContraction(const Input &a, const Input &b, Output &out, size_t count, const TensorAxisOffsets &af, const TensorAxisOffsets &bf, const TensorAxisOffsets &ac, const TensorAxisOffsets &bc, bool allow)
T TensorGateProduct(const std::array< T, G > &values, const T *coefficients, std::index_sequence< X... >)
void TensorContractionPatterns(const std::vector< size_t > &da, const std::vector< size_t > &db, const TensorAxisMask &usedA, const TensorAxisMask &usedB, const Pairs &pairs, TensorAxisPattern &fa, TensorAxisPattern &fb, TensorAxisPattern &ka, TensorAxisPattern &kb)
int TensorGateSize(const std::vector< size_t > &da, const std::vector< size_t > &db, size_t pairs)
bool TensorAddBytes(size_t &used, size_t count, size_t elementSize, size_t limit)
bool TensorSmallContraction(const Storage &a, const Storage &b, Storage &out, const std::vector< size_t > &da, const std::vector< size_t > &db, const TensorAxisMask &usedA, const TensorAxisMask &usedB, const Pairs &pairs)
std::vector< size_t > TensorContractionDimensions(const std::vector< size_t > &da, const std::vector< size_t > &db, const Pairs &pairs, TensorAxisMask &usedA, TensorAxisMask &usedB)
size_t TensorSizeProduct(size_t a, size_t b)
T TensorScalarContraction(const Storage &a, const Storage &b, const TensorAxisPattern &ka, const TensorAxisPattern &kb, bool allow)
void TensorParallelRanges(size_t jobs, int threads, const Function &function)
void TensorDenseContraction(const Storage &a, const Storage &b, Storage &out, const TensorAxisPattern &fa, const TensorAxisPattern &fb, const TensorAxisPattern &ka, const TensorAxisPattern &kb, bool allow, int gateSize=0)
constexpr bool TensorEigenStorage
int TensorContractionThreads(size_t jobs, double work, bool allow)
void TensorExecuteContraction(const Input &a, const Input &b, Output &out, const TensorAxisPattern &fa, const TensorAxisPattern &fb, const TensorAxisPattern &ka, const TensorAxisPattern &kb, const TensorAxisOffsets &af, const TensorAxisOffsets &bf, const TensorAxisOffsets &ac, const TensorAxisOffsets &bc, int gateSize, bool allow, T *packing=nullptr)
Portable dense contraction kernels.
std::vector< TensorAxis > axes
void Add(size_t extent, size_t stride)