pyqrack-cpu 2.25.0__tar.gz → 2.25.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {pyqrack_cpu-2.25.0/pyqrack_cpu.egg-info → pyqrack_cpu-2.25.2}/PKG-INFO +1 -1
  2. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_ace_backend.py +226 -78
  3. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2/pyqrack_cpu.egg-info}/PKG-INFO +1 -1
  4. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/setup.py +1 -1
  5. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/LICENSE +0 -0
  6. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/MANIFEST.in +0 -0
  7. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/Makefile +0 -0
  8. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/README.md +0 -0
  9. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyproject.toml +0 -0
  10. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/__init__.py +0 -0
  11. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/neuron_activation_fn.py +0 -0
  12. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/pauli.py +0 -0
  13. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_circuit.py +0 -0
  14. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  15. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_neuron.py +0 -0
  16. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  17. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_simulator.py +0 -0
  18. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_stabilizer.py +0 -0
  19. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_system/__init__.py +0 -0
  20. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/qrack_system/qrack_system.py +0 -0
  21. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/quimb_circuit_type.py +0 -0
  22. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/stats/__init__.py +0 -0
  23. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/stats/load_quantized_data.py +0 -0
  24. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack/stats/quantize_by_range.py +0 -0
  25. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack_cpu.egg-info/SOURCES.txt +0 -0
  26. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack_cpu.egg-info/dependency_links.txt +0 -0
  27. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack_cpu.egg-info/not-zip-safe +0 -0
  28. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack_cpu.egg-info/requires.txt +0 -0
  29. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/pyqrack_cpu.egg-info/top_level.txt +0 -0
  30. {pyqrack_cpu-2.25.0 → pyqrack_cpu-2.25.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu
3
- Version: 2.25.0
3
+ Version: 2.25.2
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -408,18 +408,12 @@ class QrackAceBackend:
408
408
  # per-simulator allocation above.
409
409
  self._detect_ancilla1 = []
410
410
  self._detect_ancilla2 = []
411
- self._detect_ancilla3 = []
412
- self._detect_ancilla4 = []
413
411
  if self.is_error_detection:
414
412
  for i in range(len(sim_counts)):
415
413
  self._detect_ancilla1.append(sim_counts[i])
416
414
  sim_counts[i] += 1
417
415
  self._detect_ancilla2.append(sim_counts[i])
418
416
  sim_counts[i] += 1
419
- self._detect_ancilla3.append(sim_counts[i])
420
- sim_counts[i] += 1
421
- self._detect_ancilla4.append(sim_counts[i])
422
- sim_counts[i] += 1
423
417
 
424
418
  # Logical-qubit wrapper around each patch's detection ancilla,
425
419
  # so it's addressable via the ORDINARY cx()/_cpauli() machinery
@@ -441,8 +435,6 @@ class QrackAceBackend:
441
435
  # legitimately need to change any single replica's own value.
442
436
  self._detect_ancilla1_lq = []
443
437
  self._detect_ancilla2_lq = []
444
- self._detect_ancilla3_lq = []
445
- self._detect_ancilla4_lq = []
446
438
  if self.is_error_detection:
447
439
  for sim_id, phys_idx in enumerate(self._detect_ancilla1):
448
440
  lq_idx = len(self._qubits)
@@ -452,14 +444,6 @@ class QrackAceBackend:
452
444
  lq_idx = len(self._qubits)
453
445
  self._qubits.append([(sim_id, phys_idx)])
454
446
  self._detect_ancilla2_lq.append(lq_idx)
455
- for sim_id, phys_idx in enumerate(self._detect_ancilla3):
456
- lq_idx = len(self._qubits)
457
- self._qubits.append([(sim_id, phys_idx)])
458
- self._detect_ancilla3_lq.append(lq_idx)
459
- for sim_id, phys_idx in enumerate(self._detect_ancilla4):
460
- lq_idx = len(self._qubits)
461
- self._qubits.append([(sim_id, phys_idx)])
462
- self._detect_ancilla4_lq.append(lq_idx)
463
447
  # Boundary repetition code, on-demand design: only the couplers
464
448
  # are actually noisy here -- single-qubit gates are already
465
449
  # exactly transversal, per-replica, with zero error, so there's
@@ -716,9 +700,6 @@ class QrackAceBackend:
716
700
  else:
717
701
  t.adjs()
718
702
 
719
- def _detect_swap(self, sim_id, idx1, idx2):
720
- self.sim[sim_id].swap(idx1, idx2)
721
-
722
703
  def _anti_shadow_wrap(self, c, t, middle_fn):
723
704
  self._qec_x(c)
724
705
  middle_fn(c, t)
@@ -757,6 +738,24 @@ class QrackAceBackend:
757
738
  def _anti_cy_shadow(self, c, t):
758
739
  self._anti_shadow_wrap(c, t, self._cy_shadow)
759
740
 
741
+ def _ccx_shadow(self, c1, c2, t, lc1, lc2, lt):
742
+ # CCNOT decomposition
743
+ self.h(lt)
744
+ self._cx_shadow(c2, t)
745
+ self.adjt(lt)
746
+ self._cx_shadow(c1, t)
747
+ self.t(lt)
748
+ self._cx_shadow(c2, t)
749
+ self.adjt(lt)
750
+ self._cx_shadow(c1, t)
751
+ self.t(lt)
752
+ self.h(lt)
753
+ self.t(lc2)
754
+ self._cx_shadow(c1, c2)
755
+ self.t(lc1)
756
+ self.adjt(lc2)
757
+ self._cx_shadow(c1, c2)
758
+
760
759
  def _unpack(self, lq):
761
760
  return self._qubits[lq]
762
761
 
@@ -2258,7 +2257,7 @@ class QrackAceBackend:
2258
2257
  for i in range(len(hq1)):
2259
2258
  sim_id, idx1 = hq1[i]
2260
2259
  _, idx2 = hq2[i]
2261
- self._detect_swap(sim_id, idx1, idx2)
2260
+ self.sim[sim_id].swap(idx1, idx2)
2262
2261
  # No post-swap correction: this branch is a native index
2263
2262
  # relabeling applied identically to every matching replica
2264
2263
  # pair, with zero Schmidt-truncation opportunity -- verified
@@ -2300,7 +2299,7 @@ class QrackAceBackend:
2300
2299
  for _hq2 in non_matching:
2301
2300
  self._cx_shadow(_hq1, _hq2)
2302
2301
  for _hq2 in matching:
2303
- self._detect_swap(_hq1[0], _hq1[1], _hq2[1])
2302
+ self.sim[_hq1[0]].swap(_hq1[1], _hq2[1])
2304
2303
  for _hq2 in non_matching:
2305
2304
  self._cx_shadow(_hq1, _hq2)
2306
2305
  # No correction on lq1 (bulk, single-replica): already a
@@ -2317,7 +2316,7 @@ class QrackAceBackend:
2317
2316
  for _hq1 in non_matching:
2318
2317
  self._cx_shadow(_hq2, _hq1)
2319
2318
  for _hq1 in matching:
2320
- self._detect_swap(_hq2[0], _hq2[1], _hq1[1])
2319
+ self.sim[_hq2[0]].swap(_hq2[1], _hq2[1])
2321
2320
  for _hq1 in non_matching:
2322
2321
  self._cx_shadow(_hq2, _hq1)
2323
2322
  self._correct(lq1)
@@ -2378,67 +2377,180 @@ class QrackAceBackend:
2378
2377
  self.mcx([c1, c2], t)
2379
2378
  return
2380
2379
 
2381
- anc1, anc2 = None, None
2382
- if self.is_error_detection and not self._in_gadget_capture and (len(hqt) == 1) and ((len(hq1) > 1) or (len(hq2) > 1)):
2383
- anc1 = self._detect_ancilla3_lq[hqt[0][0]]
2384
- anc2 = self._detect_ancilla4_lq[hqt[0][0]]
2380
+ if self.is_boundary_repetition_code or (len(hqt) > 1):
2381
+ self.h(t)
2382
+ self.cx(c2, t)
2383
+ self.adjt(t)
2384
+ self.cx(c1, t)
2385
+ self.t(t)
2386
+ self.cx(c2, t)
2387
+ self.adjt(t)
2388
+ self.cx(c1, t)
2389
+ self.t(t)
2390
+ self.h(t)
2391
+ self.t(c2)
2392
+ self.cx(c1, c2)
2393
+ self.t(c1)
2394
+ self.adjt(c2)
2395
+ self.cx(c1, c2)
2396
+ return
2397
+
2398
+
2399
+ if not self._in_gadget_capture:
2400
+ self._correct(c1)
2401
+ self._correct(c2)
2402
+
2403
+ anc1, anc2, anc1b, anc2b = None, None, None, None
2404
+ if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1)):
2385
2405
  self._in_gadget_capture = True
