pyqrack-complex128 2.25.2__tar.gz → 2.25.4__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_complex128-2.25.2 → pyqrack_complex128-2.25.4}/Makefile +1 -1
  2. {pyqrack_complex128-2.25.2/pyqrack_complex128.egg-info → pyqrack_complex128-2.25.4}/PKG-INFO +1 -1
  3. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_ace_backend.py +151 -564
  4. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4/pyqrack_complex128.egg-info}/PKG-INFO +1 -1
  5. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/setup.py +1 -1
  6. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/LICENSE +0 -0
  7. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/MANIFEST.in +0 -0
  8. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/README.md +0 -0
  9. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyproject.toml +0 -0
  10. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/__init__.py +0 -0
  11. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/neuron_activation_fn.py +0 -0
  12. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/pauli.py +0 -0
  13. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_circuit.py +0 -0
  14. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  15. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_neuron.py +0 -0
  16. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  17. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_simulator.py +0 -0
  18. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_stabilizer.py +0 -0
  19. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_system/__init__.py +0 -0
  20. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/qrack_system/qrack_system.py +0 -0
  21. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/quimb_circuit_type.py +0 -0
  22. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/stats/__init__.py +0 -0
  23. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/stats/load_quantized_data.py +0 -0
  24. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack/stats/quantize_by_range.py +0 -0
  25. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack_complex128.egg-info/SOURCES.txt +0 -0
  26. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack_complex128.egg-info/dependency_links.txt +0 -0
  27. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack_complex128.egg-info/not-zip-safe +0 -0
  28. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack_complex128.egg-info/requires.txt +0 -0
  29. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/pyqrack_complex128.egg-info/top_level.txt +0 -0
  30. {pyqrack_complex128-2.25.2 → pyqrack_complex128-2.25.4}/setup.cfg +0 -0
@@ -30,7 +30,7 @@ build-deps:
30
30
  rm -rf pyqrack/qrack_system/qrack_cl_precompile
31
31
  ifneq ($(OS),Windows_NT)
32
32
  ifeq ($(QRACK_PRESENT),)
33
- git clone https://github.com/unitaryfund/qrack.git; cd qrack; git checkout d979805bf326dfeeb477fb15a0a88278a2db6b92; cd ..
33
+ git clone https://github.com/unitaryfund/qrack.git; cd qrack; git checkout b140695ddde41ecd9099bad08e9cc0a32a46a9f9; cd ..
34
34
  endif
35
35
  mkdir -p qrack/build
36
36
  ifeq ($(UNAME_S),Linux)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-complex128
3
- Version: 2.25.2
3
+ Version: 2.25.4
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -457,26 +457,18 @@ class QrackAceBackend:
457
457
  # See _cpauli for the actual encode/couple/decode/correct cycle
458
458
  # built around these; this block is just the qubit-index
459
459
  # bookkeeping, in the same style as _detect_ancilla1/2 above.
460
- self._rep_code_ancilla1 = []
461
- self._rep_code_ancilla2 = []
460
+ self._rep_code_ancilla = []
462
461
  if self.is_boundary_repetition_code:
463
462
  for i in range(len(sim_counts)):
464
- self._rep_code_ancilla1.append(sim_counts[i])
465
- sim_counts[i] += 1
466
- self._rep_code_ancilla2.append(sim_counts[i])
463
+ self._rep_code_ancilla.append(sim_counts[i])
467
464
  sim_counts[i] += 1
468
465
 
469
- self._rep_code_ancilla1_lq = []
470
- self._rep_code_ancilla2_lq = []
466
+ self._rep_code_ancilla_lq = []
471
467
  if self.is_boundary_repetition_code:
472
- for sim_id, phys_idx in enumerate(self._rep_code_ancilla1):
473
- lq_idx = len(self._qubits)
474
- self._qubits.append([(sim_id, phys_idx)])
475
- self._rep_code_ancilla1_lq.append(lq_idx)
476
- for sim_id, phys_idx in enumerate(self._rep_code_ancilla2):
468
+ for sim_id, phys_idx in enumerate(self._rep_code_ancilla):
477
469
  lq_idx = len(self._qubits)
478
470
  self._qubits.append([(sim_id, phys_idx)])
479
- self._rep_code_ancilla2_lq.append(lq_idx)
471
+ self._rep_code_ancilla_lq.append(lq_idx)
480
472
 
481
473
  # capture calls (see _apply_coupling): those calls go through
482
474
  # the ordinary _cpauli -> _apply_coupling path themselves, which
@@ -1274,130 +1266,6 @@ class QrackAceBackend:
1274
1266
  if lhv is not None:
1275
1267
  lhv.adjsx()
1276
1268
 
