pyqrack-cpu-complex128 2.21.2__tar.gz → 2.22.0__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_complex128-2.21.2/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.22.0}/PKG-INFO +1 -1
  2. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_ace_backend.py +21 -284
  3. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
  4. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/setup.py +1 -1
  5. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/LICENSE +0 -0
  6. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/MANIFEST.in +0 -0
  7. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/Makefile +0 -0
  8. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/README.md +0 -0
  9. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyproject.toml +0 -0
  10. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/__init__.py +0 -0
  11. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/neuron_activation_fn.py +0 -0
  12. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/pauli.py +0 -0
  13. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_circuit.py +0 -0
  14. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  15. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_neuron.py +0 -0
  16. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  17. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_simulator.py +0 -0
  18. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_stabilizer.py +0 -0
  19. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_system/__init__.py +0 -0
  20. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/qrack_system/qrack_system.py +0 -0
  21. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/quimb_circuit_type.py +0 -0
  22. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/stats/__init__.py +0 -0
  23. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/stats/load_quantized_data.py +0 -0
  24. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack/stats/quantize_by_range.py +0 -0
  25. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +0 -0
  26. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
  27. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
  28. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
  29. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
  30. {pyqrack_cpu_complex128-2.21.2 → pyqrack_cpu_complex128-2.22.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.21.2
3
+ Version: 2.22.0
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,6 @@
7
7
  import math
8
8
  import os
9
9
  import random
10
- from collections import deque
11
10
 
12
11
  from .qrack_system import Qrack
13
12
  from .qrack_simulator import QrackSimulator
@@ -238,7 +237,6 @@ class QrackAceBackend:
238
237
  is_host_pointer=(True if os.environ.get("PYQRACK_HOST_POINTER_DEFAULT_ON") else False),
239
238
  is_near_clifford_tableau_writer=False,
240
239
  noise=0,
241
- history_window=0,
242
240
  is_torus=True,
243
241
  is_1d_chain=False,
244
242
  is_error_detection=True,
@@ -250,7 +248,6 @@ class QrackAceBackend:
250
248
  long_range_columns = to_clone.long_range_columns
251
249
  long_range_rows = to_clone.long_range_rows
252
250
  is_transpose = to_clone.is_transpose
253
- history_window = to_clone.history_window
254
251
  is_torus = to_clone.is_torus
255
252
  is_1d_chain = to_clone.is_1d_chain
256
253
  is_error_detection = to_clone.is_error_detection
@@ -259,15 +256,12 @@ class QrackAceBackend:
259
256
  qubit_count = 0
260
257
  if long_range_columns < 0:
261
258
  long_range_columns = 0
262
- if history_window < 0:
263
- history_window = 0
264
259
 
265
260
  self.is_1d_chain = is_1d_chain
266
261
  self._factor_width(qubit_count, is_transpose)
267
262
  self.long_range_columns = long_range_columns
268
263
  self.long_range_rows = long_range_rows
269
264
  self.is_transpose = is_transpose
270
- self.history_window = history_window
271
265
  self.is_torus = is_torus
272
266
  self.is_error_detection = is_error_detection
273
267
  self.is_boundary_repetition_code = is_boundary_repetition_code
@@ -343,28 +337,6 @@ class QrackAceBackend:
343
337
 
344
338
  self._qubits = []
345
339
  self._lhv = {}
346
- if self.history_window > 0:
347
- if to_clone and to_clone._coupling_history is not None:
348
- self._coupling_history = {
349
- k: deque(v, maxlen=self.history_window)
350
- for k, v in to_clone._coupling_history.items()
351
- }
352
- self._coupling_history_rev = dict(to_clone._coupling_history_rev)
353
- self._pending_skip = {k: set(v) for k, v in to_clone._pending_skip.items()}
354
- self._stale_replicas = {k: set(v) for k, v in to_clone._stale_replicas.items()}
355
- self._witness_map = {k: dict(v) for k, v in to_clone._witness_map.items()}
356
- else:
357
- self._coupling_history = {}
358
- self._coupling_history_rev = {}
359
- self._pending_skip = {}
360
- self._stale_replicas = {}
361
- self._witness_map = {}
362
- else:
363
- self._coupling_history = None
364
- self._coupling_history_rev = None
365
- self._pending_skip = None
366
- self._stale_replicas = None
367
- self._witness_map = None
368
340
  sim_counts = [0] * sim_count
369
341
  sim_id = 0
370
342
  tot_qubits = 0
@@ -891,10 +863,8 @@ class QrackAceBackend:
891
863
  end_caps = [p0, p1, p3, p4]
892
864
  # Classify each end-cap as decisively high / decisively low /
893
865
  # undecided (within epsilon of 0.5), rather than a bare >=0.5
894
- # check -- a replica reading EXACTLY 0.5 (e.g. one just
895
- # H-reverted by _revert_shadow_commitment, genuinely carrying
896
- # zero information) was previously always counted as "high"
897
- # via this boundary convention alone.
866
+ # check -- a replica reading EXACTLY 0.5 was previously always
867
+ # counted as "high" via this boundary convention alone.
898
868
  high_count = sum(1 for x in end_caps if x > (0.5 + self._epsilon))
899
869
  low_count = sum(1 for x in end_caps if x < (0.5 - self._epsilon))
900
870
  undecided_count = len(end_caps) - high_count - low_count
@@ -1154,17 +1124,14 @@ class QrackAceBackend:
1154
1124
  hq = self._unpack(lq)
1155
1125
  if len(hq) < 2:
1156
1126
  b = hq[0]
1157
- self._invalidate_for_gate(lq)
1158
1127
  self.sim[b[0]].u(b[1], th, ph, lm)
1159
1128
  return
1160
1129
 
1161
- skip = self._invalidate_for_gate(lq)
1162
1130
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1163
1131
 
1164
1132
  for q in qb:
1165
1133
  b = hq[q]
1166
- if (b[0], b[1]) not in skip:
1167
- self.sim[b[0]].u(b[1], th, ph, lm)
1134
+ self.sim[b[0]].u(b[1], th, ph, lm)
1168
1135
 
1169
1136
  lhv = self._lhv.get(lq)
1170
1137
  if lhv is not None:
@@ -1172,87 +1139,18 @@ class QrackAceBackend:
1172
1139
 
1173
1140
  # Correction deferred to next 2-qubit gate (_cpauli calls _correct)
1174
1141
 
1175
- def _revert_shadow_commitment(self, lq1):
1176
- """Revert shadow-committed replicas recorded under lq1. H-reverts
1177
- each committed shadow replica (undoing the stale Z-basis commitment),
1178
- stores the reverted tuples in _pending_skip[lq2] so the NEXT gate on
1179
- that boundary qubit skips those replicas entirely (rotating an H-reverted
1180
- shadow replica from the wrong starting state gives a wrong result; the
1181
- right behavior is to skip it and let the next coupling gate re-derive
1182
- from scratch), and returns the reverted set for the immediate caller to
1183
- skip as well (for case A, where the control's own rotation should also
1184
- skip its own shadow replicas that were just un-committed)."""
1185
- if self._coupling_history is None:
1186
- return set()
1187
- hist = self._coupling_history.pop(lq1, None)
1188
- if hist is None:
1189
- return set()
1190
- reverted_all = set()
1191
- for lq2, targets in hist:
1192
- reverted = set()
1193
- for t_sim, t_idx in targets:
1194
- self.sim[t_sim].h(t_idx)
1195
- reverted.add((t_sim, t_idx))
1196
- # Store as pending skip for the NEXT gate on lq2 (case B):
1197
- # when the boundary qubit's own gate runs, it should skip
1198
- # these replicas rather than applying its gate to the freshly-
1199
- # H-reverted, uncommitted state.
1200
- if reverted:
1201
- existing = self._pending_skip.get(lq2, set())
1202
- self._pending_skip[lq2] = existing | reverted
1203
- existing_stale = self._stale_replicas.get(lq2, set())
1204
- self._stale_replicas[lq2] = existing_stale | reverted
1205
- self._coupling_history_rev.pop(lq2, None)
1206
- reverted_all |= reverted
1207
- return reverted_all
1208
-
1209
- def _invalidate_for_gate(self, lq):
1210
- """Called by every single-qubit gate dispatcher. Returns the set of
1211
- (sim_id, idx) shadow replicas that should be SKIPPED in the calling
1212
- gate's own application loop.
1213
- - Case A: lq is the coherent-partner (lq1) in a live history entry.
1214
- A gate on the control side invalidates the shadow commitment: reverts
1215
- the shadow targets via H (returning them to uncommitted 0.5), stores
1216
- them in pending_skip[lq2] for the NEXT gate on the boundary qubit,
1217
- and returns them for the immediate caller to skip as well (since the
1218
- control's own replica at slot0 may also be in the reverted set).
1219
- - Case B: lq is the boundary recipient (lq2) of a commitment that a
1220
- prior case-A revert already H-reverted. Consumes the pending_skip
1221
- set so the boundary gate doesn't apply itself to the freshly-
1222
- uncommitted replicas (which would give the wrong result from the
1223
- wrong starting state). Case B NEVER triggers a fresh revert --
1224
- only case A does, because only a gate on the control side actually
1225
- invalidates the commitment; a gate on the boundary side just needs
1226
- to avoid corrupting the already-reverted starting state."""
1227
- if self._coupling_history is None:
1228
- return set()
1229
- skip = set()
1230
- # Case A: lq is the coherent control side -- fire the revert.
1231
- if lq in self._coupling_history:
1232
- skip |= self._revert_shadow_commitment(lq)
1233
- # Case B: lq is the boundary side -- ONLY consume pending_skip,
1234
- # never trigger a fresh revert (that would incorrectly revert
1235
- # a still-valid commitment that no control-side gate has touched).
1236
- pending = self._pending_skip.pop(lq, None)
1237
- if pending:
1238
- skip |= pending
1239
- return skip
1240
-
1241
1142
  def r(self, p, th, lq):
1242
1143
  hq = self._unpack(lq)
1243
1144
  if len(hq) < 2:
1244
1145
  b = hq[0]
1245
- self._invalidate_for_gate(lq)
1246
1146
  self.sim[b[0]].r(p, th, b[1])
1247
1147
  return
1248
1148
 
1249
1149
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1250
- skip = self._invalidate_for_gate(lq)
1251
1150
 
1252
1151
  for q in qb:
1253
1152
  b = hq[q]
1254
- if (b[0], b[1]) not in skip:
1255
- self.sim[b[0]].r(p, th, b[1])
1153
+ self.sim[b[0]].r(p, th, b[1])
1256
1154
 
1257
1155
  lhv = self._lhv.get(lq)
1258
1156
  if lhv is not None:
@@ -1269,17 +1167,14 @@ class QrackAceBackend:
1269
1167
  hq = self._unpack(lq)
1270
1168
  if len(hq) < 2:
1271
1169
  b = hq[0]
1272
- self._invalidate_for_gate(lq)
1273
1170
  self.sim[b[0]].h(b[1])
1274
1171
  return
1275
1172
 
1276
- skip = self._invalidate_for_gate(lq)
1277
1173
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1278
1174
 
1279
1175
  for q in qb:
1280
1176
  b = hq[q]
1281
- if (b[0], b[1]) not in skip:
1282
- self.sim[b[0]].h(b[1])
1177
+ self.sim[b[0]].h(b[1])
1283
1178
 
1284
1179
  lhv = self._lhv.get(lq)
1285
1180
  if lhv is not None:
@@ -1291,17 +1186,14 @@ class QrackAceBackend:
1291
1186
  hq = self._unpack(lq)
1292
1187
  if len(hq) < 2:
1293
1188
  b = hq[0]
1294
- self._invalidate_for_gate(lq)
1295
1189
  self.sim[b[0]].s(b[1])
1296
1190
  return
1297
1191
 
1298
- skip = self._invalidate_for_gate(lq)
1299
1192
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1300
1193
 
1301
1194
  for q in qb:
1302
1195
  b = hq[q]
1303
- if (b[0], b[1]) not in skip:
1304
- self.sim[b[0]].s(b[1])
1196
+ self.sim[b[0]].s(b[1])
1305
1197
 
1306
1198
  lhv = self._lhv.get(lq)
1307
1199
  if lhv is not None:
@@ -1311,17 +1203,14 @@ class QrackAceBackend:
1311
1203
  hq = self._unpack(lq)
1312
1204
  if len(hq) < 2:
1313
1205
  b = hq[0]
1314
- self._invalidate_for_gate(lq)
1315
1206
  self.sim[b[0]].adjs(b[1])
1316
1207
  return
1317
1208
 
1318
- skip = self._invalidate_for_gate(lq)
1319
1209
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1320
1210
 
1321
1211
  for q in qb:
1322
1212
  b = hq[q]
1323
- if (b[0], b[1]) not in skip:
1324
- self.sim[b[0]].adjs(b[1])
1213
+ self.sim[b[0]].adjs(b[1])
1325
1214
 
1326
1215
  lhv = self._lhv.get(lq)
1327
1216
  if lhv is not None:
@@ -1331,17 +1220,14 @@ class QrackAceBackend:
1331
1220
  hq = self._unpack(lq)
1332
1221
  if len(hq) < 2:
1333
1222
  b = hq[0]
1334
- self._invalidate_for_gate(lq)
1335
1223
  self.sim[b[0]].sx(b[1])
1336
1224
  return
1337
1225
 
1338
- skip = self._invalidate_for_gate(lq)
1339
1226
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1340
1227
 
1341
1228
  for q in qb:
1342
1229
  b = hq[q]
1343
- if (b[0], b[1]) not in skip:
1344
- self.sim[b[0]].sx(b[1])
1230
+ self.sim[b[0]].sx(b[1])
1345
1231
 
1346
1232
  lhv = self._lhv.get(lq)
1347
1233
  if lhv is not None:
@@ -1351,17 +1237,14 @@ class QrackAceBackend:
1351
1237
  hq = self._unpack(lq)
1352
1238
  if len(hq) < 2:
1353
1239
  b = hq[0]
1354
- self._invalidate_for_gate(lq)
1355
1240
  self.sim[b[0]].adjsx(b[1])
1356
1241
  return
1357
1242
 
1358
- skip = self._invalidate_for_gate(lq)
1359
1243
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1360
1244
 
1361
1245
  for q in qb:
1362
1246
  b = hq[q]
1363
- if (b[0], b[1]) not in skip:
1364
- self.sim[b[0]].adjsx(b[1])
1247
+ self.sim[b[0]].adjsx(b[1])
1365
1248
 
1366
1249
  lhv = self._lhv.get(lq)
1367
1250
  if lhv is not None:
@@ -1495,17 +1378,14 @@ class QrackAceBackend:
1495
1378
  hq = self._unpack(lq)
1496
1379
  if len(hq) < 2:
1497
1380
  b = hq[0]
1498
- self._invalidate_for_gate(lq)
1499
1381
  self.sim[b[0]].x(b[1])
1500
1382
  return
1501
1383
 
1502
- skip = self._invalidate_for_gate(lq)
1503
1384
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1504
1385
 
1505
1386
  for q in qb:
1506
1387
  b = hq[q]
1507
- if (b[0], b[1]) not in skip:
1508
- self.sim[b[0]].x(b[1])
1388
+ self.sim[b[0]].x(b[1])
1509
1389
 
1510
1390
  lhv = self._lhv.get(lq)
1511
1391
  if lhv is not None:
@@ -1515,17 +1395,14 @@ class QrackAceBackend:
1515
1395
  hq = self._unpack(lq)
1516
1396
  if len(hq) < 2:
1517
1397
  b = hq[0]
1518
- self._invalidate_for_gate(lq)
1519
1398
  self.sim[b[0]].y(b[1])
1520
1399
  return
1521
1400
 
1522
- skip = self._invalidate_for_gate(lq)
1523
1401
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1524
1402
 
1525
1403
  for q in qb:
1526
1404
  b = hq[q]
1527
- if (b[0], b[1]) not in skip:
1528
- self.sim[b[0]].y(b[1])
1405
+ self.sim[b[0]].y(b[1])
1529
1406
 
1530
1407
  lhv = self._lhv.get(lq)
1531
1408
  if lhv is not None:
@@ -1535,17 +1412,14 @@ class QrackAceBackend:
1535
1412
  hq = self._unpack(lq)
1536
1413
  if len(hq) < 2:
1537
1414
  b = hq[0]
1538
- self._invalidate_for_gate(lq)
1539
1415
  self.sim[b[0]].z(b[1])
1540
1416
  return
1541
1417
 
1542
- skip = self._invalidate_for_gate(lq)
1543
1418
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1544
1419
 
1545
1420
  for q in qb:
1546
1421
  b = hq[q]
1547
- if (b[0], b[1]) not in skip:
1548
- self.sim[b[0]].z(b[1])
1422
+ self.sim[b[0]].z(b[1])
1549
1423
 
1550
1424
  lhv = self._lhv.get(lq)
1551
1425
  if lhv is not None:
@@ -1555,17 +1429,14 @@ class QrackAceBackend:
1555
1429
  hq = self._unpack(lq)
1556
1430
  if len(hq) < 2:
1557
1431
  b = hq[0]
1558
- self._invalidate_for_gate(lq)
1559
1432
  self.sim[b[0]].t(b[1])
1560
1433
  return
1561
1434
 
1562
- skip = self._invalidate_for_gate(lq)
1563
1435
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1564
1436
 
1565
1437
  for q in qb:
1566
1438
  b = hq[q]
1567
- if (b[0], b[1]) not in skip:
1568
- self.sim[b[0]].t(b[1])
1439
+ self.sim[b[0]].t(b[1])
1569
1440
 
1570
1441
  lhv = self._lhv.get(lq)
1571
1442
  if lhv is not None:
@@ -1575,17 +1446,14 @@ class QrackAceBackend:
1575
1446
  hq = self._unpack(lq)
1576
1447
  if len(hq) < 2:
1577
1448
  b = hq[0]
1578
- self._invalidate_for_gate(lq)
1579
1449
  self.sim[b[0]].adjt(b[1])
1580
1450
  return
1581
1451
 
1582
- skip = self._invalidate_for_gate(lq)
1583
1452
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1584
1453
 
1585
1454
  for q in qb:
1586
1455
  b = hq[q]
1587
- if (b[0], b[1]) not in skip:
1588
- self.sim[b[0]].adjt(b[1])
1456
+ self.sim[b[0]].adjt(b[1])
1589
1457
 
1590
1458
  lhv = self._lhv.get(lq)
1591
1459
  if lhv is not None:
@@ -1650,16 +1518,6 @@ class QrackAceBackend:
1650
1518
 
1651
1519
  def _apply_coupling(self, pauli, anti, qb1, hq1, qb2, hq2, lq1_lr, lq1=None, lq2=None):
1652
1520
  shadow_targets = []
1653
- witnessed_targets = [] # (shadow_target, witness_replica) pairs --
1654
- # "easy case": a replica of the SAME target
1655
- # logical qubit lives in the SAME simulator
1656
- # as the control replica used for this
1657
- # shadow pair, and is therefore always
1658
- # exactly correct regardless of how many
1659
- # later gates happen to the control. No
1660
- # bounded window, no reactive invalidation
1661
- # needed for these -- see
1662
- # _resolve_witnessed_shadow.
1663
1521
 
1664
1522
  # Logical-level error-detection gadget: captures lq1's LOGICAL
1665
1523
  # value (and lq2's too, for CZ specifically -- see below) into a
@@ -1766,8 +1624,6 @@ class QrackAceBackend:
1766
1624
  elif (q2 not in real_gated_q2) and (lq1_lr or (b1[1] == b2[1])):
1767
1625
  shadow_fn(b1, b2)
1768
1626
  shadow_targets.append(b2)
1769
- if witness is not None and witness != b2:
1770
- witnessed_targets.append((b2, witness))
1771
1627
 
1772
1628
  is_flipped = False
1773
1629
  if anc1 is not None:
@@ -1853,30 +1709,6 @@ class QrackAceBackend:
1853
1709
  self.x(lq2)
1854
1710
  self._in_gadget_capture = False
1855
1711
 
1856
- if lq2 is not None and witnessed_targets and self._witness_map is not None:
1857
- wmap = self._witness_map.setdefault(lq2, {})
1858
- for target_replica, witness_replica in witnessed_targets:
1859
- wmap[target_replica] = witness_replica
1860
-
1861
- if (
1862
- self._coupling_history is not None
1863
- and lq1 is not None
1864
- and lq2 is not None
1865
- and shadow_targets
1866
- ):
1867
- # Only the NON-witnessed shadow targets need the bounded,
1868
- # reactive history/invalidation machinery -- witnessed ones are
1869
- # always resolvable directly and don't need tracking here at all.
1870
- witnessed_set = {t for t, _ in witnessed_targets}
1871
- unwitnessed = tuple(t for t in shadow_targets if t not in witnessed_set)
1872
- if unwitnessed:
1873
- entry = (lq2, tuple(unwitnessed))
1874
- hist = self._coupling_history.setdefault(
1875
- lq1, deque(maxlen=self.history_window)
1876
- )
1877
- hist.append(entry)
1878
- self._coupling_history_rev[lq2] = lq1
1879
-
1880
1712
  # A "coupled twirling" scheme was tried here -- after every real
1881
1713
  # inter-logical-qubit coupling gate, SWAP which physical qubit is
1882
1714
  # registered as hq[side] with one of its level-0 scratch qubits, to
@@ -2089,35 +1921,6 @@ class QrackAceBackend:
2089
1921
  sims2 = {r[0] for r in hq2}
2090
1922
 
2091
1923
  if sims1.isdisjoint(sims2):
2092
- # Zero shared physical substrate: a SWAP is, by definition,
2093
- # just "exchange which logical index refers to which
2094
- # subsystem" -- for two qubits with no shared simulator or
2095
- # replica at all, that IS the swap, exactly, with zero gates
2096
- # and zero error, regardless of what either is entangled
2097
- # with elsewhere (SWAP is a relabeling of tensor-factor
2098
- # indices; nothing about that depends on which name we call
2099
- # either subsystem). This also sidesteps the separate
2100
- # _apply_coupling local-index-match limitation entirely for
2101
- # this case, since no shadow coupling is attempted between
2102
- # these qubits at all.
2103
- #
2104
- # Must fully resolve any pending shadow/consensus state tied
2105
- # to EACH qubit's CURRENT identity before relabeling --
2106
- # otherwise a cross-reference recorded under the old identity
2107
- # (e.g. coupling_history[lq1], witness_map[lq2]) would
2108
- # silently end up describing the wrong physical qubit
2109
- # afterward. Reuses the same resolution methods prob() and
2110
- # every single-qubit gate already depend on for correctness,
2111
- # rather than hand-rolling new bookkeeping that could miss a
2112
- # cross-reference.
2113
- self._invalidate_for_gate(lq1)
2114
- self._invalidate_for_gate(lq2)
2115
- if len(hq1) >= 2:
2116
- self._resolve_witnessed_shadow(lq1)
2117
- self._resolve_pending_skip(lq1)
2118
- if len(hq2) >= 2:
2119
- self._resolve_witnessed_shadow(lq2)
2120
- self._resolve_pending_skip(lq2)
2121
1924
  self._correct(lq1)
2122
1925
  self._correct(lq2)
2123
1926
 
@@ -2164,6 +1967,13 @@ class QrackAceBackend:
2164
1967
  self.cx(lq1, lq2)
2165
1968
  return
2166
1969
 
1970
+ # Boundary-to-boundary handling
1971
+ if (len(hq1) > 1) and (len(hq2) > 1):
1972
+ self.cx(lq1, lq2)
1973
+ self.cx(lq2, lq1)
1974
+ self.cx(lq1, lq2)
1975
+ return
1976
+
2167
1977
  # Fast/exact path: every replica of lq1 lines up, position-for-
2168
1978
  # position, with the corresponding replica of lq2 on the SAME
2169
1979
  # underlying simulator. A native swap() there is pure index
@@ -2308,85 +2118,12 @@ class QrackAceBackend:
2308
2118
  self.cz(lq1, lq2)
2309
2119
  self.swap(lq1, lq2)
2310
2120
 
2311
- def _resolve_pending_skip(self, lq):
2312
- """If lq has replicas recorded as STALE (H-reverted after an
2313
- invalidating control-side rotation, then skipped by their own
2314
- later gate because _invalidate_for_gate's Case B already popped
2315
- _pending_skip to decide what to skip -- consuming that record
2316
- before it could ever reach measurement time), force those
2317
- specific replicas to match hq[0] directly.
2318
-
2319
- NOTE: this deliberately does NOT read _pending_skip, because
2320
- _invalidate_for_gate's Case B already pops (consumes) it the
2321
- moment the boundary qubit's own next gate runs -- by the time
2322
- prob()/m() is called at actual measurement time, _pending_skip is
2323
- already empty even though the replicas never got their intended
2324
- rotation. _stale_replicas is a SEPARATE record, populated
2325
- alongside _pending_skip in _revert_shadow_commitment, that is
2326
- NOT touched by Case B's pop -- only by this method, at the point
2327
- where staleness actually needs to be resolved.
2328
-
2329
- Forcing to match hq[0] requires no randomness (unlike an earlier
2330
- force-correlate-with-control attempt) because hq[0] -- the
2331
- replica sharing a real simulator with the control, via the exact
2332
- gate_fn path in _apply_coupling -- is never itself added to
2333
- shadow_targets/_pending_skip/_stale_replicas, and is verified
2334
- exact following the _ct_pair_prob/_cz_shadow phase-kickback fix."""
2335
- if self._stale_replicas is None:
2336
- return
2337
- stale = self._stale_replicas.pop(lq, None)
2338
- if not stale:
2339
- return
2340
- hq = self._unpack(lq)
2341
- b0 = hq[0]
2342
- ground_truth = self.sim[b0[0]].m(b0[1])
2343
- for (t_sim, t_idx) in stale:
2344
- self.sim[t_sim].force_m(t_idx, ground_truth)
2345
-
2346
- def _resolve_witnessed_shadow(self, lq):
2347
- """The 'easy case' generalization of history_window: if lq has any
2348
- shadow replicas with a recorded witness (a replica of the SAME
2349
- logical qubit living in the SAME simulator as the control used for
2350
- that shadow coupling), force each one to match its witness's
2351
- CURRENT value now, unconditionally.
2352
-
2353
- This is deliberately unbounded -- no history_window, no deque, no
2354
- reactive invalidation on the control's later gates at all. It
2355
- doesn't need any of that, because the witness is a real quantum
2356
- register: it automatically, continuously reflects the correct
2357
- joint state through ANY NUMBER of intervening gates on the
2358
- control (or on itself), for free, simply by being the same real
2359
- simulator. No matter how long resolution is deferred -- one gate
2360
- later or a thousand -- matching the witness at the moment it's
2361
- actually needed is exactly as correct as matching it immediately
2362
- would have been. This is why the fix for the 'easy case' doesn't
2363
- need a window at all, bounded or otherwise: unlike the harder,
2364
- no-witness case (still handled by the existing bounded
2365
- _coupling_history / _stale_replicas machinery, left for a
2366
- belief-propagation-style treatment another day), there's no
2367
- approximation being deferred here, so there's nothing for a
2368
- window size to trade off against."""
2369
- if self._witness_map is None:
2370
- return
2371
- wmap = self._witness_map.pop(lq, None)
2372
- if not wmap:
2373
- return
2374
- for (t_sim, t_idx), (w_sim, w_idx) in wmap.items():
2375
- ground_truth = self.sim[w_sim].m(w_idx)
2376
- self.sim[t_sim].force_m(t_idx, ground_truth)
2377
-
2378
2121
  def prob(self, lq):
2379
2122
  hq = self._unpack(lq)
2380
2123
  if len(hq) < 2:
2381
2124
  b = hq[0]
2382
2125
  return self.sim[b[0]].prob(b[1])
2383
2126
 
2384
- if self._witness_map is not None:
2385
- self._resolve_witnessed_shadow(lq)
2386
-
2387
- if self._stale_replicas is not None:
2388
- self._resolve_pending_skip(lq)
2389
-
2390
2127
  self._correct(lq)
2391
2128
  if len(hq) == 5:
2392
2129
  # RMS
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.21.2
3
+ Version: 2.22.0
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.21.2"
10
+ VERSION = "2.22.0"
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')