2386
- self.cx(t, anc1)
2387
- self.cx(c1, anc1)
2388
- self.cx(c2, anc1)
2389
- self.cx(t, anc2)
2390
- self.cx(c2, anc2)
2391
- self.cx(c1, anc2)
2406
+
2407
+ if len(hq1) > 1:
2408
+ anc1 = self._detect_ancilla1_lq[hq1[0][0]]
2409
+ self.cx(c1, anc1)
2410
+ if hqt[0][0] != hq1[0][0]:
2411
+ anc1b = self._detect_ancilla1_lq[hqt[0][0]]
2412
+ self.cx(c1, anc1b)
2413
+
2414
+ if len(hq2) > 1:
2415
+ if (len(hq1) > 1) and (hq1[0][0] == hq2[0][0]):
2416
+ anc2 = self._detect_ancilla2_lq[hq2[0][0]]
2417
+ else:
2418
+ anc2 = self._detect_ancilla1_lq[hq2[0][0]]
2419
+ self.cx(c2, anc2)
2420
+ if hqt[0][0] != hq2[0][0]:
2421
+ anc2b = self._detect_ancilla2_lq[hqt[0][0]]
2422
+ # Control bit-flip
2423
+ self.cx(c2, anc2b)
2424
+
2392
2425
  self._in_gadget_capture = False