1277
- def _rep_code_patch_triples(self, lq):
1278
- """(patch_replica, ancilla1_lq, ancilla2_lq) triples for lq's
1279
- non-crossbar patches -- one per patch this boundary qubit
1280
- touches. Recomputed fresh on every call (this is the on-demand
1281
- design: nothing persists between coupling events), rather than
1282
- stored once at construction the way the earlier, always-on
1283
- version did. Empty if lq is bulk (single-replica) or the
1284
- feature is off -- both cases where there's nothing to protect
1285
- or nowhere to protect it.
1286
- """
1287
- if not self.is_boundary_repetition_code:
1288
- return []
1289
- hq = self._qubits[lq]
1290
- if len(hq) < 2:
1291
- return []
1292
- triples = []
1293
- for replica in hq:
1294
- if self._boundary_sim_id is not None and replica[0] == self._boundary_sim_id:
1295
- continue
1296
- sim_id = replica[0]
1297
- triples.append((replica, self._rep_code_ancilla1_lq[sim_id], self._rep_code_ancilla2_lq[sim_id]))
1298
- return triples
1299
-
1300
- def _rep_code_encode(self, lq):
1301
- """Step 1: CNOT lq's replica-in-each-patch onto that patch's
1302
- dedicated, reusable ancilla1 -- a fresh, TEMPORARY repetition-
1303
- code copy, good for exactly the duration of one coupling event.
1304
- Returns the triples for the caller to pass to
1305
- _rep_code_extend_coupling and _rep_code_decode_and_correct, or
1306
- an empty list if lq isn't repetition-code eligible right now.
1307
- """
1308
- triples = self._rep_code_patch_triples(lq)
1309
- for (sim_id, b_idx), anc1_lq, _ in triples:
1310
- anc1_idx = self._qubits[anc1_lq][0][1]
1311
- self.sim[sim_id].mcx([b_idx], anc1_idx)
1312
- return triples
1313
-
1314
- def _rep_code_extend_coupling(self, pauli, anti, qb1, hq1, lq1_lr, lq1, triples):
1315
- """Step 2: extend the SAME coupling (same control) to each
1316
- freshly-encoded ancilla1 -- reuses _apply_coupling directly,
1317
- the same real/shadow dispatch every other target already gets.
1318
- Only meaningful for a TARGET's triples: a CONTROL's own value
1319
- is never touched by gate_fn/shadow_fn in the first place (the
1320
- established, control-invariance fact this whole file already
1321
- relies on), so there's nothing for a control's ancilla1 to need
1322
- extended to -- callers only pass a target's triples here.
1323
- """
1324
- for _, anc1_lq, _ in triples:
1325
- hq_a = self._qubits[anc1_lq]
1326
- qb_a, _ = QrackAceBackend._get_qb_lhv_indices(hq_a)
1327
- self._apply_coupling(pauli, anti, qb1, hq1, qb_a, hq_a, lq1_lr, lq1, anc1_lq)
1328
-
1329
- def _rep_code_decode_and_correct(self, lq, triples, phase=False):
1330
- """Steps 3-5: bit-flip syndrome-check-and-correct (using
1331
- ancilla2 per patch), THEN the final decode CNOT (lq's replica
1332
- -> ancilla1 again), which -- once any disagreement has already
1333
- been corrected -- returns ancilla1 exactly to |0>, ready for
1334
- the next boundary qubit's coupling event to reuse. An explicit
1335
- measure-and-reset backstops the decode CNOT for the rare
1336
- (corner-only) multi-disagreement case left uncorrected below,
1337
- so ancilla1 is unconditionally clean afterward either way.
1338
-
1339
- Same "try to force error-free, fall back to an unforced Born-
1340
- rule read, then it's obvious by elimination" decoding pattern
1341
- used elsewhere in this file. With N syndrome bits (2 for an
1342
- edge qubit's 3-qubit code, 4 for a corner's 5-qubit code): zero
1343
- disagreeing patches means no error; exactly one unambiguously
1344
- identifies that patch's ancilla1 as the outlier (every other
1345
- patch's ancilla1, by construction, still agrees with lq); a
1346
- strict majority (including unanimity) unambiguously identifies
1347
- lq itself as the outlier. A strict minority greater than one is
1348
- not explainable by any single-qubit error and is left
1349
- uncorrected -- guessing would risk introducing a wrong
1350
- correction where an unrelated, uncorrectable multi-qubit event
1351
- happened instead.
1352
- """
1353
- if not triples:
1354
- return
1355
-
1356
- syndrome = []
1357
- for (sim_id, b_idx), anc1_lq, anc2_lq in triples:
1358
- sim = self.sim[sim_id]
1359
- anc1_idx = self._qubits[anc1_lq][0][1]
1360
- anc2_idx = self._qubits[anc2_lq][0][1]
1361
-
1362
- sim.mcx([b_idx], anc2_idx)
1363
- sim.mcx([anc1_idx], anc2_idx)
1364
-
1365
- p = sim.prob(anc2_idx)
1366
- bit = sim.m(anc2_idx)
1367
- if bit:
1368
- sim.x(anc2_idx) # reset ancilla2 to |0> for reuse
1369
-
1370
- syndrome.append((bit, sim_id, anc1_idx))
1371
-
1372
- n = len(syndrome)
1373
- disagree = sum(1 for bit, _, _ in syndrome if bit)
1374
- if disagree > 0:
1375
- if disagree > n - disagree:
1376
- # Flip all bits
1377
- if phase:
1378
- self.z(lq)
1379
- else:
1380
- self.x(lq)
1381
- # Necessary either way, even if all flipped
1382
- for bit, sim_id, anc1_idx in syndrome:
1383
- if bit:
1384
- if phase:
1385
- self.sim[sim_id].z(anc1_idx)
1386
- else:
1387
- self.sim[sim_id].x(anc1_idx)
1388
-
1389
- for (sim_id, b_idx), anc1_lq, _ in triples:
1390
- anc1_idx = self._qubits[anc1_lq][0][1]
1391
- sim = self.sim[sim_id]
1392
- sim.mcx([b_idx], anc1_idx)
1393
- p = sim.prob(anc1_idx)
1394
- if self._ps_epsilon >= (1.0 - p):
1395
- b = sim.m(anc1_idx)
1396
- else:
1397
- b = sim.force_m(anc1_idx, False)
1398
- if b:
1399
- sim.x(anc1_idx)
1400
-
1401
1269
  def x(self, lq):
