Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
TensorContraction.h
Go to the documentation of this file.
1
3#pragma once
4
5#include <algorithm>
6#include <array>
7#include <complex>
8#include <cstddef>
9#include <limits>
10#include <stdexcept>
11#include <type_traits>
12#include <utility>
13#include <valarray>
14#include <vector>
15
16#include <Eigen/Core>
17#ifdef _OPENMP
18#include <omp.h>
19#endif
20
21namespace Utils
22{
23namespace detail
24{
25
26inline size_t TensorSizeProduct(size_t a, size_t b)
27{
28 if (b && a > std::numeric_limits<size_t>::max() / b)
29 throw std::length_error("Tensor dimensions overflow size_t");
30 return a * b;
31}
32
33// Most circuit tensors fit on the stack, including the bookkeeping for their
34// free axes. Retain arbitrary runtime rank for circuit-cutting callers.
36{
37 public:
38 explicit TensorAxisMask(size_t rank) : extended(rank > local.size() ? rank : 0, 0)
39 {
40 }
41
42 unsigned char &operator[](size_t i)
43 {
44 return extended.empty() ? local[i] : extended[i];
45 }
46
47 unsigned char operator[](size_t i) const
48 {
49 return extended.empty() ? local[i] : extended[i];
50 }
51
52 private:
53 std::array<unsigned char, 32> local{};
54 std::vector<unsigned char> extended;
55};
56
58{
59 size_t extent;
60 size_t stride;
61};
62
64{
65 std::vector<TensorAxis> axes;
66 size_t count = 1;
67
68 void Add(size_t extent, size_t stride)
69 {
70 count = TensorSizeProduct(count, extent);
71 if (extent == 1)
72 return;
73 if (!axes.empty() && stride == TensorSizeProduct(axes.back().extent, axes.back().stride))
74 axes.back().extent = TensorSizeProduct(axes.back().extent, extent);
75 else
76 axes.push_back({extent, stride});
77 }
78
79 bool Linear() const
80 {
81 return axes.size() <= 1;
82 }
83
84 size_t Stride() const
85 {
86 return axes.empty() ? 0 : axes[0].stride;
87 }
88};
89
90inline bool TensorMatrixContiguous(const TensorAxisPattern &rows, const TensorAxisPattern &columns)
91{
92 return rows.Linear() && rows.Stride() == 1 && columns.Linear() && columns.Stride() == rows.count;
93}
94
95// Irregular axes are decoded once, not in each multiply-add. Linear patterns
96// (including ordinary matrix layouts) require no offset table.
98{
99 public:
100 TensorAxisOffsets() = default;
101
102 explicit TensorAxisOffsets(const TensorAxisPattern &pattern) : stride(pattern.Stride())
103 {
104 if (pattern.Linear())
105 return;
106 offsets.resize(pattern.count);
107 size_t n = 1;
108 for (const auto &axis : pattern.axes)
109 {
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;
113 n *= axis.extent;
114 }
115 }
116
117 size_t operator[](size_t i) const
118 {
119 return offsets.empty() ? i * stride : offsets[i];
120 }
121
122 size_t Bytes() const
123 {
124 return offsets.capacity() * sizeof(size_t);
125 }
126
127 private:
128 size_t stride = 0;
129 std::vector<size_t> offsets;
130};
131
132template <class Pairs>
133std::vector<size_t> TensorContractionDimensions(const std::vector<size_t> &da, const std::vector<size_t> &db, const Pairs &pairs, TensorAxisMask &usedA,
134 TensorAxisMask &usedB)
135{
136 for (const auto &pair : pairs)
137 {
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;
145 }
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)
149 if (!usedA[i])
150 dimensions.push_back(da[i]);
151 for (size_t i = 0; i < db.size(); ++i)
152 if (!usedB[i])
153 dimensions.push_back(db[i]);
154 if (dimensions.empty())
155 dimensions.push_back(1);
156 return dimensions;
157}
158
159template <class Pairs>
160void TensorContractionPatterns(const std::vector<size_t> &da, const std::vector<size_t> &db, const TensorAxisMask &usedA, const TensorAxisMask &usedB,
161 const Pairs &pairs, TensorAxisPattern &fa, TensorAxisPattern &fb, TensorAxisPattern &ka, TensorAxisPattern &kb)
162{
163 std::vector<size_t> sa(da.size()), sb(db.size());
164 size_t stride = 1;
165 for (size_t i = 0; i < da.size(); ++i)
166 {
167 sa[i] = stride;
168 stride = TensorSizeProduct(stride, da[i]);
169 if (!usedA[i])
170 fa.Add(da[i], sa[i]);
171 }
172 stride = 1;
173 for (size_t i = 0; i < db.size(); ++i)
174 {
175 sb[i] = stride;
176 stride = TensorSizeProduct(stride, db[i]);
177 if (!usedB[i])
178 fb.Add(db[i], sb[i]);
179 }
180 // Favor contiguous left-hand reduction axes, preserving the axis pairing.
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)
184 {
185 ka.Add(da[pair.first], sa[pair.first]);
186 kb.Add(db[pair.second], sb[pair.second]);
187 }
188}
189
190// Positive sizes denote a gate on the right, negative sizes one on the left.
191// The existing stack kernel continues to handle two small binary tensors.
192inline int TensorGateSize(const std::vector<size_t> &da, const std::vector<size_t> &db, size_t pairs)
193{
194 if (pairs != 1 && pairs != 2)
195 return 0;
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))
198 return 1 << pairs;
199 if (db.size() > 4 && isGate(da))
200 return -(1 << pairs);
201 return 0;
202}
203
204// Subtraction/division checks prevent overflow before optional allocations.
205inline bool TensorAddBytes(size_t &used, size_t count, size_t elementSize, size_t limit)
206{
207 if (used > limit || count > (limit - used) / elementSize)
208 return false;
209 used += count * elementSize;
210 return true;
211}
212
213// A full reduction can be as large as both inputs. An odometer avoids adding
214// input-sized index arrays just to compute one scalar.
216{
217 public:
218 TensorAxisCursor(const TensorAxisPattern &pattern, size_t linear) : pattern(pattern), indices(pattern.axes.size(), 0)
219 {
220 for (size_t i = 0; i < indices.size(); ++i)
221 {
222 indices[i] = linear % pattern.axes[i].extent;
223 linear /= pattern.axes[i].extent;
224 offset += indices[i] * pattern.axes[i].stride;
225 }
226 }
227
228 void Next()
229 {
230 for (size_t i = 0; i < indices.size(); ++i)
231 {
232 const auto &axis = pattern.axes[i];
233 if (++indices[i] < axis.extent)
234 {
235 offset += axis.stride;
236 return;
237 }
238 indices[i] = 0;
239 offset -= (axis.extent - 1) * axis.stride;
240 }
241 }
242
243 size_t offset = 0;
244
245 private:
246 const TensorAxisPattern &pattern;
247 std::vector<size_t> indices;
248};
249
250inline int TensorContractionThreads(size_t jobs, double work, bool allow)
251{
252#ifdef _OPENMP
253 if (allow && !omp_in_parallel())
254 {
255 // Amortize a team over arithmetic work, rather than output size alone.
256 // Respect OMP_NUM_THREADS / omp_set_num_threads and limit useful workers.
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)));
259 }
260#else
261 (void)jobs;
262 (void)work;
263 (void)allow;
264#endif
265 return 1;
266}
267
268template <class Function> void TensorParallelRanges(size_t jobs, int threads, const Function &function)
269{
270#ifdef _OPENMP
271 if (threads > 1)
272 {
273#pragma omp parallel num_threads(threads)
274 {
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);
281 }
282 return;
283 }
284#else
285 (void)threads;
286#endif
287 function(0, jobs, 0);
288}
289
290template <class T, class Storage>
291constexpr bool TensorEigenStorage =
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 *>);
295
296template <class T, class Storage>
297T TensorScalarContraction(const Storage &a, const Storage &b, const TensorAxisPattern &ka, const TensorAxisPattern &kb, bool allow)
298{
299 const int threads = TensorContractionThreads(ka.count, double(ka.count), allow);
300 auto reduce = [&](size_t begin, size_t end) -> T {
301 if constexpr (TensorEigenStorage<T, Storage>)
302 {
303 if (ka.Linear() && kb.Linear() && ka.Stride() == 1 && kb.Stride() == 1)
304 {
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);
308 // dot()/adjoint() conjugate complex values; contraction does not.
309 return av.cwiseProduct(bv).sum();
310 }
311 }
312 TensorAxisCursor ai(ka, begin), bi(kb, begin);
313 T sum{};
314 for (size_t k = begin; k < end; ++k)
315 {
316 sum = sum + a[ai.offset] * b[bi.offset];
317 ai.Next();
318 bi.Next();
319 }
320 return sum;
321 };
322 if (threads == 1)
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); });
326 T sum{};
327 for (const auto &partial : partials)
328 sum = sum + partial;
329 return sum;
330}
331
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,
334 const TensorAxisMask &usedA, const TensorAxisMask &usedB, const Pairs &pairs)
335{
336 if (da.size() > 4 || db.size() > 4)
337 return false;
338 for (auto d : da)
339 if (d == 0 || d > 2)
340 return false;
341 for (auto d : db)
342 if (d == 0 || d > 2)
343 return false;
344 std::array<size_t, 4> sa{}, sb{};
345 size_t stride = 1;
346 for (size_t i = 0; i < da.size(); ++i)
347 {
348 sa[i] = stride;
349 stride *= da[i];
350 }
351 stride = 1;
352 for (size_t i = 0; i < db.size(); ++i)
353 {
354 sb[i] = stride;
355 stride *= db[i];
356 }
357 std::array<size_t, 16> fa{}, fb{}, ka{}, kb{};
358 auto append = [](auto &offsets, size_t &count, size_t d, size_t s) {
359 if (d == 2)
360 for (size_t i = 0; i < count; ++i)
361 offsets[i + count] = offsets[i] + s;
362 count *= d;
363 };
364 size_t m = 1, n = 1, k = 1, kbCount = 1;
365 for (size_t i = 0; i < da.size(); ++i)
366 if (!usedA[i])
367 append(fa, m, da[i], sa[i]);
368 for (size_t i = 0; i < db.size(); ++i)
369 if (!usedB[i])
370 append(fb, n, db[i], sb[i]);
371 for (const auto &pair : pairs)
372 {
373 append(ka, k, da[pair.first], sa[pair.first]);
374 append(kb, kbCount, db[pair.second], sb[pair.second]);
375 }
376 for (size_t j = 0; j < n; ++j)
377 for (size_t i = 0; i < m; ++i)
378 {
379 T sum{};
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;
383 }
384 return true;
385}
386
387template <size_t G, class T, size_t... X> T TensorGateProduct(const std::array<T, G> &values, const T *coefficients, std::index_sequence<X...>)
388{
389 return ((values[X] * coefficients[X]) + ...);
390}
391
392template <size_t G, bool SmallLeft, class T, class Input, class Output>
393void TensorGateContraction(const Input &a, const Input &b, Output &out, size_t count, const TensorAxisOffsets &af, const TensorAxisOffsets &bf,
394 const TensorAxisOffsets &ac, const TensorAxisOffsets &bc, bool allow)
395{
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)
405 {
406 offsets[x] = contract[x];
407 for (size_t j = 0; j < G; ++j)
408 coefficients[j * G + x] = gate[gateFree[j] + gateContract[x]];
409 }
410 const int threads = TensorContractionThreads(count, double(count) * G * G, allow);
411 TensorParallelRanges(count, threads, [&](size_t begin, size_t end, size_t) {
412 for (size_t i = begin; i < end; ++i)
413 {
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>{});
420 }
421 });
422}
423
424// Shared execution for one-off contractions and prepared network plans.
425template <class T, class Input, class Output>
426void TensorExecuteContraction(const Input &a, const Input &b, Output &out, const TensorAxisPattern &fa, const TensorAxisPattern &fb,
427 const TensorAxisPattern &ka, const TensorAxisPattern &kb, const TensorAxisOffsets &af, const TensorAxisOffsets &bf,
428 const TensorAxisOffsets &ac, const TensorAxisOffsets &bc, int gateSize, bool allow, T *packing = nullptr)
429{
430 const size_t m = fa.count, n = fb.count, k = ka.count;
431 if (m == 1 && n == 1)
432 {
433 out[0] = TensorScalarContraction<T>(a, b, ka, kb, allow);
434 return;
435 }
436 const double work = double(m) * double(n) * double(k);
438 {
439 switch (gateSize)
440 {
441 case 2:
442 TensorGateContraction<2, false, T>(a, b, out, m, af, bf, ac, bc, allow);
443 return;
444 case 4:
445 TensorGateContraction<4, false, T>(a, b, out, m, af, bf, ac, bc, allow);
446 return;
447 case -2:
448 TensorGateContraction<2, true, T>(a, b, out, n, af, bf, ac, bc, allow);
449 return;
450 case -4:
451 TensorGateContraction<4, true, T>(a, b, out, n, af, bf, ac, bc, allow);
452 return;
453 default:
454 break;
455 }
456 // Small/skinny products have too little reuse to amortize GEMM packing.
457 if (m >= 8 && n >= 8 && k >= 8)
458 {
459 using Matrix = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;
460 const T *ap = &a[0], *bp = &b[0];
461 Matrix packedA, packedB;
462 if (!TensorMatrixContiguous(fa, ka))
463 {
464 T *packed = packing;
465 if (packed)
466 packing += m * k;
467 else
468 {
469 packedA.resize(m, k);
470 packed = packedA.data();
471 }
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]];
475 ap = packed;
476 }
477 if (!TensorMatrixContiguous(kb, fb))
478 {
479 T *packed = packing;
480 if (!packed)
481 {
482 packedB.resize(k, n);
483 packed = packedB.data();
484 }
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]];
488 bp = packed;
489 }
490 // Bounded row tiles keep Eigen's GEMM serial. Our outer work partition
491 // owns parallelism, without changing process-wide Eigen thread settings.
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;
496 const size_t jobs = TensorSizeProduct(rowTiles, colTiles);
497 const int threads = TensorContractionThreads(jobs, work, allow);
498 TensorParallelRanges(jobs, threads, [&](size_t begin, size_t end, size_t) {
499 for (size_t job = begin; job < end; ++job)
500 {
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)
509 // External runtimes own their thread pools. Avoid starting one
510 // behind the caller's serial switch or inside an OpenMP worker.
511 dest.noalias() = left.lazyProduct(right);
512#else
513 dest.noalias() = left * right;
514#endif
515 }
516 });
517 return;
518 }
519 }
520 const size_t jobs = TensorSizeProduct(m, n);
521 const int threads = TensorContractionThreads(jobs, work, allow);
522 TensorParallelRanges(jobs, threads, [&](size_t begin, size_t end, size_t) {
523 size_t i = begin % m, j = begin / m;
524 for (size_t offset = begin; offset < end; ++offset)
525 {
526 const size_t baseA = af[i], baseB = bf[j];
527 T sum{};
528 for (size_t x = 0; x < k; ++x)
529 sum = sum + a[baseA + ac[x]] * b[baseB + bc[x]];
530 out[offset] = sum;
531 if (++i == m)
532 {
533 i = 0;
534 ++j;
535 }
536 }
537 });
538}
539
540template <class T, class Storage>
541void TensorDenseContraction(const Storage &a, const Storage &b, Storage &out, const TensorAxisPattern &fa, const TensorAxisPattern &fb,
542 const TensorAxisPattern &ka, const TensorAxisPattern &kb, bool allow, int gateSize = 0)
543{
544 if (fa.count == 1 && fb.count == 1)
545 {
546 out[0] = TensorScalarContraction<T>(a, b, ka, kb, allow);
547 return;
548 }
549 const TensorAxisOffsets af(fa), bf(fb), ac(ka), bc(kb);
550 TensorExecuteContraction<T>(a, b, out, fa, fb, ka, kb, af, bf, ac, bc, gateSize, allow);
551}
552
553// Internal prepared metadata. Execute requires validated shapes and distinct
554// live input/output storage; ForestContractor enforces these at plan reuse.
556{
557 public:
558 template <class Pairs> bool Prepare(const std::vector<size_t> &da, const std::vector<size_t> &db, const Pairs &pairs, size_t byteLimit)
559 {
560 TensorContractionPlan candidate;
561 if (!candidate.Initialize(da, db, pairs, byteLimit))
562 return false;
563 *this = std::move(candidate);
564 return true;
565 }
566
567 const std::vector<size_t> &GetDims() const
568 {
569 return dimensions;
570 }
571
572 size_t GetSize() const
573 {
574 return size;
575 }
576
577 // Optional GEMM scratch in elements. Overflow disables retained packing.
578 size_t GetPackingSize() const
579 {
580 const size_t m = fa.count, n = fb.count, k = ka.count;
581 if (gateSize || m < 8 || n < 8 || k < 8)
582 return 0;
583 size_t count = 0;
584 const size_t limit = std::numeric_limits<size_t>::max();
585 if ((!TensorMatrixContiguous(fa, ka) && !TensorAddBytes(count, m, k, limit)) ||
586 (!TensorMatrixContiguous(kb, fb) && !TensorAddBytes(count, k, n, limit)))
587 return limit;
588 return count;
589 }
590
591 size_t ExtraBytes() const
592 {
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() +
595 bc.Bytes();
596 }
597
598 // Scratch, if supplied, holds GetPackingSize() elements and must not alias
599 // live inputs or outputs. Values are packed anew on every execution.
600 template <class T> void Execute(const T *a, const T *b, T *out, bool allow, T *packing = nullptr) const
601 {
602 TensorExecuteContraction<T>(a, b, out, fa, fb, ka, kb, af, bf, ac, bc, gateSize, allow, packing);
603 }
604
605 private:
606 template <class Pairs> bool Initialize(const std::vector<size_t> &da, const std::vector<size_t> &db, const Pairs &pairs, size_t byteLimit)
607 {
608 TensorAxisMask usedA(da.size()), usedB(db.size());
609 dimensions = TensorContractionDimensions(da, db, pairs, usedA, usedB);
610 if (std::find(da.begin(), da.end(), 0) != da.end() || std::find(db.begin(), db.end(), 0) != db.end())
611 return false;
612 TensorContractionPatterns(da, db, usedA, usedB, pairs, fa, fb, ka, kb);
613 size = TensorSizeProduct(fa.count, fb.count);
614 gateSize = TensorGateSize(da, db, pairs.size());
615 size_t bytes = ExtraBytes();
616 if (bytes > byteLimit)
617 return false;
618 // A scalar reduction uses cursors, never input-sized offset tables.
619 if (fa.count == 1 && fb.count == 1)
620 return true;
621 for (const auto *pattern : {&fa, &fb, &ka, &kb})
622 if (!pattern->Linear() && !TensorAddBytes(bytes, pattern->count, sizeof(size_t), byteLimit))
623 return false;
624 af = TensorAxisOffsets(fa);
625 bf = TensorAxisOffsets(fb);
626 ac = TensorAxisOffsets(ka);
627 bc = TensorAxisOffsets(kb);
628 return ExtraBytes() <= byteLimit;
629 }
630
631 std::vector<size_t> dimensions;
632 TensorAxisPattern fa, fb, ka, kb;
633 TensorAxisOffsets af, bf, ac, bc;
634 size_t size = 0;
635 int gateSize = 0;
636};
637
638} // namespace detail
639} // namespace Utils
void Output(const json::object &result, bool framed)
TensorAxisCursor(const TensorAxisPattern &pattern, size_t linear)
unsigned char operator[](size_t i) const
unsigned char & operator[](size_t i)
TensorAxisOffsets(const TensorAxisPattern &pattern)
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
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)