Qrack  10.0
General classical-emulating-quantum development framework
statevector_turboquant.hpp
Go to the documentation of this file.
1 //
3 // (C) Daniel Strano and the Qrack contributors 2017-2026. All rights reserved.
4 //
5 // Block-compressed quantum state vector using TurboQuant-variant for complex
6 // amplitudes. Based on TurboQuant (Zandieh et al., arXiv:2504.19874) and
7 // Apache 2.0 open-source implementation by TheTom (github.com/TheTom/turboquant_plus).
8 // Adapted for complex quantum state vectors by Dan Strano and (Anthropic) Claude.
9 //
10 // Each block of (1 << p) complex amplitudes is independently rotated by a
11 // random orthogonal matrix (applied separately to real and imaginary parts)
12 // and quantized per-coordinate at b bits.
13 //
14 // Key properties:
15 // - get_probs(): decompresses block-by-block in parallel
16 // - read()/write(): decompress one block, operate, recompress — O(block_size)
17 // - write2() same block: decompress once — O(block_size)
18 // - write2() cross block: decompress two blocks — O(2*block_size)
19 // - shuffle(): block-swap when half-capacity is block-aligned
20 // - Serialization: seed (8 bytes) + scales + packed data per block;
21 // rotation matrices regenerated from seed on load — O(1) vs O(D²) storage
22 //
23 // Build configuration (set via CMake, overridable at runtime via constructor):
24 // QRACK_TURBO_BITS — default bits per quantized coordinate (default 4)
25 //
26 // Licensed under the GNU Lesser General Public License V3.
27 // See LICENSE.md in the project root or https://www.gnu.org/licenses/lgpl-3.0.en.html
28 // for details.
29 
30 #pragma once
31 
32 #include "statevector.hpp"
33 
34 #include <algorithm>
35 #include <cmath>
36 #include <cstring>
37 #include <iostream>
38 #include <mutex>
39 #include <random>
40 #include <vector>
41 
42 #ifndef QRACK_TURBO_BITS
43 #define QRACK_TURBO_BITS 4
44 #endif
45 
46 namespace Qrack {
47 
48 // ---------------------------------------------------------------------------
49 // TurboQuant helpers
50 // ---------------------------------------------------------------------------
51 
52 // Build a random orthogonal d×d matrix from a fixed seed.
53 // Storing the seed rather than the matrix reduces serialized size from
54 // O(d²) to O(1) — critical for large block sizes.
55 // Column-major storage: column j starts at R[j*d].
56 static inline std::vector<real1> _tq_make_rotation(const size_t d, const uint64_t seed)
57 {
58  std::mt19937_64 rng(seed);
59  std::normal_distribution<real1_f> normal(ZERO_R1_F, ONE_R1_F);
60  std::vector<real1> R(d * d);
61  for (auto& v : R) {
62  v = (real1)normal(rng);
63  }
64  for (size_t j = 0U; j < d; ++j) {
65  real1 nrm = ZERO_R1;
66  for (size_t i = 0U; i < d; ++i) {
67  nrm += R[j * d + i] * R[j * d + i];
68  }
69  nrm = std::sqrt(nrm);
70  if (nrm < (real1)1e-8)
71  nrm = (real1)1e-8;
72  for (size_t i = 0U; i < d; ++i) {
73  R[j * d + i] /= nrm;
74  }
75  for (size_t k = j + 1U; k < d; ++k) {
76  real1 dot = ZERO_R1;
77  for (size_t i = 0U; i < d; ++i) {
78  dot += R[j * d + i] * R[k * d + i];
79  }
80  for (size_t i = 0U; i < d; ++i) {
81  R[k * d + i] -= dot * R[j * d + i];
82  }
83  }
84  }
85  return R;
86 }
87 
88 // Convenience overload: generate a random seed from hardware entropy,
89 // return both the rotation and the seed used (for later serialization).
90 static inline std::vector<real1> _tq_make_rotation(const size_t d, uint64_t* seed_out)
91 {
92  std::random_device rd;
93  *seed_out = ((uint64_t)rd() << 32U) | (uint64_t)rd();
94  return _tq_make_rotation(d, *seed_out);
95 }
96 
97 // Compute transpose of a d×d column-major matrix.
98 static inline std::vector<real1> _tq_transpose(const std::vector<real1>& R, const size_t d)
99 {
100  std::vector<real1> T(d * d);
101  for (size_t i = 0U; i < d; ++i)
102  for (size_t j = 0U; j < d; ++j)
103  T[i * d + j] = R[j * d + i];
104  return T;
105 }
106 
107 // Apply d×d column-major rotation R to vector v of length d.
108 // Result written into out (may alias v if caller provides scratch).
109 static inline void _tq_rotate(const real1* v, const std::vector<real1>& R, const size_t d, real1* out)
110 {
111  for (size_t i = 0U; i < d; ++i) {
112  real1 s = ZERO_R1;
113  for (size_t j = 0U; j < d; ++j) {
114  s += R[j * d + i] * v[j];
115  }
116  out[i] = s;
117  }
118 }
119 
120 // Quantize a single real value to bits-bit bucket index, given scale (std dev).
121 static inline int _tq_quant_bucket(const real1 val, const real1 scale, const int bits)
122 {
123  const int levels = 1 << bits;
124  const real1 lo = (real1)-3.0 * scale;
125  const real1 hi = (real1)3.0 * scale;
126  const real1 step = (hi - lo) / (real1)levels;
127  if (step < (real1)1e-8)
128  return 0;
129  const real1 clamped = std::max(lo, std::min(hi - step, val));
130  int bucket = (int)((clamped - lo) / step);
131  if (bucket < 0)
132  bucket = 0;
133  if (bucket >= levels)
134  bucket = levels - 1;
135  return bucket;
136 }
137 
138 // Dequantize a bucket index back to a real value.
139 static inline real1 _tq_dequant(const int bucket, const real1 scale, const int bits)
140 {
141  const int levels = 1 << bits;
142  const real1 lo = (real1)-3.0 * scale;
143  const real1 hi = (real1)3.0 * scale;
144  const real1 step = (hi - lo) / (real1)levels;
145  return lo + ((real1)bucket + (real1)0.5) * step;
146 }
147 
148 // ---------------------------------------------------------------------------
149 // Binary I/O helpers
150 // ---------------------------------------------------------------------------
151 static inline void _tq_write_size(std::ostream& os, const size_t x)
152 {
153  os.write(reinterpret_cast<const char*>(&x), sizeof(size_t));
154 }
155 static inline void _tq_write_int(std::ostream& os, const int x)
156 {
157  os.write(reinterpret_cast<const char*>(&x), sizeof(int));
158 }
159 static inline void _tq_write_bool(std::ostream& os, const bool x)
160 {
161  os.write(reinterpret_cast<const char*>(&x), sizeof(bool));
162 }
163 static inline size_t _tq_read_size(std::istream& is)
164 {
165  size_t x;
166  is.read(reinterpret_cast<char*>(&x), sizeof(size_t));
167  return x;
168 }
169 static inline int _tq_read_int(std::istream& is)
170 {
171  int x;
172  is.read(reinterpret_cast<char*>(&x), sizeof(int));
173  return x;
174 }
175 static inline bool _tq_read_bool(std::istream& is)
176 {
177  bool x;
178  is.read(reinterpret_cast<char*>(&x), sizeof(bool));
179  return x;
180 }
181 
182 // ---------------------------------------------------------------------------
183 // TurboBlock
184 //
185 // Design rationale:
186 // - Single rotation matrix R acts on the D-dimensional real vector formed
187 // by interleaving re and im parts: [re_0, im_0, re_1, im_1, ...].
188 // This respects the joint complex structure of Hilbert space — the
189 // natural unit is the complex amplitude |ψ|² = re² + im², not re and im
190 // independently. Under Haar measure, |ψ|² is uniformly distributed, and
191 // the rotation concentrates the interleaved real vector into approximately
192 // equal-variance Gaussian coordinates.
193 // - Single scalar block_scale = RMS amplitude magnitude over the whole
194 // block, estimated on first compression. Both re and im coordinates are
195 // quantized against this same scale, which is theoretically correct since
196 // Haar uniformity implies equal variance for re and im after rotation.
197 // - One seed (8 bytes) replaces the full D×D rotation matrix, reducing
198 // serialized size from O(D²) to O(1). The matrix is regenerated
199 // deterministically from the seed on load.
200 //
201 // Serialized size per block:
202 // header: ~21 bytes
203 // seed: 8 bytes
204 // block_scale: 4 bytes (one real1)
205 // packed: (2*D*BITS + 63) / 64 * 8 bytes
206 // ----------------------------------------
207 // For D=64, BITS=4: 21 + 8 + 4 + 64 = ~97 bytes per block
208 // For n=16 qubits, 1024 blocks: ~97 KB (vs 512 KB uncompressed)
209 // ---------------------------------------------------------------------------
210 struct TurboBlock {
211  size_t D; // block size = number of complex amplitudes
212  int BITS; // bits per quantized coordinate
213  int LEVELS; // 1 << BITS
214 
215  // Single seed for the one rotation matrix acting on the 2D-dimensional
216  // interleaved [re_0, im_0, re_1, im_1, ...] real vector.
217  uint64_t seed;
218 
219  // Rotation matrix R (2D×2D, col-major) and its transpose RT (= inverse).
220  // Regenerated from seed — not serialized.
221  std::vector<real1> R; // rotation: 2D×2D
222  std::vector<real1> RT; // transpose of R
223 
224  // Single scalar scale: RMS amplitude magnitude over whole block.
225  // Both re and im coordinates are quantized against this scale.
227 
228  // Packed bucket indices: 2D coordinates (D re + D im), BITS each.
229  size_t NWORDS;
230  std::unique_ptr<uint64_t[]> packed;
232 
233  TurboBlock(int p, int b)
234  : D(1ULL << p)
235  , BITS(b)
236  , LEVELS(1 << b)
237  , seed(0U)
238  , R(_tq_make_rotation(2U * (1ULL << p), &seed))
239  , RT(_tq_transpose(R, 2U * (1ULL << p)))
241  , NWORDS((2U * (1ULL << p) * b + 63U) / 64U)
242  , packed(new uint64_t[(2U * (1ULL << p) * b + 63U) / 64U])
243  , initialized(false)
244  {
245  std::fill(packed.get(), packed.get() + NWORDS, 0U);
246  }
247 
249  : D(o.D)
250  , BITS(o.BITS)
251  , LEVELS(o.LEVELS)
252  , seed(o.seed)
253  , R(o.R)
254  , RT(o.RT)
256  , NWORDS(o.NWORDS)
257  , packed(new uint64_t[o.NWORDS])
259  {
260  std::copy(o.packed.get(), o.packed.get() + o.NWORDS, packed.get());
261  }
262 
264  {
265  if (this == &o)
266  return *this;
267  D = o.D;
268  BITS = o.BITS;
269  LEVELS = o.LEVELS;
270  seed = o.seed;
271  R = o.R;
272  RT = o.RT;
274  NWORDS = o.NWORDS;
275  packed.reset(new uint64_t[NWORDS]);
277  std::copy(o.packed.get(), o.packed.get() + o.NWORDS, packed.get());
278  return *this;
279  }
280 
281  // Pack a bucket index into the packed array.
282  // idx is the coordinate index.
283  void pack_bucket(const size_t idx, const int bucket)
284  {
285  const size_t bit_offset = idx * (size_t)BITS;
286  const size_t word = bit_offset / 64U;
287  const size_t bit = bit_offset % 64U;
288  const uint64_t mask = (uint64_t)(LEVELS - 1) << bit;
289  packed[word] = (packed[word] & ~mask) | ((uint64_t)bucket << bit);
290  if (bit + (size_t)BITS > 64U) {
291  const size_t overflow = bit + (size_t)BITS - 64U;
292  const uint64_t mask2 = ((uint64_t)1 << overflow) - 1U;
293  packed[word + 1U] = (packed[word + 1U] & ~mask2) | ((uint64_t)bucket >> (BITS - (int)overflow));
294  }
295  }
296 
297  // Unpack a bucket index from the packed array.
298  int unpack_bucket(const size_t idx) const
299  {
300  const size_t bit_offset = idx * (size_t)BITS;
301  const size_t word = bit_offset / 64U;
302  const size_t bit = bit_offset % 64U;
303  int bucket = (int)((packed[word] >> bit) & (uint64_t)(LEVELS - 1));
304  if (bit + (size_t)BITS > 64U) {
305  const size_t overflow = bit + (size_t)BITS - 64U;
306  const int high = (int)(packed[word + 1U] & (((uint64_t)1 << overflow) - 1U));
307  bucket |= (high << (BITS - (int)overflow));
308  bucket &= (LEVELS - 1);
309  }
310  return bucket;
311  }
312 
313  // Compress an array of D complex amplitudes into this block.
314  void compress(const complex* amps)
315  {
316  // Interleave re and im into a single 2D-dimensional real vector,
317  // then apply the single joint rotation.
318  const size_t D2 = 2U * D;
319  std::vector<real1> v_in(D2), v_rot(D2);
320  for (size_t i = 0U; i < D; ++i) {
321  v_in[2U * i] = real(amps[i]);
322  v_in[2U * i + 1U] = imag(amps[i]);
323  }
324  _tq_rotate(v_in.data(), R, D2, v_rot.data());
325 
326  // On first compression, estimate block_scale as RMS amplitude magnitude.
327  // Under Haar uniformity both re and im contributions are equal, so a
328  // single scalar captures the joint distribution correctly.
329  if (!initialized) {
330  real1 sum = ZERO_R1;
331  for (size_t j = 0U; j < D2; ++j) {
332  sum += v_rot[j] * v_rot[j];
333  }
334  block_scale = std::sqrt(sum / (real1)D2 + (real1)1e-8);
335  initialized = true;
336  }
337 
338  std::fill(packed.get(), packed.get() + NWORDS, 0U);
339  for (size_t j = 0U; j < D2; ++j) {
341  }
342  }
343 
344  // Decompress this block into an array of D complex amplitudes.
345  void decompress(complex* amps) const
346  {
347  const size_t D2 = 2U * D;
348  std::vector<real1> v_rot(D2), v_out(D2);
349  for (size_t j = 0U; j < D2; ++j) {
350  v_rot[j] = _tq_dequant(unpack_bucket(j), block_scale, BITS);
351  }
352  _tq_rotate(v_rot.data(), RT, D2, v_out.data());
353  for (size_t i = 0U; i < D; ++i) {
354  amps[i] = complex(v_out[2U * i], v_out[2U * i + 1U]);
355  }
356  }
357 
358  // Total probability mass in this block — computable without full
359  // decompression since ||Rv||² = ||v||² under orthogonal rotation.
361  {
362  real1 total = ZERO_R1;
363  const size_t D2 = 2U * D;
364  for (size_t j = 0U; j < D2; ++j) {
366  total += v * v;
367  }
368  return total;
369  }
370 
371  // --- Serialization ------------------------------------------------------
372  //
373  // Per-block binary format:
374  // size_t D
375  // int BITS
376  // bool initialized
377  // uint64_t seed (rotation regenerated on load — O(1) storage)
378  // if initialized:
379  // real1 block_scale
380  // size_t NWORDS
381  // uint64_t[NWORDS] packed
382 
383  void save(std::ostream& os) const
384  {
385  _tq_write_size(os, D);
386  _tq_write_int(os, BITS);
388  os.write(reinterpret_cast<const char*>(&seed), sizeof(uint64_t));
389  if (initialized) {
390  os.write(reinterpret_cast<const char*>(&block_scale), sizeof(real1));
391  }
392  _tq_write_size(os, NWORDS);
393  os.write(reinterpret_cast<const char*>(packed.get()), (std::streamsize)(NWORDS * sizeof(uint64_t)));
394  }
395 
396  static TurboBlock load(std::istream& is)
397  {
398  const size_t D_in = _tq_read_size(is);
399  const int BITS_in = _tq_read_int(is);
400  const bool init = _tq_read_bool(is);
401 
402  // Compute p = log2(D_in)
403  int p = 0;
404  for (size_t tmp = D_in; tmp > 1U; tmp >>= 1U) {
405  ++p;
406  }
407 
408  uint64_t seed_in;
409  is.read(reinterpret_cast<char*>(&seed_in), sizeof(uint64_t));
410 
411  // Reconstruct block, then overwrite rotation with seeded version
412  TurboBlock blk(p, BITS_in);
413  blk.seed = seed_in;
414  blk.R = _tq_make_rotation(2U * D_in, seed_in);
415  blk.RT = _tq_transpose(blk.R, 2U * D_in);
416  blk.initialized = init;
417 
418  if (init) {
419  is.read(reinterpret_cast<char*>(&blk.block_scale), sizeof(real1));
420  }
421 
422  const size_t nwords = _tq_read_size(is);
423  blk.NWORDS = nwords;
424  blk.packed.reset(new uint64_t[nwords]);
425  is.read(reinterpret_cast<char*>(blk.packed.get()), (std::streamsize)(nwords * sizeof(uint64_t)));
426 
427  return blk;
428  }
429 
430  friend std::ostream& operator<<(std::ostream& os, const TurboBlock& b)
431  {
432  b.save(os);
433  return os;
434  }
435 
436  friend std::istream& operator>>(std::istream& is, TurboBlock& b)
437  {
438  b = TurboBlock::load(is);
439  return is;
440  }
441 };
442 
443 // ---------------------------------------------------------------------------
444 // StateVectorTurboQuant
445 // ---------------------------------------------------------------------------
446 class StateVectorTurboQuant;
447 typedef std::shared_ptr<StateVectorTurboQuant> StateVectorTurboQuantPtr;
448 
450 protected:
451  size_t BLOCK;
452  size_t num_blocks;
453  std::vector<TurboBlock> blocks;
454  std::vector<std::mutex> block_mutexes;
455 
456  size_t block_of(const bitCapIntOcl i) const { return (size_t)(i / BLOCK); }
457  size_t offset_in(const bitCapIntOcl i) const { return (size_t)(i % BLOCK); }
458 
459  template <typename F> void with_block(const size_t b, F&& f)
460  {
461  std::lock_guard<std::mutex> lock(block_mutexes[b]);
462  std::vector<complex> amps(BLOCK);
463  blocks[b].decompress(amps.data());
464  f(amps.data(), BLOCK);
465  blocks[b].compress(amps.data());
466  }
467 
468 public:
469  // Construct from raw amplitudes (nullptr = |0⟩)
470  StateVectorTurboQuant(bitCapIntOcl cap, int p, int b, const complex* copyIn)
471  : StateVector(cap)
472  , BLOCK(1ULL << p)
473  , num_blocks((cap + (1ULL << p) - 1U) / (1ULL << p))
474  , blocks(num_blocks, TurboBlock(p, b))
476  {
477  copy_in(copyIn);
478  }
479 
481 
482  // --- Serialization ------------------------------------------------------
483  //
484  // Stream format:
485  // size_t capacity
486  // size_t BLOCK
487  // size_t num_blocks
488  // TurboBlock[num_blocks]
489 
490  void save(std::ostream& os) const
491  {
492  _tq_write_size(os, (size_t)capacity);
493  _tq_write_size(os, BLOCK);
495  for (size_t i = 0U; i < num_blocks; ++i) {
496  blocks[i].save(os);
497  }
498  }
499 
500  static StateVectorTurboQuantPtr load(std::istream& is)
501  {
502  const bitCapIntOcl cap = (bitCapIntOcl)_tq_read_size(is);
503  const size_t block_size = _tq_read_size(is);
504  const size_t nblocks = _tq_read_size(is);
505 
506  int p = 0;
507  for (size_t tmp = block_size; tmp > 1U; tmp >>= 1U) {
508  ++p;
509  }
510 
511  // Read all blocks
512  std::vector<TurboBlock> loaded;
513  loaded.reserve(nblocks);
514  for (size_t i = 0U; i < nblocks; ++i) {
515  loaded.push_back(TurboBlock::load(is));
516  }
517 
518  const int bits = loaded.empty() ? QRACK_TURBO_BITS : loaded[0].BITS;
519 
520  // Construct shell with correct geometry, then overwrite blocks
521  auto sv = std::make_shared<StateVectorTurboQuant>(cap, p, bits, nullptr);
522  sv->blocks = std::move(loaded);
523 
524  return sv;
525  }
526 
527  friend std::ostream& operator<<(std::ostream& os, const StateVectorTurboQuant& sv)
528  {
529  sv.save(os);
530  return os;
531  }
532 
533  friend std::istream& operator>>(std::istream& is, StateVectorTurboQuantPtr& sv)
534  {
536  return is;
537  }
538 
539  // --- StateVector interface ----------------------------------------------
540 
541  complex read(const bitCapInt& i) { return read((bitCapIntOcl)i); }
543  {
544  std::vector<complex> amps(BLOCK);
545  blocks[block_of(i)].decompress(amps.data());
546  return amps[offset_in(i)];
547  }
548 
549 #if ENABLE_COMPLEX_X2
550  complex2 read2(const bitCapInt& i1, const bitCapInt& i2) { return read2((bitCapIntOcl)i1, (bitCapIntOcl)i2); }
551  complex2 read2(const bitCapIntOcl& i1, const bitCapIntOcl& i2) { return complex2(read(i1), read(i2)); }
552 #endif
553 
554  void write(const bitCapInt& i, const complex& c) { write((bitCapIntOcl)i, c); }
555  void write(const bitCapIntOcl& i, const complex& c)
556  {
557  with_block(block_of(i), [&](complex* amps, size_t) { amps[offset_in(i)] = c; });
558  }
559 
560  void write2(const bitCapInt& i1, const complex& c1, const bitCapInt& i2, const complex& c2)
561  {
562  write2((bitCapIntOcl)i1, c1, (bitCapIntOcl)i2, c2);
563  }
564 
565  void write2(const bitCapIntOcl& i1, const complex& c1, const bitCapIntOcl& i2, const complex& c2)
566  {
567  const size_t b1 = block_of(i1), b2 = block_of(i2);
568  if (b1 == b2) {
569  with_block(b1, [&](complex* amps, size_t) {
570  amps[offset_in(i1)] = c1;
571  amps[offset_in(i2)] = c2;
572  });
573  } else {
574  const size_t blo = std::min(b1, b2), bhi = std::max(b1, b2);
575  std::lock_guard<std::mutex> lo(block_mutexes[blo]);
576  std::lock_guard<std::mutex> hi(block_mutexes[bhi]);
577  std::vector<complex> a1(BLOCK), a2(BLOCK);
578  blocks[b1].decompress(a1.data());
579  blocks[b2].decompress(a2.data());
580  a1[offset_in(i1)] = c1;
581  a2[offset_in(i2)] = c2;
582  blocks[b1].compress(a1.data());
583  blocks[b2].compress(a2.data());
584  }
585  }
586 
587  void clear()
588  {
589  par_for(0U, num_blocks, [&](const bitCapIntOcl& b, const unsigned&) {
590  std::vector<complex> z(BLOCK, ZERO_CMPLX);
591  blocks[b].compress(z.data());
592  });
593  }
594 
595  void copy_in(const complex* copyIn)
596  {
597  par_for(0U, num_blocks, [&](const bitCapIntOcl& b, const unsigned&) {
598  std::vector<complex> amps(BLOCK, ZERO_CMPLX);
599  if (copyIn) {
600  const size_t len = std::min(BLOCK, (size_t)(capacity - b * BLOCK));
601  std::copy(copyIn + b * BLOCK, copyIn + b * BLOCK + len, amps.data());
602  }
603  blocks[b].compress(amps.data());
604  });
605  }
606 
607  void copy_in(const complex* copyIn, const bitCapIntOcl offset, const bitCapIntOcl length)
608  {
609  if (!length)
610  return;
611  const size_t b0 = block_of(offset);
612  const size_t b1 = block_of(offset + length - 1U);
613  for (size_t b = b0; b <= b1; ++b) {
614  with_block(b, [&](complex* amps, size_t) {
615  const size_t base = b * BLOCK;
616  for (size_t j = 0U; j < BLOCK; ++j) {
617  const size_t g = base + j;
618  if (g >= (size_t)offset && g < (size_t)(offset + length))
619  amps[j] = copyIn ? copyIn[g - offset] : ZERO_CMPLX;
620  }
621  });
622  }
623  }
624 
625  void copy_in(StateVectorPtr sv, const bitCapIntOcl src, const bitCapIntOcl dst, const bitCapIntOcl len)
626  {
627  std::vector<complex> tmp(len, ZERO_CMPLX);
628  if (sv)
629  for (bitCapIntOcl i = 0U; i < len; ++i)
630  tmp[i] = sv->read(src + i);
631  copy_in(sv ? tmp.data() : nullptr, dst, len);
632  }
633 
634  void copy_out(complex* out)
635  {
636  par_for(0U, num_blocks, [&](const bitCapIntOcl& b, const unsigned&) {
637  std::vector<complex> amps(BLOCK);
638  blocks[b].decompress(amps.data());
639  const size_t len = std::min(BLOCK, (size_t)(capacity - b * BLOCK));
640  std::copy(amps.data(), amps.data() + len, out + b * BLOCK);
641  });
642  }
643 
644  void copy_out(complex* out, const bitCapIntOcl offset, const bitCapIntOcl length)
645  {
646  for (bitCapIntOcl i = 0U; i < length; ++i)
647  out[i] = read(offset + i);
648  }
649 
650  void copy(StateVectorPtr toCopy)
651  {
652  auto src = std::dynamic_pointer_cast<StateVectorTurboQuant>(toCopy);
653  if (src) {
654  par_for(0U, num_blocks, [&](const bitCapIntOcl& b, const unsigned&) {
655  std::lock_guard<std::mutex> lk(block_mutexes[b]);
656  blocks[b] = src->blocks[b];
657  });
658  } else {
659  std::vector<complex> tmp(capacity);
660  toCopy->copy_out(tmp.data());
661  copy_in(tmp.data());
662  }
663  }
664 
666  {
667  // Swap upper and lower halves block-by-block.
668  // For capacity that is a power of 2, the upper half starts at capacity/2
669  auto other = std::dynamic_pointer_cast<StateVectorTurboQuant>(svp);
670  const bitCapIntOcl half = capacity >> 1U;
671  const size_t hb = (size_t)(half / BLOCK);
672  if (other && (half % BLOCK == 0U)) {
673  // Block-aligned shuffle: swap block pointers (swap the TurboBlock objects)
674  par_for(
675  0U, hb, [&](const bitCapIntOcl& b, const unsigned&) { std::swap(blocks[b + hb], other->blocks[b]); });
676  } else {
677  // Fallback: decompress, swap, recompress
678  par_for(0U, half, [&](const bitCapIntOcl& i, const unsigned&) {
679  complex amp = svp->read(i);
680  svp->write(i, read(i + half));
681  write(i + half, amp);
682  });
683  }
684  }
685 
686  void get_probs(real1* outArray)
687  {
688  // Decompress block by block — norm preservation only holds for total block norm,
689  // not per-amplitude, so we must decompress to get per-amplitude probs.
690  par_for(0U, num_blocks, [&](const bitCapIntOcl& b, const unsigned&) {
691  std::vector<complex> amps(BLOCK);
692  blocks[b].decompress(amps.data());
693  const size_t len = std::min(BLOCK, (size_t)(capacity - b * BLOCK));
694  for (size_t j = 0U; j < len; ++j)
695  outArray[b * BLOCK + j] = norm(amps[j]);
696  });
697  }
698 
699  bool is_sparse() { return false; }
700 };
701 
702 typedef std::shared_ptr<StateVectorTurboQuant> StateVectorTurboQuantPtr;
703 } // namespace Qrack
void par_for(const bitCapIntOcl begin, const bitCapIntOcl end, ParallelFunc fn)
Call fn once for every numerical value between begin and end.
Definition: parallel_for.cpp:50
Definition: statevector_turboquant.hpp:449
void copy_in(const complex *copyIn)
Definition: statevector_turboquant.hpp:595
std::vector< TurboBlock > blocks
Definition: statevector_turboquant.hpp:453
void shuffle(StateVectorPtr svp)
Definition: statevector_turboquant.hpp:665
void copy(StateVectorPtr toCopy)
Definition: statevector_turboquant.hpp:650
void write2(const bitCapInt &i1, const complex &c1, const bitCapInt &i2, const complex &c2)
Definition: statevector_turboquant.hpp:560
bitCapIntOcl get_size()
Definition: statevector_turboquant.hpp:480
void get_probs(real1 *outArray)
Definition: statevector_turboquant.hpp:686
complex read(const bitCapIntOcl &i)
Definition: statevector_turboquant.hpp:542
size_t block_of(const bitCapIntOcl i) const
Definition: statevector_turboquant.hpp:456
void clear()
Definition: statevector_turboquant.hpp:587
void copy_out(complex *out)
Definition: statevector_turboquant.hpp:634
bool is_sparse()
Definition: statevector_turboquant.hpp:699
complex read(const bitCapInt &i)
Definition: statevector_turboquant.hpp:541
friend std::istream & operator>>(std::istream &is, StateVectorTurboQuantPtr &sv)
Definition: statevector_turboquant.hpp:533
void with_block(const size_t b, F &&f)
Definition: statevector_turboquant.hpp:459
void write(const bitCapIntOcl &i, const complex &c)
Definition: statevector_turboquant.hpp:555
void save(std::ostream &os) const
Definition: statevector_turboquant.hpp:490
friend std::ostream & operator<<(std::ostream &os, const StateVectorTurboQuant &sv)
Definition: statevector_turboquant.hpp:527
StateVectorTurboQuant(bitCapIntOcl cap, int p, int b, const complex *copyIn)
Definition: statevector_turboquant.hpp:470
void copy_in(const complex *copyIn, const bitCapIntOcl offset, const bitCapIntOcl length)
Definition: statevector_turboquant.hpp:607
static StateVectorTurboQuantPtr load(std::istream &is)
Definition: statevector_turboquant.hpp:500
size_t BLOCK
Definition: statevector_turboquant.hpp:451
void copy_out(complex *out, const bitCapIntOcl offset, const bitCapIntOcl length)
Definition: statevector_turboquant.hpp:644
size_t num_blocks
Definition: statevector_turboquant.hpp:452
size_t offset_in(const bitCapIntOcl i) const
Definition: statevector_turboquant.hpp:457
void write(const bitCapInt &i, const complex &c)
Definition: statevector_turboquant.hpp:554
void copy_in(StateVectorPtr sv, const bitCapIntOcl src, const bitCapIntOcl dst, const bitCapIntOcl len)
Definition: statevector_turboquant.hpp:625
void write2(const bitCapIntOcl &i1, const complex &c1, const bitCapIntOcl &i2, const complex &c2)
Optimized "write" that is only guaranteed to write if either amplitude is nonzero.
Definition: statevector_turboquant.hpp:565
std::vector< std::mutex > block_mutexes
Definition: statevector_turboquant.hpp:454
Definition: statevector.hpp:50
bitCapIntOcl capacity
Definition: statevector.hpp:52
Half-precision floating-point type.
Definition: half.hpp:2206
GLOSSARY: bitLenInt - "bit-length integer" - unsigned integer ID of qubit position in register bitCap...
Definition: complex16x2simd.hpp:25
quid init()
"Quasi-default constructor" (for an empty simulator)
Definition: wasm_api.cpp:634
void T(quid sid, bitLenInt q)
(External API) "T" Gate
Definition: wasm_api.cpp:1154
static std::vector< real1 > _tq_make_rotation(const size_t d, const uint64_t seed)
Definition: statevector_turboquant.hpp:56
std::shared_ptr< StateVectorTurboQuant > StateVectorTurboQuantPtr
Definition: statevector_turboquant.hpp:446
void seed(quid sid, unsigned s)
"Seed" random number generator (if pseudo-random Mersenne twister is in use)
Definition: wasm_api.cpp:873
void U(quid sid, bitLenInt q, real1_f theta, real1_f phi, real1_f lambda)
(External API) 3-parameter unitary gate
Definition: wasm_api.cpp:1199
half_float::half real1
Definition: qrack_types.hpp:106
std::complex< real1 > complex
Definition: qrack_types.hpp:140
static void _tq_write_int(std::ostream &os, const int x)
Definition: statevector_turboquant.hpp:155
double norm(const complex2 &c)
Definition: complex16x2simd.hpp:122
static size_t _tq_read_size(std::istream &is)
Definition: statevector_turboquant.hpp:163
void R(quid sid, real1_f phi, QubitPauliBasis q)
(External API) Rotation around Pauli axes
Definition: wasm_api.cpp:1485
QRACK_CONST real1 ONE_R1
Definition: qrack_types.hpp:188
static int _tq_read_int(std::istream &is)
Definition: statevector_turboquant.hpp:169
QRACK_CONST real1 ZERO_R1
Definition: qrack_types.hpp:186
static real1 _tq_dequant(const int bucket, const real1 scale, const int bits)
Definition: statevector_turboquant.hpp:139
static std::vector< real1 > _tq_transpose(const std::vector< real1 > &R, const size_t d)
Definition: statevector_turboquant.hpp:98
static void _tq_write_size(std::ostream &os, const size_t x)
Definition: statevector_turboquant.hpp:151
static int _tq_quant_bucket(const real1 val, const real1 scale, const int bits)
Definition: statevector_turboquant.hpp:121
std::shared_ptr< StateVector > StateVectorPtr
Definition: qrack_types.hpp:147
QRACK_CONST complex ZERO_CMPLX
Definition: qrack_types.hpp:258
static void _tq_rotate(const real1 *v, const std::vector< real1 > &R, const size_t d, real1 *out)
Definition: statevector_turboquant.hpp:109
static bool _tq_read_bool(std::istream &is)
Definition: statevector_turboquant.hpp:175
static void _tq_write_bool(std::ostream &os, const bool x)
Definition: statevector_turboquant.hpp:159
uint32 sqrt(uint32 &r, int &exp)
Fixed point square root.
Definition: half.hpp:1638
HALF_CONSTEXPR_NOERR unsigned int overflow(unsigned int sign=0)
Half-precision overflow.
Definition: half.hpp:815
#define ZERO_R1_F
Definition: qrack_types.hpp:162
#define bitCapInt
Definition: qrack_types.hpp:65
#define bitCapIntOcl
Definition: qrack_types.hpp:53
#define ONE_R1_F
Definition: qrack_types.hpp:165
#define QRACK_TURBO_BITS
Definition: statevector_turboquant.hpp:43
Definition: statevector_turboquant.hpp:210
void pack_bucket(const size_t idx, const int bucket)
Definition: statevector_turboquant.hpp:283
uint64_t seed
Definition: statevector_turboquant.hpp:217
friend std::istream & operator>>(std::istream &is, TurboBlock &b)
Definition: statevector_turboquant.hpp:436
real1 get_total_prob() const
Definition: statevector_turboquant.hpp:360
bool initialized
Definition: statevector_turboquant.hpp:231
std::vector< real1 > R
Definition: statevector_turboquant.hpp:221
std::unique_ptr< uint64_t[]> packed
Definition: statevector_turboquant.hpp:230
size_t D
Definition: statevector_turboquant.hpp:211
TurboBlock(const TurboBlock &o)
Definition: statevector_turboquant.hpp:248
real1 block_scale
Definition: statevector_turboquant.hpp:226
void compress(const complex *amps)
Definition: statevector_turboquant.hpp:314
friend std::ostream & operator<<(std::ostream &os, const TurboBlock &b)
Definition: statevector_turboquant.hpp:430
size_t NWORDS
Definition: statevector_turboquant.hpp:229
TurboBlock(int p, int b)
Definition: statevector_turboquant.hpp:233
static TurboBlock load(std::istream &is)
Definition: statevector_turboquant.hpp:396
int LEVELS
Definition: statevector_turboquant.hpp:213
TurboBlock & operator=(const TurboBlock &o)
Definition: statevector_turboquant.hpp:263
void save(std::ostream &os) const
Definition: statevector_turboquant.hpp:383
std::vector< real1 > RT
Definition: statevector_turboquant.hpp:222
int BITS
Definition: statevector_turboquant.hpp:212
int unpack_bucket(const size_t idx) const
Definition: statevector_turboquant.hpp:298
void decompress(complex *amps) const
Definition: statevector_turboquant.hpp:345
SIMD implementation of the double precision complex vector type of 2 complex numbers,...
Definition: complex16x2simd.hpp:30