1402
1270
  hq = self._unpack(lq)
1403
1271
  if len(hq) < 2:
@@ -1543,75 +1411,41 @@ class QrackAceBackend:
1543
1411
  def _apply_coupling(self, pauli, anti, qb1, hq1, qb2, hq2, lq1_lr, lq1=None, lq2=None):
1544
1412
  shadow_targets = []
1545
1413
 
1546
- # Logical-level error-detection gadget: captures lq1's LOGICAL
1547
- # value (and lq2's too, for CZ specifically -- see below) into a
1548
- # dedicated hidden ancilla via a genuine, nested cx() call --
1549
- # NOT a raw capture of one specific physical replica. This
1550
- # matters: gate_fn/shadow_fn below never gate the control (only
1551
- # ever read it via .prob()), so lq1's LOGICAL value is
1552
- # structurally guaranteed not to change across this loop --
1553
- # that invariant holds regardless of what _correct() may have
1554
- # legitimately done to any INDIVIDUAL replica beforehand,
1555
- # because the nested cx(lq1, anc) call determines lq1's value
1556
- # the same robust way any other logical coupling would, not by
1557
- # trusting one physical replica directly (an earlier version of
1558
- # this tried exactly that and crashed: _correct() can
1559
- # legitimately need to change hq1[0]'s own value, so "hq1[0]
1560
- # never changes" isn't actually a valid invariant, even though
1561
- # "lq1's logical value never changes here" is).
1414
+ # Logical-level error-detection gadget: an earlier version of this
1415
+ # also captured lq1's own logical value into a dedicated ancilla
1416
+ # (via a nested cx()), on the theory that the shadow-coupling
1417
+ # machinery might introduce imprecision between two captures of a
1418
+ # value that structurally can't change. Instrumented and stress-
1419
+ # tested directly, across both the single-participant and N-way
1420
+ # (patch + adjacent patch + crossbar replica) majority-vote forms:
1421
+ # 0 firings out of 38,400 opportunities in a circuit deliberately
1422
+ # built to exercise it hard (many simultaneous, repeated bulk/
1423
+ # boundary couplings sharing the same per-patch ancillae). That's
1424
+ # consistent with this file's other same-qubit-round-trip
1425
+ # invariants (native swap, single-qubit gates) being structurally
1426
+ # exact rather than approximate here -- there was nothing left for
1427
+ # it to catch. Removed entirely, control-protection ancilla
1428
+ # allocation included.
1562
1429
  #
1563
- # The genuine syndrome signal comes from the capture itself: the
1564
- # nested cx() goes through the same _correct()/shadow-coupling
1565
- # machinery as any other logical operation, which can introduce
1566
- # some imprecision between the "before" and "after" capture even
1567
- # though lq1's true value didn't change -- that imprecision is
1568
- # exactly this architecture's own intrinsic (not noise=p)
1569
- # approximation error, which is what's worth catching.
1570
- #
1571
- # CZ also captures lq2 into the SAME ancilla: unlike CX/CY,
1572
- # where the target's value legitimately, intentionally changes
1573
- # (that's the entangling operation, not an error), CZ never
1574
- # changes either participant's Z-population on any input, so
1575
- # lq2's logical value is an equally valid invariant here, for
1576
- # this gate family only -- same reasoning as the earlier
1577
- # single-replica CZ dual-check, just applied at the logical
1578
- # level now.
1579
- anc1, anc2, anc1b, anc1c = None, None, None, None
1580
- if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1)):
1581
- anc1 = self._detect_ancilla1_lq[hq1[0][0]]
1430
+ # The XOR check below is a different invariant and stays: it
1431
+ # doesn't ask whether lq1's own value held still, it asks whether
1432
+ # the CORRELATION the shadow-coupling step just introduced between
1433
+ # lq1 and lq2 matches what a real controlled gate would have
1434
+ # produced -- and that did fire non-trivially (773/24,000, ~3.2%)
1435
+ # in the same stress test. For CZ specifically, lq2's own
1436
+ # population never legitimately changes either, so this doubles as
1437
+ # a lq2-invariant check for that gate family, same reasoning as
1438
+ # the single-replica CZ dual-check elsewhere.
1439
+ anc2 = None
1440
+ if self.is_error_detection and not self._in_gadget_capture and (len(hq1) > 1):
1441
+ anc2 = self._detect_ancilla2_lq[hq1[0][0]]
1582
1442
  self._in_gadget_capture = True