2393
2426
 
2394
- # CCNOT decomposition
2395
- self.h(t)
2396
- self.cx(c2, t)
2397
- self.adjt(t)
2398
- self.cx(c1, t)
2399
- self.t(t)
2400
- self.cx(c2, t)
2401
- self.adjt(t)
2402
- self.cx(c1, t)
2403
- self.t(t)
2404
- self.h(t)
2405
- self.t(c2)
2406
- self.cx(c1, c2)
2407
- self.t(c1)
2408
- self.adjt(c2)
2409
- self.cx(c1, c2)
2427
+ qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
2428
+ qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
2429
+ qbt, _ = QrackAceBackend._get_qb_lhv_indices(hqt)
2430
+
2431
+ for qt in qbt:
2432
+ witness = None
2433
+ bt = hqt[qt]
2434
+ for q1 in qb1:
2435
+ b1 = hq1[q1]
2436
+ for q2 in qb2:
2437
+ b2 = hq2[q2]
2438
+ for q2 in qb2:
2439
+ b2 = hq2[q2]
2440
+ if bt[0] == b1[0] and bt[0] == b2[0]:
2441
+ witness = bt
2442
+ break
2443
+ if witness is not None:
2444
+ break
2445
+ if witness is not None:
2446
+ break
2447
+ if witness:
2448
+ self.sim[b1[0]].mcx([b1[1], b2[1]], bt[1])
2449
+ else:
2450
+ self._ccx_shadow(b1, b2, bt, c1, c2, t)
2451
+
2452
+ self._in_gadget_capture = True
2410
2453
 
2411
2454
  if anc1 is not None:
2412
2455
  # Syndrome
2413
- self.cx(t, anc1)
2414
- self.cx(c2, anc1)
2415
- self.cx(t, anc2)
2416
- self.cx(c1, anc2)
2456
+ self.cx(c1, anc1)
2417
2457
 
2418
2458
  # Post-selection
2419
2459
  anc_sim, anc_idx = self._qubits[anc1][0]
2420
2460
  p = self.sim[anc_sim].prob(anc_idx)
2461
+ is_flipped = False
2421
2462
  if self._ps_epsilon >= (1.0 - p):
2422
2463
  b1 = self.m(anc1)
2423
2464
  else:
2424
2465
  b1 = self.force_m(anc1, False)
2425
2466
  if b1:
2426
2467
  self.x(anc1)
2468
+ q = None
2469
+ for c in hq1:
2470
+ if c[0] == anc_sim:
2471
+ q = c[1]
2472
+ break
2473
+ if q is None:
2474
+ self.x(c1)
2475
+ is_flipped = True
2476
+ else:
2477
+ self.sim[anc_sim].x(q)
2427
2478
 
