qubasic 0.7.0__tar.gz → 0.8.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.
- {qubasic-0.7.0 → qubasic-0.8.0}/CHANGELOG.md +7 -0
- {qubasic-0.7.0/qubasic.egg-info → qubasic-0.8.0}/PKG-INFO +12 -3
- {qubasic-0.7.0 → qubasic-0.8.0}/README.md +11 -2
- {qubasic-0.7.0 → qubasic-0.8.0}/pyproject.toml +1 -1
- {qubasic-0.7.0 → qubasic-0.8.0/qubasic.egg-info}/PKG-INFO +12 -3
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/__init__.py +1 -1
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/analysis.py +158 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/executor.py +6 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/parser.py +5 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/qol.py +1 -1
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/statements.py +1 -1
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/terminal.py +13 -2
- {qubasic-0.7.0 → qubasic-0.8.0}/tests/test_qubasic.py +41 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/LICENSE +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/MANIFEST.in +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/examples/bell.qb +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/examples/grover3.qb +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/examples/locc_teleport.qb +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/examples/sweep_rx.qb +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic.egg-info/SOURCES.txt +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic.egg-info/entry_points.txt +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic.egg-info/top_level.txt +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/__main__.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/algorithms.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/backend.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/classic.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/cli.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/control_flow.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/debug.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/demos.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/display.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/engine.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/engine_state.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/errors.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/exec_context.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/expression.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/file_io.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/gates.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/help_text.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/locc.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/locc_commands.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/locc_display.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/locc_engine.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/locc_execution.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/memory.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/patterns.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/profiler.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/program_mgmt.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/protocol.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/scope.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/screen.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/state_display.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/strings.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/subs.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/qubasic_core/sweep.py +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/setup.cfg +0 -0
- {qubasic-0.7.0 → qubasic-0.8.0}/tests/test_features.py +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 (2026-06-19)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Partial measurement: `MEASURE 0, 2` measures a subset of qubits and reports the histogram over just those qubits (the bare `MEASURE` still measures all).
|
|
7
|
+
- Process tomography: `PTOMOGRAPHY` reconstructs the circuit's Pauli Transfer Matrix (the unitary, or the full noisy channel via the superoperator when a noise model is active), and reports the trace-preserving and unital flags plus the average gate fidelity to the identity. Limited to <= 2 qubits.
|
|
8
|
+
- Randomized benchmarking: `RB [max_length] [samples]` runs single-qubit RB over the 24-element Clifford group (each sequence ends with its recovery Clifford), fits the survival decay `p(m) = A f^m + B`, and reports the decay `f` and the error per Clifford `(1 - f)/2`. Reflects the active noise model.
|
|
9
|
+
|
|
3
10
|
## 0.7.0 (2026-06-19)
|
|
4
11
|
|
|
5
12
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -417,12 +417,21 @@ STATS SHOW Show mean/stddev/min/max per state
|
|
|
417
417
|
STATS CLEAR Reset accumulator
|
|
418
418
|
```
|
|
419
419
|
|
|
420
|
-
###
|
|
420
|
+
### Characterization
|
|
421
421
|
```
|
|
422
422
|
FIDELITY |BELL> State fidelity |<target|psi>|^2 vs a named state
|
|
423
423
|
FIDELITY [0.707,0,0,0.707] ...or an explicit amplitude list
|
|
424
|
-
TOMOGRAPHY
|
|
424
|
+
TOMOGRAPHY State tomography: reconstruct rho from Pauli expectations
|
|
425
425
|
TOMOGRAPHY 2000 Statistical tomography (2000 shots per Pauli basis)
|
|
426
|
+
PTOMOGRAPHY Process tomography: reconstruct the Pauli Transfer Matrix (<=2 qubits)
|
|
427
|
+
RB Single-qubit randomized benchmarking (fits decay -> error/Clifford)
|
|
428
|
+
RB 64 12 Sequence lengths up to 64, 12 random sequences each
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Partial measurement
|
|
432
|
+
```
|
|
433
|
+
MEASURE Measure all qubits (the default)
|
|
434
|
+
MEASURE 0, 2 Measure only qubits 0 and 2; histogram is over that subset
|
|
426
435
|
```
|
|
427
436
|
|
|
428
437
|
## Algorithm primitives
|
|
@@ -384,12 +384,21 @@ STATS SHOW Show mean/stddev/min/max per state
|
|
|
384
384
|
STATS CLEAR Reset accumulator
|
|
385
385
|
```
|
|
386
386
|
|
|
387
|
-
###
|
|
387
|
+
### Characterization
|
|
388
388
|
```
|
|
389
389
|
FIDELITY |BELL> State fidelity |<target|psi>|^2 vs a named state
|
|
390
390
|
FIDELITY [0.707,0,0,0.707] ...or an explicit amplitude list
|
|
391
|
-
TOMOGRAPHY
|
|
391
|
+
TOMOGRAPHY State tomography: reconstruct rho from Pauli expectations
|
|
392
392
|
TOMOGRAPHY 2000 Statistical tomography (2000 shots per Pauli basis)
|
|
393
|
+
PTOMOGRAPHY Process tomography: reconstruct the Pauli Transfer Matrix (<=2 qubits)
|
|
394
|
+
RB Single-qubit randomized benchmarking (fits decay -> error/Clifford)
|
|
395
|
+
RB 64 12 Sequence lengths up to 64, 12 random sequences each
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Partial measurement
|
|
399
|
+
```
|
|
400
|
+
MEASURE Measure all qubits (the default)
|
|
401
|
+
MEASURE 0, 2 Measure only qubits 0 and 2; histogram is over that subset
|
|
393
402
|
```
|
|
394
403
|
|
|
395
404
|
## Algorithm primitives
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -417,12 +417,21 @@ STATS SHOW Show mean/stddev/min/max per state
|
|
|
417
417
|
STATS CLEAR Reset accumulator
|
|
418
418
|
```
|
|
419
419
|
|
|
420
|
-
###
|
|
420
|
+
### Characterization
|
|
421
421
|
```
|
|
422
422
|
FIDELITY |BELL> State fidelity |<target|psi>|^2 vs a named state
|
|
423
423
|
FIDELITY [0.707,0,0,0.707] ...or an explicit amplitude list
|
|
424
|
-
TOMOGRAPHY
|
|
424
|
+
TOMOGRAPHY State tomography: reconstruct rho from Pauli expectations
|
|
425
425
|
TOMOGRAPHY 2000 Statistical tomography (2000 shots per Pauli basis)
|
|
426
|
+
PTOMOGRAPHY Process tomography: reconstruct the Pauli Transfer Matrix (<=2 qubits)
|
|
427
|
+
RB Single-qubit randomized benchmarking (fits decay -> error/Clifford)
|
|
428
|
+
RB 64 12 Sequence lengths up to 64, 12 random sequences each
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Partial measurement
|
|
432
|
+
```
|
|
433
|
+
MEASURE Measure all qubits (the default)
|
|
434
|
+
MEASURE 0, 2 Measure only qubits 0 and 2; histogram is over that subset
|
|
426
435
|
```
|
|
427
436
|
|
|
428
437
|
## Algorithm primitives
|
|
@@ -344,6 +344,164 @@ class AnalysisMixin:
|
|
|
344
344
|
self.io.writeln(f" Purity Tr(rho^2) = {purity:.6f}")
|
|
345
345
|
self.io.writeln(f" Fidelity to the simulated pure state = {fid:.6f}")
|
|
346
346
|
|
|
347
|
+
def cmd_ptomography(self, rest: str = '') -> None:
|
|
348
|
+
"""PTOMOGRAPHY — reconstruct the circuit's process as a Pauli Transfer Matrix.
|
|
349
|
+
|
|
350
|
+
Builds the channel of the current program (the unitary, or the full
|
|
351
|
+
noisy channel when a noise model is active) and reports its PTM, where
|
|
352
|
+
R[i,j] = (1/d) Tr(P_i E(P_j)) over Pauli operators. Trace-preserving and
|
|
353
|
+
unital flags and the average gate fidelity to the identity are shown.
|
|
354
|
+
Limited to <= 2 qubits (the process matrix is 4^n x 4^n)."""
|
|
355
|
+
if not self.program:
|
|
356
|
+
self.io.writeln("?NOTHING TO CHARACTERIZE — enter a program first")
|
|
357
|
+
return
|
|
358
|
+
n = self.num_qubits
|
|
359
|
+
if n > 2:
|
|
360
|
+
self.io.writeln(f"?PTOMOGRAPHY limited to 2 qubits (4^n process matrix); have {n}")
|
|
361
|
+
return
|
|
362
|
+
import itertools
|
|
363
|
+
try:
|
|
364
|
+
qc, _ = self.build_circuit()
|
|
365
|
+
from qiskit.quantum_info import Operator, SuperOp, PTM
|
|
366
|
+
if self._noise_model is not None:
|
|
367
|
+
from qiskit_aer import AerSimulator
|
|
368
|
+
from qiskit import transpile
|
|
369
|
+
qc2 = qc.copy()
|
|
370
|
+
qc2.save_superop()
|
|
371
|
+
b = AerSimulator(method='superop', noise_model=self._noise_model)
|
|
372
|
+
res = b.run(transpile(qc2, b, optimization_level=self._transpile_opt_level)).result()
|
|
373
|
+
ptm = PTM(SuperOp(np.asarray(res.data()['superop'])))
|
|
374
|
+
src = 'noisy channel'
|
|
375
|
+
else:
|
|
376
|
+
ptm = PTM(Operator(qc))
|
|
377
|
+
src = 'unitary'
|
|
378
|
+
R = np.real(np.asarray(ptm.data))
|
|
379
|
+
labels = [''.join(p) for p in itertools.product('IXYZ', repeat=n)]
|
|
380
|
+
self.io.writeln(f"\n Pauli Transfer Matrix ({len(R)}x{len(R)}, {src}):")
|
|
381
|
+
self.io.writeln(' ' + ''.join(f'{lb:>7}' for lb in labels))
|
|
382
|
+
for i, row in enumerate(R):
|
|
383
|
+
cells = ''.join(f'{v:+7.3f}' for v in row)
|
|
384
|
+
self.io.writeln(f" {labels[i]:>4} {cells}")
|
|
385
|
+
tp = bool(np.allclose(R[0], [1.0] + [0.0] * (len(R) - 1), atol=1e-6))
|
|
386
|
+
unital = bool(np.allclose(R[:, 0], [1.0] + [0.0] * (len(R) - 1), atol=1e-6))
|
|
387
|
+
d = 2 ** n
|
|
388
|
+
f_avg = (float(np.trace(R)) + d) / (d * d + d) # avg gate fidelity to identity
|
|
389
|
+
self.io.writeln(f" Trace-preserving: {tp} Unital: {unital}")
|
|
390
|
+
self.io.writeln(f" Avg gate fidelity to identity: {f_avg:.6f}")
|
|
391
|
+
except Exception as e:
|
|
392
|
+
self.io.writeln(f"?PTOMOGRAPHY ERROR: {e}")
|
|
393
|
+
|
|
394
|
+
def _single_qubit_cliffords(self):
|
|
395
|
+
"""Return the 24 single-qubit Cliffords as (gate_word, matrix) pairs.
|
|
396
|
+
|
|
397
|
+
Generated by BFS over words in {H, S} (which generate the group up to
|
|
398
|
+
global phase), cached on the instance. The word, applied in order, gives
|
|
399
|
+
the stored matrix, so RB sequences run as native h/s gates that the
|
|
400
|
+
active noise model decorates."""
|
|
401
|
+
cached = getattr(self, '_rb_cliffords', None)
|
|
402
|
+
if cached is not None:
|
|
403
|
+
return cached
|
|
404
|
+
from collections import deque
|
|
405
|
+
H = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)
|
|
406
|
+
S = np.array([[1, 0], [0, 1j]], dtype=complex)
|
|
407
|
+
gens = (('H', H), ('S', S))
|
|
408
|
+
|
|
409
|
+
def canon(M):
|
|
410
|
+
for v in M.ravel():
|
|
411
|
+
if abs(v) > 1e-9:
|
|
412
|
+
M = M / (v / abs(v)) # fix global phase: first nonzero entry real+
|
|
413
|
+
break
|
|
414
|
+
return tuple(np.round(M, 6).ravel())
|
|
415
|
+
|
|
416
|
+
ident = np.eye(2, dtype=complex)
|
|
417
|
+
table = {canon(ident): ([], ident)}
|
|
418
|
+
queue = deque(table)
|
|
419
|
+
while queue:
|
|
420
|
+
word, M = table[queue.popleft()]
|
|
421
|
+
for gname, G in gens:
|
|
422
|
+
M2 = G @ M
|
|
423
|
+
k2 = canon(M2)
|
|
424
|
+
if k2 not in table:
|
|
425
|
+
table[k2] = (word + [gname], M2)
|
|
426
|
+
queue.append(k2)
|
|
427
|
+
self._rb_cliffords = list(table.values())
|
|
428
|
+
return self._rb_cliffords
|
|
429
|
+
|
|
430
|
+
def cmd_rb(self, rest: str = '') -> None:
|
|
431
|
+
"""RB [max_length] [samples] — single-qubit randomized benchmarking.
|
|
432
|
+
|
|
433
|
+
Runs random Clifford sequences of growing length, each ending with the
|
|
434
|
+
recovery Clifford that inverts the sequence (ideal output |0>), measures
|
|
435
|
+
the survival probability, and fits p(m) = A f^m + B to extract the decay
|
|
436
|
+
f and the error per Clifford (1 - f)/2. Reflects the active noise model
|
|
437
|
+
(with no noise, f ~ 1). Defaults: max_length 16, 10 sequences per length."""
|
|
438
|
+
parts = rest.split()
|
|
439
|
+
max_len = int(parts[0]) if len(parts) > 0 else 16
|
|
440
|
+
samples = int(parts[1]) if len(parts) > 1 else 10
|
|
441
|
+
cliffords = self._single_qubit_cliffords()
|
|
442
|
+
gate_method = {'H': 'h', 'S': 's'}
|
|
443
|
+
from qiskit import QuantumCircuit, transpile
|
|
444
|
+
from qiskit_aer import AerSimulator
|
|
445
|
+
backend = AerSimulator(noise_model=self._noise_model) if self._noise_model else AerSimulator()
|
|
446
|
+
shots = max(200, self.shots)
|
|
447
|
+
if self._seed is not None:
|
|
448
|
+
np.random.seed(self._seed)
|
|
449
|
+
lengths = []
|
|
450
|
+
m = 1
|
|
451
|
+
while m <= max_len:
|
|
452
|
+
lengths.append(m)
|
|
453
|
+
m *= 2
|
|
454
|
+
self.io.writeln(f"\n Randomized benchmarking (1 qubit, {samples} sequences/length):")
|
|
455
|
+
self.io.writeln(f" {'length':>8} {'survival':>9}")
|
|
456
|
+
survival = []
|
|
457
|
+
for m in lengths:
|
|
458
|
+
ps = []
|
|
459
|
+
for _ in range(samples):
|
|
460
|
+
qc = QuantumCircuit(1, 1)
|
|
461
|
+
net = np.eye(2, dtype=complex)
|
|
462
|
+
for idx in np.random.randint(len(cliffords), size=m):
|
|
463
|
+
word, mat = cliffords[idx]
|
|
464
|
+
for g in word:
|
|
465
|
+
getattr(qc, gate_method[g])(0)
|
|
466
|
+
net = mat @ net
|
|
467
|
+
inv = net.conj().T
|
|
468
|
+
ridx = max(range(len(cliffords)),
|
|
469
|
+
key=lambda i: abs(np.trace(cliffords[i][1].conj().T @ inv)))
|
|
470
|
+
for g in cliffords[ridx][0]:
|
|
471
|
+
getattr(qc, gate_method[g])(0)
|
|
472
|
+
qc.measure(0, 0)
|
|
473
|
+
kw = {'shots': shots}
|
|
474
|
+
if self._seed is not None:
|
|
475
|
+
kw['seed_simulator'] = self._seed
|
|
476
|
+
# optimization_level=0: the sequence composes to ~identity, so any
|
|
477
|
+
# gate cancellation would strip the gates (and their noise) and
|
|
478
|
+
# flatten the decay. RB needs every gate physically executed.
|
|
479
|
+
counts = backend.run(
|
|
480
|
+
transpile(qc, backend, optimization_level=0), **kw).result().get_counts()
|
|
481
|
+
ps.append(counts.get('0', 0) / sum(counts.values()))
|
|
482
|
+
mean = float(np.mean(ps))
|
|
483
|
+
survival.append(mean)
|
|
484
|
+
self.io.writeln(f" {m:>8} {mean:>9.4f}")
|
|
485
|
+
|
|
486
|
+
if max(survival) - min(survival) < 0.02:
|
|
487
|
+
# Flat survival: no measurable decay, so f is underdetermined. The
|
|
488
|
+
# physical reading is no error (f = 1), not whatever the fit drifts to.
|
|
489
|
+
A, f, B = 0.0, 1.0, float(np.mean(survival))
|
|
490
|
+
else:
|
|
491
|
+
def obj(v):
|
|
492
|
+
A, f, B = v
|
|
493
|
+
f = min(max(f, 0.0), 1.2)
|
|
494
|
+
return sum((p - (A * f ** mm + B)) ** 2 for mm, p in zip(lengths, survival))
|
|
495
|
+
|
|
496
|
+
best, _ = self._nelder_mead(obj, [0.5, 0.99, 0.5], 400, 0.2)
|
|
497
|
+
A, f, B = best
|
|
498
|
+
f = min(max(f, 0.0), 1.0)
|
|
499
|
+
epc = (1.0 - f) / 2.0
|
|
500
|
+
self.io.writeln(f" fit p(m) = {A:.3f} * {f:.5f}^m + {B:.3f}")
|
|
501
|
+
self.io.writeln(f" Decay f = {f:.5f} error per Clifford = {epc:.5f}")
|
|
502
|
+
self.variables['_RB_F'] = f
|
|
503
|
+
self.variables['_RB_EPC'] = epc
|
|
504
|
+
|
|
347
505
|
def cmd_ram(self) -> None:
|
|
348
506
|
"""RAM — show memory budget, per-instance cost, and parallelism estimates."""
|
|
349
507
|
ram = _get_ram_gb()
|
|
@@ -111,6 +111,8 @@ class ExecutorMixin:
|
|
|
111
111
|
qc = QuantumCircuit(self.num_qubits)
|
|
112
112
|
# Fresh mid-circuit measurement registry for this build (dynamic IF).
|
|
113
113
|
self._classical_bits = {}
|
|
114
|
+
# Partial-measurement subset (None = measure all at the end).
|
|
115
|
+
self._measure_subset = None
|
|
114
116
|
# Apply any qubit state preparation requested via POKE to $0100.
|
|
115
117
|
if getattr(self, '_poke_state_prep', None):
|
|
116
118
|
self._emit_poke_state_prep(qc)
|
|
@@ -161,6 +163,10 @@ class ExecutorMixin:
|
|
|
161
163
|
|
|
162
164
|
if isinstance(parsed, MeasureStmt):
|
|
163
165
|
has_measure = True
|
|
166
|
+
if parsed.qubits:
|
|
167
|
+
self._measure_subset = [
|
|
168
|
+
self._resolve_qubit(q)
|
|
169
|
+
for q in parsed.qubits.replace(',', ' ').split()]
|
|
164
170
|
if self._on_measure_target is not None and not self._on_measure_fired:
|
|
165
171
|
self._on_measure_fired = True
|
|
166
172
|
self._gosub_stack.append(ctx.ip + 1)
|
|
@@ -517,6 +517,11 @@ def parse_stmt(raw: str) -> Stmt:
|
|
|
517
517
|
return RemStmt(raw=raw)
|
|
518
518
|
if upper == 'MEASURE':
|
|
519
519
|
return MeasureStmt(raw=raw)
|
|
520
|
+
# MEASURE <qubit list> — partial measurement of a subset (digits/commas only,
|
|
521
|
+
# so MEASURE_X and similar are not captured here).
|
|
522
|
+
m_sub = re.match(r'MEASURE\s+([\d,\s]+)$', text, re.IGNORECASE)
|
|
523
|
+
if m_sub:
|
|
524
|
+
return MeasureStmt(raw=raw, qubits=m_sub.group(1).strip())
|
|
520
525
|
if upper == 'END':
|
|
521
526
|
return EndStmt(raw=raw)
|
|
522
527
|
if upper == 'END IF':
|
|
@@ -96,7 +96,7 @@ _ALL_COMMANDS = [
|
|
|
96
96
|
'DIFF', 'PLOT', 'THEME', 'CLIP', 'EXPLAIN',
|
|
97
97
|
'QFT', 'IQFT', 'DIFFUSE', 'MCX', 'MCZ', 'MCP', 'QADD', 'QADDC', 'QPE',
|
|
98
98
|
'MINIMIZE', 'GRADIENT', 'FIDELITY', 'TOMOGRAPHY', 'COUPLING', 'BASIS',
|
|
99
|
-
'LOADQASM', 'SET_DENSITY', 'SAVEPNG',
|
|
99
|
+
'LOADQASM', 'SET_DENSITY', 'SAVEPNG', 'PTOMOGRAPHY', 'RB', 'MEASURE',
|
|
100
100
|
# Gates
|
|
101
101
|
'H', 'X', 'Y', 'Z', 'S', 'T', 'SDG', 'TDG', 'SX', 'ID',
|
|
102
102
|
'RX', 'RY', 'RZ', 'P', 'U', 'CX', 'CZ', 'CY', 'CH',
|
|
@@ -487,6 +487,8 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
487
487
|
'PROBE': 'cmd_probe',
|
|
488
488
|
# Classic
|
|
489
489
|
'RESTORE': 'cmd_restore',
|
|
490
|
+
# Characterization
|
|
491
|
+
'PTOMOGRAPHY': 'cmd_ptomography',
|
|
490
492
|
}
|
|
491
493
|
_CMD_WITH_ARG = {
|
|
492
494
|
'LIST': 'cmd_list', 'QUBITS': 'cmd_qubits', 'SHOTS': 'cmd_shots',
|
|
@@ -519,7 +521,7 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
519
521
|
'IMPORT': 'cmd_import', 'TYPE': 'cmd_type',
|
|
520
522
|
'SAMPLE': 'cmd_sample', 'ESTIMATE': 'cmd_estimate', 'BENCH': 'cmd_bench',
|
|
521
523
|
'MINIMIZE': 'cmd_minimize', 'GRADIENT': 'cmd_gradient',
|
|
522
|
-
'FIDELITY': 'cmd_fidelity', 'TOMOGRAPHY': 'cmd_tomography',
|
|
524
|
+
'FIDELITY': 'cmd_fidelity', 'TOMOGRAPHY': 'cmd_tomography', 'RB': 'cmd_rb',
|
|
523
525
|
'COUPLING': 'cmd_coupling', 'BASIS': 'cmd_basis',
|
|
524
526
|
'LOADQASM': 'cmd_loadqasm', 'SAVEPNG': 'cmd_savepng',
|
|
525
527
|
'SET_STATE': 'cmd_set_state', 'SET_DENSITY': 'cmd_set_density',
|
|
@@ -1325,7 +1327,16 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1325
1327
|
if self.sim_method in ('unitary', 'superop'):
|
|
1326
1328
|
qc.save_unitary(label='unitary') if self.sim_method == 'unitary' else qc.save_superop(label='superop')
|
|
1327
1329
|
elif has_measure:
|
|
1328
|
-
|
|
1330
|
+
subset = getattr(self, '_measure_subset', None)
|
|
1331
|
+
if subset:
|
|
1332
|
+
# Partial measurement: report counts over the chosen qubits only.
|
|
1333
|
+
from qiskit.circuit import ClassicalRegister
|
|
1334
|
+
cr = ClassicalRegister(len(subset), 'sub')
|
|
1335
|
+
qc.add_register(cr)
|
|
1336
|
+
for i, q in enumerate(subset):
|
|
1337
|
+
qc.measure(q, cr[i])
|
|
1338
|
+
else:
|
|
1339
|
+
qc.measure_all()
|
|
1329
1340
|
|
|
1330
1341
|
self.last_circuit = qc
|
|
1331
1342
|
self._last_transpiled = None
|
|
@@ -1648,6 +1648,47 @@ class TestNewCommands(unittest.TestCase):
|
|
|
1648
1648
|
self.assertIsNotNone(t._last_transpiled)
|
|
1649
1649
|
|
|
1650
1650
|
|
|
1651
|
+
class TestCharacterization(unittest.TestCase):
|
|
1652
|
+
"""Partial measurement, process tomography, randomized benchmarking."""
|
|
1653
|
+
|
|
1654
|
+
def test_partial_measurement(self):
|
|
1655
|
+
# GHZ measured on a subset gives correlated subset counts.
|
|
1656
|
+
t = QBasicTerminal(); t.num_qubits = 3; t.shots = 1000; t._seed = 1
|
|
1657
|
+
for ln in ['10 H 0', '20 CX 0,1', '30 CX 0,2', '40 MEASURE 0,2']:
|
|
1658
|
+
t.process(ln, track_undo=False)
|
|
1659
|
+
capture(t.cmd_run)
|
|
1660
|
+
self.assertTrue(all(len(k) == 2 for k in t.last_counts)) # two-bit keys
|
|
1661
|
+
self.assertEqual(set(t.last_counts), {'00', '11'}) # correlated
|
|
1662
|
+
# single-qubit subset gives one-bit keys
|
|
1663
|
+
t2 = QBasicTerminal(); t2.num_qubits = 3; t2.shots = 1000; t2._seed = 1
|
|
1664
|
+
for ln in ['10 H 0', '20 CX 0,1', '30 MEASURE 1']:
|
|
1665
|
+
t2.process(ln, track_undo=False)
|
|
1666
|
+
capture(t2.cmd_run)
|
|
1667
|
+
self.assertEqual(set(t2.last_counts), {'0', '1'})
|
|
1668
|
+
|
|
1669
|
+
def test_process_tomography(self):
|
|
1670
|
+
# X gate: PTM = diag(1, 1, -1, -1).
|
|
1671
|
+
t = QBasicTerminal(); t.num_qubits = 1
|
|
1672
|
+
t.process('10 X 0', track_undo=False)
|
|
1673
|
+
_, out = capture(t.cmd_ptomography)
|
|
1674
|
+
self.assertIn('Trace-preserving: True', out)
|
|
1675
|
+
self.assertIn('-1.000', out) # Y and Z rows flip sign
|
|
1676
|
+
# H gate maps Z -> X, so the X column has a +1 off the diagonal.
|
|
1677
|
+
t2 = QBasicTerminal(); t2.num_qubits = 1
|
|
1678
|
+
t2.process('10 H 0', track_undo=False)
|
|
1679
|
+
_, out2 = capture(t2.cmd_ptomography)
|
|
1680
|
+
self.assertIn('Trace-preserving: True', out2)
|
|
1681
|
+
|
|
1682
|
+
def test_randomized_benchmarking(self):
|
|
1683
|
+
# Noiseless single-qubit RB: perfect recovery, f = 1, error per Clifford = 0.
|
|
1684
|
+
t = QBasicTerminal(); t.num_qubits = 1; t.shots = 400; t._seed = 3
|
|
1685
|
+
capture(t.cmd_rb, '4 3')
|
|
1686
|
+
self.assertAlmostEqual(t.variables['_RB_F'], 1.0, places=6)
|
|
1687
|
+
self.assertAlmostEqual(t.variables['_RB_EPC'], 0.0, places=6)
|
|
1688
|
+
# 24 single-qubit Cliffords are generated.
|
|
1689
|
+
self.assertEqual(len(t._single_qubit_cliffords()), 24)
|
|
1690
|
+
|
|
1691
|
+
|
|
1651
1692
|
if __name__ == '__main__':
|
|
1652
1693
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
1653
1694
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|