1583
- # Control bit-flip
1584
- self.cx(lq1, anc1)
1585
- if len(hq1) > 1:
1586
- if hq2[0][0] != hq1[0][0]:
1587
- anc1b = self._detect_ancilla1_lq[hq2[0][0]]
1588
- # Control bit-flip
1589
- self.cx(lq1, anc1b)
1590
-
1591
- # Third participant: hq1 is a boundary qubit here
1592
- # (len(hq1) > 1), so it always carries a genuine physical
1593
- # replica in the shared boundary "crossbar" simulator too
1594
- # -- index 2 of its replica list, by construction (see
1595
- # __init__'s qubit-building loop: the crossbar replica is
1596
- # always appended right after the (up to two) patch
1597
- # replicas, for every boundary qubit, edge or corner
1598
- # alike). That replica was never checked by this gadget
1599
- # before now, even though it's a distinct physical copy
1600
- # of lq1's value -- give it its own dedicated ancilla,
1601
- # already allocated in the crossbar sim alongside every
1602
- # other simulator's pair, and capture lq1 through it the
1603
- # same nested-cx way as anc1/anc1b, so it can vote
1604
- # alongside them below instead of going unchecked.
1605
- anc1c = self._detect_ancilla1_lq[hq1[2][0]]
1606
- self.cx(lq1, anc1c)
1607
-
1608
- anc2 = self._detect_ancilla2_lq[hq1[0][0]]
1609
- # XOR on target
1610
- self.cx(lq2, anc2)
1611
- if anti:
1612
- self.acx(lq1, anc2)
1613
- else:
1614
- self.cx(lq1, anc2)
1443
+ # XOR on target
1444
+ self.cx(lq2, anc2)
1445
+ if anti:
1446
+ self.acx(lq1, anc2)
1447
+ else:
1448
+ self.cx(lq1, anc2)
1615
1449
  self._in_gadget_capture = False
1616
1450
 
1617
1451
  # A target replica (q2) that shares a simulator with ANY control
@@ -1666,168 +1500,19 @@ class QrackAceBackend:
1666
1500
  shadow_fn(b1, b2)
1667
1501
  shadow_targets.append(b2)
1668
1502
 