2479
+ if anc1b is not None:
2480
+ anc_sim, anc_idx = self._qubits[anc1b][0]
2481
+ p = self.sim[anc_sim].prob(anc_idx)
2482
+ if b1:
2483
+ p = 1.0 - p
2484
+ if self._ps_epsilon >= (1.0 - p):
2485
+ b2 = self.m(anc1b)
2486
+ else:
2487
+ b2 = self.force_m(anc1b, b1)
2488
+ if b2:
2489
+ self.x(anc1b)
2490
+ if b2 != is_flipped:
2491
+ q = None
2492
+ for c in hq1:
2493
+ if c[0] == anc_sim:
2494
+ q = c[1]
2495
+ break
2496
+ if q is None:
2497
+ if not is_flipped:
2498
+ self.x(c1)
2499
+ else:
2500
+ self.sim[anc_sim].x(q)
2501
+
2502
+ if anc2 is not None:
2503
+ # Syndrome
2504
+ self.cx(c2, anc2)
2505
+
2506
+ # Post-selection
2428
2507
  anc_sim, anc_idx = self._qubits[anc2][0]
2429
2508
  p = self.sim[anc_sim].prob(anc_idx)
2430
- if b1:
2431
- p = 1.0 - p
2509
+ is_flipped = False
2432
2510
  if self._ps_epsilon >= (1.0 - p):
2433
- b2 = self.m(anc2)
2511
+ b1 = self.m(anc2)
2434
2512
  else:
2435
- b2 = self.force_m(anc2, b1)
2436
- if b2:
2513
+ b1 = self.force_m(anc2, False)
2514
+ if b1:
2437
2515
  self.x(anc2)
2516
+ q = None
2517
+ for c in hq2:
2518
+ if c[0] == anc_sim:
2519
+ q = c[1]
2520
+ break
2521
+ if q is None:
2522
+ self.x(c2)
2523
+ is_flipped = True
2524
+ else:
2525
+ self.sim[anc_sim].x(q)
2526
+
2527
+ if anc2b is not None:
2528
+ anc_sim, anc_idx = self._qubits[anc2b][0]
2529
+ p = self.sim[anc_sim].prob(anc_idx)
2530
+ if b1:
2531
+ p = 1.0 - p
2532
+ if self._ps_epsilon >= (1.0 - p):
2533
+ b2 = self.m(anc2b)
2534
+ else:
2535
+ b2 = self.force_m(anc2b, b1)
2536
+ if b2:
2537
+ self.x(anc2b)
2538
+ if b2 != is_flipped:
2539
+ q = None
2540
+ for c in hq2:
2541
+ if c[0] == anc_sim:
2542
+ q = c[1]
2543
+ break
2544
+ if q is None:
2545
+ if not is_flipped:
2546
+ self.x(c2)
2547
+ else:
2548
+ self.sim[anc_sim].x(q)
2549
+
2550
+ self._in_gadget_capture = False
2438
2551
 
2439
- # Correction gate
2440
- if b1 or b2:
2441
- self.x(t)
2552
+ if not self._in_gadget_capture:
2553
+ self._correct(t)
2442
2554
 
2443
2555
  def ccz(self, c1, c2, t):
2444
2556
  self.h(t)
@@ -2969,7 +3081,60 @@ class QrackAceBackend:
2969
3081
  if a != b:
2970
3082
  coupling_map.add((a, b))
2971
3083
 