1669
- is_flipped = False
1670
- if anc1 is not None:
1671
- self._in_gadget_capture = True
1672
- self.cx(lq1, anc1)
1673
- # Check prob() before forcing: force_m(anc, False)
1674
- # assumes the "agree" branch has nonzero probability,
1675
- # which usually holds (that's the whole invariant this
1676
- # gadget relies on) but isn't guaranteed -- if the
1677
- # architecture's own approximation genuinely, confidently
1678
- # drove lq1 (or lq2) to a different value between the
1679
- # two captures, the "agree" branch can have exactly zero
1680
- # probability, and forcing it crashes outright (verified
1681
- # directly: reproducible RuntimeError under realistic
1682
- # circuit depth). Accept that a genuine, confident
1683
- # disagreement occurred instead of forcing the
1684
- # impossible branch: force the TRUE condition (safe,
1685
- # since that branch is the one that actually carries the
1686
- # probability), reset the ancilla back to |0>, and apply
1687
- # a corrective X to lq1 to restore the detected
1688
- # invariant.
1689
- if (anc1b is not None) or (anc1c is not None):
1690
- # Generalized N-way version (N = 2 or 3, i.e. patch A and
1691
- # patch B and/or the crossbar's own replica) of the
1692
- # single-pair steering above: same per-participant rule,
1693
- # just no longer hand-written per pair. Read every
1694
- # participant's prob() BEFORE forcing any of them (a
1695
- # forced measurement can affect correlated others), find
1696
- # whichever one -- if any -- shows confident (within
1697
- # ps_epsilon of certain) evidence of an actual flip, force
1698
- # THAT one first (the branch its own probability actually
1699
- # supports, never the near-impossible one), then for each
1700
- # remaining participant either steer it to agree (force
1701
- # True) if its own signal is too close to the hard-zero
1702
- # boundary to trust as an honest independent reading, or
1703
- # let it measure honestly otherwise -- exactly the same
1704
- # judgment call the original single anc1b case made, now
1705
- # just applied per-participant in a loop. If nobody shows
1706
- # confident evidence of a flip, every participant is
1707
- # forced to the "no error" branch, as before.
1708
- ancs = [anc1] + ([anc1b] if anc1b is not None else []) + ([anc1c] if anc1c is not None else [])
1709
- # Complete each participant's before/after XOR capture
1710
- # (anc1's own second cx() already happened just above,
1711
- # unconditionally) before reading any prob() below.
1712
- if anc1b is not None:
1713
- self.cx(lq1, anc1b)
1714
- if anc1c is not None:
1715
- self.cx(lq1, anc1c)
1716
- probs = []
1717
- for a in ancs:
1718
- anc_sim, anc_idx = self._qubits[a][0]
1719
- probs.append(self.sim[anc_sim].prob(anc_idx))
1720
-
1721
- anchor = None
1722
- for i in range(len(ancs)):
1723
- if self._ps_epsilon >= (1.0 - probs[i]):
1724
- anchor = i
1725
- break
1726
-
1727
- bits = [None] * len(ancs)
1728
- if anchor is not None:
1729
- bits[anchor] = self.force_m(ancs[anchor], True)
1730
- for i in range(len(ancs)):
1731
- if i == anchor:
1732
- continue
1733
- if self._ps_epsilon >= probs[i]:
1734
- bits[i] = self.force_m(ancs[i], True)
1735
- else:
1736
- bits[i] = self.m(ancs[i])
1737
- else:
1738
- bits = [self.force_m(a, False) for a in ancs]
1739
-
1740
- # Map each participant to the SPECIFIC physical replica
1741
- # of hq1 its own syndrome actually covers -- anc1 is
1742
- # patch A (hq1[0]), anc1c is always the crossbar
1743
- # (hq1[2], by construction), and anc1b is whichever of
1744
- # hq1's replicas lives in hq2's own patch (found by sim
1745
- # id rather than assumed to be a fixed index, since
1746
- # corner qubits have patch replicas at indices 1, 3, and
1747
- # 4). This can legitimately be absent: hq1 and hq2 may
1748
- # share no common patch at all for an arbitrary (non-
1749
- # adjacent) logical coupling -- confirmed directly
1750
- # (hq1=[(2,1),(3,0),(4,2)] coupled to hq2=[(1,1)], no
1751
- # overlap) -- in which case there is no single physical
1752
- # replica to target for that participant specifically.
1753
- replicas = [hq1[0]]
1754
- if anc1b is not None:
1755
- replicas.append(next((r for r in hq1 if r[0] == hq2[0][0]), None))
1756
- if anc1c is not None:
1757
- replicas.append(hq1[2])
1758
-
1759
- # Majority vote decides whether the LOGICAL value itself
1760
- # genuinely flipped (updating the LHV shadow, same as the
1761
- # transversal self.x(lq1) used to do unconditionally);
1762
- # any minority participant is still individually
1763
- # corrected below regardless, since ITS replica is wrong
1764
- # either way -- it just isn't treated as evidence the
1765
- # logical value moved, the same way a repetition code's
1766
- # decoded value doesn't change just because one physical
1767
- # bit needed fixing.
1768
- n_flipped = sum(1 for b in bits if b)
1769
- if (n_flipped << 1) > len(ancs):
1770
- is_flipped = True
1771
- lhv = self._lhv.get(lq1)
1772
- if lhv is not None:
1773
- lhv.x()
1774
-
1775
- for i, a in enumerate(ancs):
1776
- if bits[i]:
1777
- self.x(a)
1778
- # Targeted, single-replica correction rather than
1779
- # a transversal self.x(lq1): these replicas are a
1780
- # pseudo-repetition code for lq1, and a
1781
- # participant flipping means THAT PHYSICAL COPY
1782
- # is the one in error, not the logical value as a
1783
- # whole. Flipping every replica transversally is
1784
- # only ever correct by coincidence when all
1785
- # participants agree -- on a genuine minority
1786
- # disagreement it would "fix" the actual error
1787
- # while simultaneously breaking the (correct)
1788
- # majority replicas that were never wrong. Fixing
1789
- # per-replica is correct in the agreeing case too
1790
- # (each flipped participant's own replica still
1791
- # gets corrected), so this applies unconditionally,
1792
- # not just when a disagreement is detected. Falls
1793
- # back to the transversal correction only when no
1794
- # single hq1 replica corresponds to this
1795
- # participant at all (see the no-overlap case
1796
- # above).
1797
- b = replicas[i]
1798
- if b is not None:
1799
- self.sim[b[0]].x(b[1])
1800
- else:
1801
- self.x(lq1)
1802
- else:
1803
- anc_sim, anc_idx = self._qubits[anc1][0]
1804
- p = self.sim[anc_sim].prob(anc_idx)
1805
- if self._ps_epsilon >= (1.0 - p):
1806
- b = self.m(anc1)
1807
- else:
1808
- b = self.force_m(anc1, False)
1809
- if b:
1810
- self.x(anc1)
1811
- self.x(lq1)
1812
- is_flipped = True
1813
- self._in_gadget_capture = False
1814
-
1815
1503
  if anc2 is not None:
1816
1504
  self._in_gadget_capture = True
1817
1505
  # XOR check
1818
1506
  self.cx(lq2, anc2)
1819
1507
  anc_sim, anc_idx = self._qubits[anc2][0]
1820
1508
  p = self.sim[anc_sim].prob(anc_idx)
1821
- # Probabilistic error cancellation
1822
- if not is_flipped:
1823
- p = 1.0 - p
1509
+ p = 1.0 - p
1824
1510
  if self._ps_epsilon >= p:
1825
1511
  b = self.m(anc2)
1826
1512
  else:
1827
- b = self.force_m(anc2, is_flipped)
1513
+ b = self.force_m(anc2, False)
1828
1514
  if b:
1829
1515
  self.x(anc2)
1830
- if b != is_flipped:
1831
1516
  self.x(lq2)
1832
1517
  self._in_gadget_capture = False
1833
1518
 
@@ -1865,91 +1550,70 @@ class QrackAceBackend:
1865
1550
  self._correct(lq1)
1866
1551
  self._correct(lq2)
1867
1552
 
1868
- # Boundary repetition code, on-demand: encode/couple/decode/
1869
- # correct wraps ONLY the actual coupler call immediately below,
1870
- # tightly -- not the transversal single-qubit gates (already
1871
- # exact, per-replica, nothing to protect), and not the outer
1872
- # cy()/cz() wrapper when this _cpauli call is their internal,
1873
- # H-sandwiched cx() (see below for why that's actually fine now,
1874
- # unlike the earlier design). Guarded by not
1875
- # self._in_gadget_capture the same way the _correct() calls
1876
- # above are: a nested capture's own target is always a hidden,
1877
- # single-replica ancilla (never boundary-eligible, so this would
1878
- # no-op anyway), but the CONTROL side could still be a protected
1879
- # boundary qubit, and re-running its own encode/check/decode
1880
- # cycle on every nested capture would be wasteful and redundant
1881
- # with the outer call already doing it once.
1882
- #
1883
- # lq1 (control) gets a SIMPLER cycle than lq2 (target): the
1884
- # coupler never gates the control at all (the same control-
1885
- # invariance fact this whole file already relies on), so there's
1886
- # no coupling effect to extend to its ancilla1 -- just verify/
1887
- # correct lq1's OWN reliability once, right before the coupler
1888
- # is about to read it, so a stale value doesn't feed a wrong
1889
- # shadow decision.
1890
- if self.is_boundary_repetition_code and not self._in_gadget_capture:
1891
- t1 = self._rep_code_encode(lq1)
1892
- self._rep_code_decode_and_correct(lq1, t1)
1893
-
1894
- # lq2 (target) encode happens BEFORE the coupler, so the SAME
1895
- # coupling can be extended to ancilla1 right after -- capturing
1896
- # a "before" snapshot the coupler's own effect then lands on
1897
- # too, the same real/shadow dispatch every other target gets.
1898
- t2 = []
1899
- if self.is_boundary_repetition_code and not self._in_gadget_capture:
1900
- t2 = self._rep_code_encode(lq2)
1901
- if t2:
1902
- # Only defined for "patch-local" connectivity, the only
1903
- # officially supported connectivity model here (get_
1904
- # logical_coupling_map() only ever returns pairs that
1905
- # share a simulator). The pure-shadow case (lq1, lq2
1906
- # share NO simulator at all) only exists as a fallback
1907
- # for users who ignore that map, and this repetition-
1908
- # code layer doesn't try to support it: raise plainly
1909
- # instead of silently coupling ancilla1 through an
1910
- # undefined/unsupported path.
1911
- sims1 = {b[0] for b in hq1}
1912
- sims2 = {b[0] for b in hq2}
1913
- if not (sims1 & sims2):
1914
- raise RuntimeError(
1915
- "Boundary repetition code: lq1 and lq2 share no simulator "
1916
- "(pure-shadow coupling, outside the officially supported "
1917
- "patch-local connectivity map) -- not supported with "
1918
- "is_boundary_repetition_code=True."
1919
- )
1553
+ t2 = [lq2]
1554
+ anc1 = None
1555
+ if self.is_boundary_repetition_code and (not self._in_gadget_capture) and lq1_lr and (not lq2_lr):
1556
+ # Encode:
1557
+ anc1 = self._rep_code_ancilla_lq[hq1[0][0]]
1558
+ self._in_gadget_capture = True
1559
+ self.cx(lq2, anc1)
1560
+ self._in_gadget_capture = False
1561
+ t2.append(anc1)
1920
1562
 
1921
1563
  qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
1922
- qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
1564
+
1923
1565
  # Apply cross coupling on every qubit, including former-LHV boundary
1924
1566
  # qubits, which now live as real qubits in the shared boundary sim.
1925
- self._apply_coupling(pauli, anti, qb1, hq1, qb2, hq2, lq1_lr, lq1, lq2)
1926
-
1927
- if t2:
1928
- self._rep_code_extend_coupling(pauli, anti, qb1, hq1, lq1_lr, lq1, t2)
1567
+ # Also apply across target repetition.
1568
+ for l2 in t2:
1569
+ h2 = self._unpack(l2)
1570
+ qb2, _ = QrackAceBackend._get_qb_lhv_indices(h2)
1571
+ self._apply_coupling(pauli, anti, qb1, hq1, qb2, h2, lq1_lr, lq1, l2)
1929
1572
 
1930
1573
  if lq2 in self._lhv:
1931
1574
  ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
1932
1575
  _cpauli_lhv(ctrl_prob, self._lhv[lq2], pauli, anti)
1933
1576
 