2972
- self._coupling_map = sorted(coupling_map)
3084
+ # BUGFIX (per Dan): distant boundary-to-boundary couplings -- two
3085
+ # boundary qubits sharing a simulator id even when they're
3086
+ # nowhere near each other. Tried re-deriving adjacency from
3087
+ # sim_id arithmetic several ways; none held up, because sim_id
3088
+ # doesn't decompose cleanly back into a geometric position.
3089
+ # Dropping that entirely: the patch grid is a plain, regular
3090
+ # series of rectangles by construction, and that geometry is
3091
+ # already sitting in self._is_row_long_range /
3092
+ # self._is_col_long_range (True = bulk, False = boundary). Two
3093
+ # boundary qubits are kept coupled only when they lie on the
3094
+ # SAME boundary row or column AND bound (or lie within) one
3095
+ # single contiguous bulk run along it -- i.e. no OTHER boundary
3096
+ # crossing lies strictly between them -- which is exactly "the
3097
+ # same single side of one rectangular patch." A corner
3098
+ # naturally keeps this relationship along both its own row and
3099
+ # its own column (its real spokes to the nearest patch in each
3100
+ # of the four directions), while corner-to-distant-corner (or
3101
+ # any other boundary-to-boundary pair separated by an
3102
+ # intervening boundary, or lying on neither a shared row nor a
3103
+ # shared column at all) correctly drops out. Bulk-involved pairs
3104
+ # are untouched -- a bulk qubit's single simulator membership
3105
+ # was never ambiguous.
3106
+ def is_boundary(lq):
3107
+ return len(self._qubits[lq]) > 1
3108
+
3109
+ def same_single_side(idx_a, idx_b, long_range_arr, length):
3110
+ if idx_a == idx_b:
3111
+ return True
3112
+ lo, hi = min(idx_a, idx_b), max(idx_a, idx_b)
3113
+ if all(long_range_arr[i] for i in range(lo + 1, hi)):
3114
+ return True
3115
+ if self.is_torus:
3116
+ wrapped = list(range(hi + 1, length)) + list(range(0, lo))
3117
+ if all(long_range_arr[i] for i in wrapped):
3118
+ return True
3119
+ return False
3120
+
3121
+ filtered_map = set()
3122
+ for a, b in coupling_map:
3123
+ if is_boundary(a) and is_boundary(b):
3124
+ a_row, a_col = a // self._row_length, a % self._row_length
3125
+ b_row, b_col = b // self._row_length, b % self._row_length
3126
+ keep = False
3127
+ if (a_row == b_row) and (not self._is_row_long_range[a_row]):
3128
+ if same_single_side(a_col, b_col, self._is_col_long_range, self._row_length):
3129
+ keep = True
3130
+ if (a_col == b_col) and (not self._is_col_long_range[a_col]):
3131
+ if same_single_side(a_row, b_row, self._is_row_long_range, self._col_length):
3132
+ keep = True
3133
+ if not keep:
3134
+ continue
3135
+ filtered_map.add((a, b))
3136
+
3137
+ self._coupling_map = sorted(filtered_map)
2973
3138
 
2974
3139
  return self._coupling_map
2975
3140
 
@@ -3300,23 +3465,6 @@ class QrackAceBackend:
3300
3465
  # same condition to stay consistent with what the gate does.
3301
3466
  if (is_a_simple != is_b_simple) and has_match:
3302
3467
  p_net_swap = 2 * p * (1 - p)
3303
- # swap()'s own anc1/anc2 cross-check (verifying lq1's new
3304
- # value against lq2's old, and vice versa -- see swap())
3305
- # wraps the ENTIRE call, every branch, not specifically
3306
- # this matching-replica step -- _detect_swap itself is
3307
- # just a plain swap(), carrying no gadget of its own
3308
- # (an earlier per-replica parity gadget living there was
3309
- # proven a mathematically guaranteed no-op, the same way
3310
- # the old cx/cy/cz gadget was, and was simplified away).
3311
- # So this IS damped, unlike the stale "swap/iswap
3312
- # deliberately not damped" note this replaced -- but
3313
- # unconditionally so, not gated on any structural
3314
- # per-qubit condition BEYOND the "at least one side is
3315
- # boundary" precondition this whole branch already
3316
- # requires: swap()'s gadget captures both sides whenever
3317
- # is_error_detection is on and either side qualifies, no
3318
- # separate len(hq)>1-style gate the way _cpauli's anc2
3319
- # has on top of that.
3320
3468
  if self.is_error_detection:
3321
3469
  p_net_swap *= y
3322
3470
  if is_a_simple:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu
3
- Version: 2.25.0
3
+ Version: 2.25.2
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -7,7 +7,7 @@ from setuptools import setup
7
7
  from setuptools.command.build_py import build_py
8
8
 
9
9
 
10
- VERSION = "2.25.0"
10
+ VERSION = "2.25.2"
11
11
 
12
12
  # Read long description from README.
13
13
  README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.md')
File without changes
File without changes
File without changes
File without changes
File without changes