1577
+ if anc1 is not None:
1578
+ anc2 = self._detect_ancilla1_lq[hq1[0][0]]
1579
+ self._in_gadget_capture = True
1580
+ self.cx(lq2, anc2)
1581
+ self.cx(anc1, anc2)
1582
+ self._in_gadget_capture = False
1583
+
1584
+ # If anc2 shows a mismatch, it's more likely
1585
+ # that the boundary qubit is the one in error.
1586
+
1587
+ anc_sim, anc_idx = self._qubits[anc2][0]
1588
+ p = self.sim[anc_sim].prob(anc_idx)
1589
+ if self._ps_epsilon >= (1.0 - p):
1590
+ b = self.m(anc2)
1591
+ else:
1592
+ b = self.force_m(anc2, False)
1593
+ if b:
1594
+ self.x(anc2)
1595
+ self.x(lq2)
1596
+
1597
+ # Decode:
1598
+ self._in_gadget_capture = True
1599
+ self.cx(lq2, anc1)
1600
+ self._in_gadget_capture = False
1601
+
1602
+ # After we corrected any mismatch above, it's unlikely
1603
+ # that we can't post-select no-error on this ancilla.
1604
+
1605
+ anc_sim, anc_idx = self._qubits[anc1][0]
1606
+ p = self.sim[anc_sim].prob(anc_idx)
1607
+ if self._ps_epsilon >= (1.0 - p):
1608
+ b = self.m(anc1)
1609
+ else:
1610
+ b = self.force_m(anc1, False)
1611
+ if b:
1612
+ self.x(anc1)
1613
+
1934
1614
  if self._in_gadget_capture:
1935
1615
  return
1936
1616
 
1937
- # Repetition-code decode-and-correct for lq2 happens HERE,
1938
- # immediately after the coupler+extension, in the SAME frame
1939
- # they just ran in -- deliberately BEFORE _correct()'s own,
1940
- # separate soft reconciliation below, which can itself rotate
1941
- # lq2 (the phase=True call) and would otherwise pull it out of
1942
- # sync with an ancilla1 that never gets that same rotation. This
1943
- # is also exactly why cy()/cz()'s internal H-sandwich is safe
1944
- # now, unlike the earlier, always-on design: encode, couple,
1945
- # and this decode-and-correct are all contained within this one
1946
- # _cpauli call, whatever frame it happens to be running in --
1947
- # never spanning an external rotation the way permanently-
1948
- # synced partners, checked from a separate top-level call, used
1949
- # to.
1950
- if self.is_boundary_repetition_code:
1951
- self._rep_code_decode_and_correct(lq2, t2)
1952
-
1953
1617
  if pauli != Pauli.PauliZ:
1954
1618
  self._correct(lq2, False, pauli != Pauli.PauliX)
1955
1619
  if pauli != Pauli.PauliX:
@@ -2196,27 +1860,6 @@ class QrackAceBackend:
2196
1860
  # the right eligibility with it. Nothing else to move.
2197
1861
  return
2198
1862
 
2199
- # Boundary repetition code: redirect entirely to the 3-CNOT
2200
- # decomposition at the bottom of this method whenever EITHER
2201
- # side is multi-replica (repetition-code eligible), rather than
2202
- # extending each of the specialized fast/partial-match paths
2203
- # below individually with new, separately-unverified encode/
2204
- # decode logic. self.cx() already handles the on-demand encode/
2205
- # couple/decode/correct cycle correctly -- verified directly,
2206
- # including real bugs found and fixed there -- so this reuses
2207
- # that rather than re-deriving equivalent logic three more times
2208
- # under the same risk of another subtle mismatch. Costs the
2209
- # fast-path optimizations below for this specific case; given
2210
- # the fast/exact-match path additionally requires lq1 and lq2 to
2211
- # be TWO boundary qubits with identically-matching patch
2212
- # structure -- rare on its own -- the case being deferred is
2213
- # narrow.
2214
- if self.is_boundary_repetition_code and (len(hq1) > 1 or len(hq2) > 1):
2215
- self.cx(lq1, lq2)
2216
- self.cx(lq2, lq1)
2217
- self.cx(lq1, lq2)
2218
- return
2219
-
2220
1863
  # Boundary-to-boundary handling
2221
1864
  if (len(hq1) > 1) and (len(hq2) > 1):
2222
1865
  self.cx(lq1, lq2)
@@ -2316,7 +1959,7 @@ class QrackAceBackend:
2316
1959
  for _hq1 in non_matching:
2317
1960
  self._cx_shadow(_hq2, _hq1)
2318
1961
  for _hq1 in matching:
2319
- self.sim[_hq2[0]].swap(_hq2[1], _hq2[1])
1962
+ self.sim[_hq2[0]].swap(_hq2[1], _hq1[1])
2320
1963
  for _hq1 in non_matching:
2321
1964
  self._cx_shadow(_hq2, _hq1)
2322
1965
  self._correct(lq1)
@@ -2377,7 +2020,7 @@ class QrackAceBackend:
2377
2020
  self.mcx([c1, c2], t)
2378
2021
  return
2379
2022
 
2380
- if self.is_boundary_repetition_code or (len(hqt) > 1):
2023
+ if len(hqt) > 1:
2381
2024
  self.h(t)
2382
2025
  self.cx(c2, t)
2383
2026
  self.adjt(t)
@@ -2400,28 +2043,55 @@ class QrackAceBackend:
2400
2043
  self._correct(c1)
2401
2044
  self._correct(c2)
2402
2045
 
2403
- anc1, anc2, anc1b, anc2b = None, None, None, None
2046
+ # Control-protection ancillas (anc1/anc1b/anc2/anc2b, checking
2047
+ # whether c1/c2's own values held still) removed entirely, same
2048
+ # basis as _apply_coupling: instrumented and stress-tested
2049
+ # directly -- 3 of the 4 (anc1, anc1b, anc2) never fired across
2050
+ # ~21,600 opportunities in a circuit built to exercise them hard,
2051
+ # and the 4th (anc2b) only fired at a modest ~1.5% rate. Dropped
2052
+ # regardless of that last one, on the same call as
2053
+ # _apply_coupling's equivalent mechanism.
2054
+ #
2055
+ # AND-invariant, generalized from _apply_coupling's CX/CZ XOR
2056
+ # check to the Toffoli's actual relationship: t should end up as
2057
+ # t_before XOR (c1 AND c2), not just t_before XOR c1 or XOR c2
2058
+ # individually. mcx is the native AND-conditioned flip, so it
2059
+ # plays the same role here that a plain cx(control, anc) played
2060
+ # in the 2-qubit case: capture t's pre-gate value, then fold in
2061
+ # c1 AND c2 via a genuine nested mcx() (which goes through the
2062
+ # same shadow/_correct machinery as any other logical gate, so
2063
+ # this is exposed to the same class of intrinsic approximation
2064
+ # error the CX/CZ check catches, not a raw single-replica read).
2065
+ # Uses t's own patch ancilla2 array, since t itself is guaranteed
2066
+ # bulk on this path (the len(hqt) > 1 case returns earlier,
2067
+ # above) and nothing else in this method uses that array anymore.
2068
+ anc_and = None
2404
2069
  if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1)):
2405
2070
  self._in_gadget_capture = True
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
-
2071
+ anc_and = self._detect_ancilla2_lq[hqt[0][0]]
2072
+ self.cx(t, anc_and)
2073
+ # self.mcx() only supports a single control at the logical
2074
+ # level (a syntax-convenience wrapper, confirmed directly --
2075
+ # it raises for 2 controls), so it can't do the AND capture
2076
+ # itself. When c1 and c2 both happen to have a physical
2077
+ # replica in t's own home-patch simulator (the same one
2078
+ # anc_and lives in), use a genuine native mcx there directly
2079
+ # -- exact, and a clean, independent reference to compare the
2080
+ # real Toffoli against. Otherwise, fall back to a recursive
2081
+ # ccx(c1, c2, anc_and): safe from infinite recursion here
2082
+ # specifically because _in_gadget_capture is already True, so
2083
+ # the recursive call's own "not self._in_gadget_capture" gate
2084
+ # skips setting up a second anc_and, and just performs the
2085
+ # ordinary native-witness-or-shadow Toffoli computation on
2086
+ # anc_and instead.
2087
+ anc_sim = hqt[0][0]
2088
+ c1_idx = next((r[1] for r in hq1 if r[0] == anc_sim), None)
2089
+ c2_idx = next((r[1] for r in hq2 if r[0] == anc_sim), None)
2090
+ if (c1_idx is not None) and (c2_idx is not None):
2091
+ _, anc_and_idx = self._qubits[anc_and][0]
2092
+ self.sim[anc_sim].mcx([c1_idx, c2_idx], anc_and_idx)
2093
+ else:
2094
+ self.ccx(c1, c2, anc_and)
2425
2095
  self._in_gadget_capture = False
2426
2096
 
2427
2097
  qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
@@ -2451,101 +2121,18 @@ class QrackAceBackend:
2451
2121
 
2452
2122
  self._in_gadget_capture = True
2453
2123
 
2454
- if anc1 is not None:
2455
- # Syndrome
2456
- self.cx(c1, anc1)
2457
-
2458
- # Post-selection
2459
- anc_sim, anc_idx = self._qubits[anc1][0]
2124
+ if anc_and is not None:
2125
+ self.cx(t, anc_and)
2126
+ anc_sim, anc_idx = self._qubits[anc_and][0]
2460
2127
  p = self.sim[anc_sim].prob(anc_idx)
2461
- is_flipped = False
2462
- if self._ps_epsilon >= (1.0 - p):
2463
- b1 = self.m(anc1)
2464
- else:
2465
- b1 = self.force_m(anc1, False)
2466
- if b1:
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)
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
2507
- anc_sim, anc_idx = self._qubits[anc2][0]
2508
- p = self.sim[anc_sim].prob(anc_idx)
2509
- is_flipped = False
2510
- if self._ps_epsilon >= (1.0 - p):
2511
- b1 = self.m(anc2)
2128
+ p = 1.0 - p
2129
+ if self._ps_epsilon >= p:
2130
+ b = self.m(anc_and)
2512
2131
  else:
2513
- b1 = self.force_m(anc2, False)
2514
- if b1:
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)
2132
+ b = self.force_m(anc_and, False)
2133
+ if b:
2134
+ self.x(anc_and)
2135
+ self.x(t)
2549
2136
 
2550
2137
  self._in_gadget_capture = False
2551
2138
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-complex128
3
- Version: 2.25.2
3
+ Version: 2.25.4
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.2"
10
+ VERSION = "2.25.4"
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')