qubasic 0.1.0__py3-none-any.whl
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.
- qbasic.py +113 -0
- qbasic_core/__init__.py +80 -0
- qbasic_core/__main__.py +6 -0
- qbasic_core/analysis.py +179 -0
- qbasic_core/backend.py +76 -0
- qbasic_core/classic.py +419 -0
- qbasic_core/control_flow.py +576 -0
- qbasic_core/debug.py +327 -0
- qbasic_core/demos.py +356 -0
- qbasic_core/display.py +274 -0
- qbasic_core/engine.py +126 -0
- qbasic_core/engine_state.py +109 -0
- qbasic_core/errors.py +37 -0
- qbasic_core/exec_context.py +24 -0
- qbasic_core/executor.py +861 -0
- qbasic_core/expression.py +228 -0
- qbasic_core/file_io.py +457 -0
- qbasic_core/gates.py +284 -0
- qbasic_core/help_text.py +167 -0
- qbasic_core/io_protocol.py +33 -0
- qbasic_core/locc.py +10 -0
- qbasic_core/locc_commands.py +221 -0
- qbasic_core/locc_display.py +61 -0
- qbasic_core/locc_engine.py +195 -0
- qbasic_core/locc_execution.py +389 -0
- qbasic_core/memory.py +369 -0
- qbasic_core/mock_backend.py +66 -0
- qbasic_core/noise_mixin.py +96 -0
- qbasic_core/parser.py +564 -0
- qbasic_core/patterns.py +186 -0
- qbasic_core/profiler.py +156 -0
- qbasic_core/program_mgmt.py +369 -0
- qbasic_core/protocol.py +77 -0
- qbasic_core/py.typed +0 -0
- qbasic_core/scope.py +74 -0
- qbasic_core/screen.py +115 -0
- qbasic_core/state_display.py +60 -0
- qbasic_core/statements.py +387 -0
- qbasic_core/strings.py +107 -0
- qbasic_core/subs.py +261 -0
- qbasic_core/sweep.py +82 -0
- qbasic_core/terminal.py +1697 -0
- qubasic-0.1.0.dist-info/METADATA +736 -0
- qubasic-0.1.0.dist-info/RECORD +48 -0
- qubasic-0.1.0.dist-info/WHEEL +5 -0
- qubasic-0.1.0.dist-info/entry_points.txt +2 -0
- qubasic-0.1.0.dist-info/licenses/LICENSE +21 -0
- qubasic-0.1.0.dist-info/top_level.txt +2 -0
qbasic_core/gates.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Gate matrices, builders, and numpy simulation primitives."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
8
|
+
# Gate tables: name -> (num_params, num_qubits)
|
|
9
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
GATE_TABLE = {
|
|
12
|
+
# 0-param, 1-qubit
|
|
13
|
+
'H': (0, 1), 'X': (0, 1), 'Y': (0, 1), 'Z': (0, 1),
|
|
14
|
+
'S': (0, 1), 'T': (0, 1), 'SDG': (0, 1), 'TDG': (0, 1),
|
|
15
|
+
'SX': (0, 1), 'ID': (0, 1),
|
|
16
|
+
# 1-param, 1-qubit
|
|
17
|
+
'RX': (1, 1), 'RY': (1, 1), 'RZ': (1, 1), 'P': (1, 1),
|
|
18
|
+
# 3-param, 1-qubit
|
|
19
|
+
'U': (3, 1),
|
|
20
|
+
# 0-param, 2-qubit
|
|
21
|
+
'CX': (0, 2), 'CNOT': (0, 2), 'CZ': (0, 2), 'CY': (0, 2),
|
|
22
|
+
'CH': (0, 2), 'SWAP': (0, 2), 'DCX': (0, 2), 'ISWAP':(0, 2),
|
|
23
|
+
# 1-param, 2-qubit
|
|
24
|
+
'CRX': (1, 2), 'CRY': (1, 2), 'CRZ': (1, 2), 'CP': (1, 2),
|
|
25
|
+
'RXX': (1, 2), 'RYY': (1, 2), 'RZZ': (1, 2),
|
|
26
|
+
# 0-param, 3-qubit
|
|
27
|
+
'CCX': (0, 3), 'TOFFOLI': (0, 3), 'CSWAP': (0, 3), 'FREDKIN': (0, 3),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Aliases
|
|
31
|
+
GATE_ALIASES = {
|
|
32
|
+
'CNOT': 'CX', 'TOFFOLI': 'CCX', 'FREDKIN': 'CSWAP',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
37
|
+
# Pre-computed constant gate matrices (allocated once, not per call)
|
|
38
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
39
|
+
|
|
40
|
+
_MAT_CY = np.eye(4, dtype=complex)
|
|
41
|
+
_MAT_CY[2,2], _MAT_CY[2,3], _MAT_CY[3,2], _MAT_CY[3,3] = 0, -1j, 1j, 0
|
|
42
|
+
|
|
43
|
+
_MAT_CH = np.eye(4, dtype=complex)
|
|
44
|
+
_h = 1/np.sqrt(2)
|
|
45
|
+
_MAT_CH[2,2], _MAT_CH[2,3], _MAT_CH[3,2], _MAT_CH[3,3] = _h, _h, _h, -_h
|
|
46
|
+
|
|
47
|
+
_MAT_CCX = np.eye(8, dtype=complex)
|
|
48
|
+
_MAT_CCX[6,6], _MAT_CCX[6,7], _MAT_CCX[7,6], _MAT_CCX[7,7] = 0, 1, 1, 0
|
|
49
|
+
|
|
50
|
+
_MAT_CSWAP = np.eye(8, dtype=complex)
|
|
51
|
+
_MAT_CSWAP[5,5], _MAT_CSWAP[5,6], _MAT_CSWAP[6,5], _MAT_CSWAP[6,6] = 0, 1, 1, 0
|
|
52
|
+
|
|
53
|
+
_MAT_H = np.array([[1,1],[1,-1]], dtype=complex) / np.sqrt(2)
|
|
54
|
+
_MAT_X = np.array([[0,1],[1,0]], dtype=complex)
|
|
55
|
+
_MAT_Y = np.array([[0,-1j],[1j,0]], dtype=complex)
|
|
56
|
+
_MAT_Z = np.array([[1,0],[0,-1]], dtype=complex)
|
|
57
|
+
_MAT_S = np.array([[1,0],[0,1j]], dtype=complex)
|
|
58
|
+
_MAT_T = np.array([[1,0],[0,np.exp(1j*np.pi/4)]], dtype=complex)
|
|
59
|
+
_MAT_SDG = np.array([[1,0],[0,-1j]], dtype=complex)
|
|
60
|
+
_MAT_TDG = np.array([[1,0],[0,np.exp(-1j*np.pi/4)]], dtype=complex)
|
|
61
|
+
_MAT_SX = np.array([[1+1j,1-1j],[1-1j,1+1j]], dtype=complex) / 2
|
|
62
|
+
_MAT_ID = np.eye(2, dtype=complex)
|
|
63
|
+
_MAT_CX = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]], dtype=complex)
|
|
64
|
+
_MAT_CZ = np.diag([1, 1, 1, -1]).astype(complex)
|
|
65
|
+
_MAT_SWAP = np.array([[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]], dtype=complex)
|
|
66
|
+
_MAT_DCX = np.array([[1,0,0,0],[0,0,0,1],[0,1,0,0],[0,0,1,0]], dtype=complex)
|
|
67
|
+
_MAT_ISWAP = np.array([[1,0,0,0],[0,0,1j,0],[0,1j,0,0],[0,0,0,1]], dtype=complex)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
71
|
+
# Parameterized gate matrix builders
|
|
72
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
73
|
+
|
|
74
|
+
def _mat_crx(p):
|
|
75
|
+
t = p[0]
|
|
76
|
+
c, s = np.cos(t/2), np.sin(t/2)
|
|
77
|
+
m = np.eye(4, dtype=complex)
|
|
78
|
+
m[2,2], m[2,3] = c, -1j*s
|
|
79
|
+
m[3,2], m[3,3] = -1j*s, c
|
|
80
|
+
return m
|
|
81
|
+
|
|
82
|
+
def _mat_cry(p):
|
|
83
|
+
t = p[0]
|
|
84
|
+
c, s = np.cos(t/2), np.sin(t/2)
|
|
85
|
+
m = np.eye(4, dtype=complex)
|
|
86
|
+
m[2,2], m[2,3] = c, -s
|
|
87
|
+
m[3,2], m[3,3] = s, c
|
|
88
|
+
return m
|
|
89
|
+
|
|
90
|
+
def _mat_crz(p):
|
|
91
|
+
t = p[0]
|
|
92
|
+
m = np.eye(4, dtype=complex)
|
|
93
|
+
m[2,2], m[3,3] = np.exp(-1j*t/2), np.exp(1j*t/2)
|
|
94
|
+
return m
|
|
95
|
+
|
|
96
|
+
def _mat_rxx(p):
|
|
97
|
+
t = p[0]
|
|
98
|
+
ct, st = np.cos(t/2), np.sin(t/2)
|
|
99
|
+
m = np.zeros((4,4), dtype=complex)
|
|
100
|
+
m[0,0] = m[1,1] = m[2,2] = m[3,3] = ct
|
|
101
|
+
m[0,3] = m[3,0] = m[1,2] = m[2,1] = -1j*st
|
|
102
|
+
return m
|
|
103
|
+
|
|
104
|
+
def _mat_ryy(p):
|
|
105
|
+
# RYY(t) = exp(-i * t/2 * YY). The off-diagonal signs are asymmetric
|
|
106
|
+
# because Y = [[0,-i],[i,0]] yields YY with sign pattern:
|
|
107
|
+
# |00>-|11> coupling: +i sin(t/2) (anti-diagonal corners)
|
|
108
|
+
# |01>-|10> coupling: -i sin(t/2) (inner off-diagonal)
|
|
109
|
+
t = p[0]
|
|
110
|
+
ct, st = np.cos(t/2), np.sin(t/2)
|
|
111
|
+
m = np.zeros((4,4), dtype=complex)
|
|
112
|
+
m[0,0] = m[1,1] = m[2,2] = m[3,3] = ct
|
|
113
|
+
m[0,3] = m[3,0] = 1j*st
|
|
114
|
+
m[1,2] = m[2,1] = -1j*st
|
|
115
|
+
return m
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
119
|
+
# Gate matrix registry: name -> callable(params) -> np.ndarray
|
|
120
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
121
|
+
|
|
122
|
+
# Adding a new gate only requires an entry here and in GATE_TABLE.
|
|
123
|
+
# 0-param gates return pre-computed constants (no allocation per call).
|
|
124
|
+
_GATE_BUILDERS = {
|
|
125
|
+
# 0-param, 1-qubit
|
|
126
|
+
'H': lambda p: _MAT_H,
|
|
127
|
+
'X': lambda p: _MAT_X,
|
|
128
|
+
'Y': lambda p: _MAT_Y,
|
|
129
|
+
'Z': lambda p: _MAT_Z,
|
|
130
|
+
'S': lambda p: _MAT_S,
|
|
131
|
+
'T': lambda p: _MAT_T,
|
|
132
|
+
'SDG': lambda p: _MAT_SDG,
|
|
133
|
+
'TDG': lambda p: _MAT_TDG,
|
|
134
|
+
'SX': lambda p: _MAT_SX,
|
|
135
|
+
'ID': lambda p: _MAT_ID,
|
|
136
|
+
# 1-param, 1-qubit
|
|
137
|
+
'RX': lambda p: np.array([[np.cos(p[0]/2), -1j*np.sin(p[0]/2)],
|
|
138
|
+
[-1j*np.sin(p[0]/2), np.cos(p[0]/2)]], dtype=complex),
|
|
139
|
+
'RY': lambda p: np.array([[np.cos(p[0]/2), -np.sin(p[0]/2)],
|
|
140
|
+
[np.sin(p[0]/2), np.cos(p[0]/2)]], dtype=complex),
|
|
141
|
+
'RZ': lambda p: np.array([[np.exp(-1j*p[0]/2), 0],
|
|
142
|
+
[0, np.exp(1j*p[0]/2)]], dtype=complex),
|
|
143
|
+
'P': lambda p: np.array([[1, 0], [0, np.exp(1j*p[0])]], dtype=complex),
|
|
144
|
+
# 3-param, 1-qubit
|
|
145
|
+
'U': lambda p: np.array([
|
|
146
|
+
[np.cos(p[0]/2), -np.exp(1j*p[2])*np.sin(p[0]/2)],
|
|
147
|
+
[np.exp(1j*p[1])*np.sin(p[0]/2),
|
|
148
|
+
np.exp(1j*(p[1]+p[2]))*np.cos(p[0]/2)]], dtype=complex),
|
|
149
|
+
# 0-param, 2-qubit
|
|
150
|
+
'CX': lambda p: _MAT_CX,
|
|
151
|
+
'CZ': lambda p: _MAT_CZ,
|
|
152
|
+
'CY': lambda p: _MAT_CY,
|
|
153
|
+
'CH': lambda p: _MAT_CH,
|
|
154
|
+
'SWAP': lambda p: _MAT_SWAP,
|
|
155
|
+
'DCX': lambda p: _MAT_DCX,
|
|
156
|
+
'ISWAP': lambda p: _MAT_ISWAP,
|
|
157
|
+
# 1-param, 2-qubit
|
|
158
|
+
'CRX': _mat_crx,
|
|
159
|
+
'CRY': _mat_cry,
|
|
160
|
+
'CRZ': _mat_crz,
|
|
161
|
+
'CP': lambda p: np.diag([1, 1, 1, np.exp(1j*p[0])]).astype(complex),
|
|
162
|
+
'RXX': _mat_rxx,
|
|
163
|
+
'RYY': _mat_ryy,
|
|
164
|
+
'RZZ': lambda p: np.diag([np.exp(-1j*p[0]/2), np.exp(1j*p[0]/2),
|
|
165
|
+
np.exp(1j*p[0]/2), np.exp(-1j*p[0]/2)]),
|
|
166
|
+
# 0-param, 3-qubit
|
|
167
|
+
'CCX': lambda p: _MAT_CCX,
|
|
168
|
+
'CSWAP': lambda p: _MAT_CSWAP,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
173
|
+
# Numpy simulation primitives
|
|
174
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
175
|
+
|
|
176
|
+
# Fast-path lookup for 0-param gates: return pre-computed constants directly,
|
|
177
|
+
# skipping the builder callable and its lambda overhead.
|
|
178
|
+
_GATE_CONSTANTS: dict[str, np.ndarray] = {
|
|
179
|
+
'H': _MAT_H,
|
|
180
|
+
'X': _MAT_X,
|
|
181
|
+
'Y': _MAT_Y,
|
|
182
|
+
'Z': _MAT_Z,
|
|
183
|
+
'S': _MAT_S,
|
|
184
|
+
'T': _MAT_T,
|
|
185
|
+
'SDG': _MAT_SDG,
|
|
186
|
+
'TDG': _MAT_TDG,
|
|
187
|
+
'SX': _MAT_SX,
|
|
188
|
+
'ID': _MAT_ID,
|
|
189
|
+
'CX': _MAT_CX,
|
|
190
|
+
'CZ': _MAT_CZ,
|
|
191
|
+
'CY': _MAT_CY,
|
|
192
|
+
'CH': _MAT_CH,
|
|
193
|
+
'SWAP': _MAT_SWAP,
|
|
194
|
+
'DCX': _MAT_DCX,
|
|
195
|
+
'ISWAP': _MAT_ISWAP,
|
|
196
|
+
'CCX': _MAT_CCX,
|
|
197
|
+
'CSWAP': _MAT_CSWAP,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _np_gate_matrix(name: str, params: tuple[float, ...] = ()) -> np.ndarray:
|
|
202
|
+
"""Return the unitary matrix for a named gate via registry lookup."""
|
|
203
|
+
canonical = GATE_ALIASES.get(name, name)
|
|
204
|
+
# Fast path: 0-param gates return pre-computed constants directly.
|
|
205
|
+
mat = _GATE_CONSTANTS.get(canonical)
|
|
206
|
+
if mat is not None:
|
|
207
|
+
return mat
|
|
208
|
+
builder = _GATE_BUILDERS.get(canonical)
|
|
209
|
+
if builder is None:
|
|
210
|
+
raise ValueError(f"No matrix for gate: {name}")
|
|
211
|
+
return builder(params)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _apply_gate_np(
|
|
215
|
+
sv: np.ndarray, matrix: np.ndarray, qubits: list[int], n_qubits: int,
|
|
216
|
+
) -> np.ndarray:
|
|
217
|
+
"""Apply a k-qubit gate to a statevector using numpy tensordot.
|
|
218
|
+
|
|
219
|
+
Returns a tensor of shape [2]*n (possibly non-contiguous view).
|
|
220
|
+
This avoids an extra 32 GB copy for large qubit counts.
|
|
221
|
+
The caller stores the result directly; previous sv is freed.
|
|
222
|
+
"""
|
|
223
|
+
k = len(qubits)
|
|
224
|
+
sv_t = sv.reshape([2] * n_qubits) # view if already contiguous
|
|
225
|
+
axes = [n_qubits - 1 - q for q in qubits]
|
|
226
|
+
gate_t = matrix.reshape([2] * (2 * k))
|
|
227
|
+
# Contract gate's input axes (k..2k-1) with sv's target axes
|
|
228
|
+
result = np.tensordot(gate_t, sv_t, axes=(list(range(k, 2*k)), axes))
|
|
229
|
+
# Move gate output axes (0..k-1) to the target positions (view, no copy)
|
|
230
|
+
return np.moveaxis(result, list(range(k)), axes)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _measure_np(
|
|
234
|
+
sv: np.ndarray, qubit: int, n_qubits: int,
|
|
235
|
+
) -> tuple[int, np.ndarray]:
|
|
236
|
+
"""Mid-circuit measurement with Born-rule sampling and state collapse.
|
|
237
|
+
|
|
238
|
+
Computes marginal probabilities for |0> and |1> on the target qubit,
|
|
239
|
+
samples an outcome, and collapses + renormalizes the statevector.
|
|
240
|
+
Includes a numerical floor to avoid division-by-zero when both
|
|
241
|
+
marginals underflow to zero.
|
|
242
|
+
"""
|
|
243
|
+
sv = np.ascontiguousarray(sv).reshape([2] * n_qubits)
|
|
244
|
+
ax = n_qubits - 1 - qubit
|
|
245
|
+
idx_0 = [slice(None)] * n_qubits
|
|
246
|
+
idx_1 = [slice(None)] * n_qubits
|
|
247
|
+
idx_0[ax] = 0
|
|
248
|
+
idx_1[ax] = 1
|
|
249
|
+
p0 = float(np.sum(np.abs(sv[tuple(idx_0)])**2))
|
|
250
|
+
p1 = float(np.sum(np.abs(sv[tuple(idx_1)])**2))
|
|
251
|
+
total = p0 + p1
|
|
252
|
+
if total < 1e-300:
|
|
253
|
+
# Numerical underflow: both marginals are essentially zero.
|
|
254
|
+
# Default to outcome 0 and return a normalized |0> on this qubit.
|
|
255
|
+
new_sv = np.zeros(2**n_qubits, dtype=complex)
|
|
256
|
+
new_sv[0] = 1.0
|
|
257
|
+
return 0, new_sv
|
|
258
|
+
outcome = 0 if np.random.random() < p0 / total else 1
|
|
259
|
+
# Guard: if the chosen outcome has negligible probability, force the other.
|
|
260
|
+
if outcome == 0 and p0 < 1e-300:
|
|
261
|
+
outcome = 1
|
|
262
|
+
elif outcome == 1 and p1 < 1e-300:
|
|
263
|
+
outcome = 0
|
|
264
|
+
new_sv = np.zeros_like(sv)
|
|
265
|
+
if outcome == 0:
|
|
266
|
+
new_sv[tuple(idx_0)] = sv[tuple(idx_0)] / np.sqrt(p0)
|
|
267
|
+
else:
|
|
268
|
+
new_sv[tuple(idx_1)] = sv[tuple(idx_1)] / np.sqrt(p1)
|
|
269
|
+
return outcome, new_sv.reshape(-1)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _sample_np(sv: np.ndarray, n_qubits: int, shots: int) -> dict[str, int]:
|
|
273
|
+
"""Sample measurement outcomes from a statevector."""
|
|
274
|
+
probs = np.abs(np.ascontiguousarray(sv).ravel())**2
|
|
275
|
+
probs /= probs.sum()
|
|
276
|
+
indices = np.random.choice(probs.size, size=shots, p=probs)
|
|
277
|
+
unique, ucounts = np.unique(indices, return_counts=True)
|
|
278
|
+
return {format(idx, f'0{n_qubits}b'): int(cnt) for idx, cnt in zip(unique, ucounts)}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _sample_one_np(sv: np.ndarray, n_qubits: int) -> str:
|
|
282
|
+
"""Sample a single measurement outcome."""
|
|
283
|
+
counts = _sample_np(sv, n_qubits, 1)
|
|
284
|
+
return next(iter(counts))
|
qbasic_core/help_text.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""QBASIC help text and banner art — extracted from terminal.py."""
|
|
2
|
+
|
|
3
|
+
HELP_TEXT = """\
|
|
4
|
+
|
|
5
|
+
QBASIC — Quantum BASIC Terminal
|
|
6
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
7
|
+
|
|
8
|
+
PROGRAM EDITING
|
|
9
|
+
10 H 0 Enter a numbered line (program statement)
|
|
10
|
+
10 Delete line 10
|
|
11
|
+
LIST Show the program
|
|
12
|
+
DELETE 10 Delete line 10
|
|
13
|
+
DELETE 10-50 Delete range
|
|
14
|
+
RENUM Renumber lines (10, 20, 30, ...)
|
|
15
|
+
NEW Clear everything
|
|
16
|
+
SAVE <file> Save program to .qb file
|
|
17
|
+
LOAD <file> Load program from .qb file
|
|
18
|
+
RUN Execute the program
|
|
19
|
+
|
|
20
|
+
GATES (immediate or in a program)
|
|
21
|
+
H 0 Hadamard on qubit 0
|
|
22
|
+
X 0 / Y 0 / Z 0 Pauli gates
|
|
23
|
+
S 0 / T 0 Phase gates (and SDG, TDG)
|
|
24
|
+
RX PI/4, 0 Rotation gates (RX, RY, RZ, P)
|
|
25
|
+
CX 0, 1 CNOT (control, target)
|
|
26
|
+
CZ 0, 1 Controlled-Z
|
|
27
|
+
CCX 0, 1, 2 Toffoli
|
|
28
|
+
SWAP 0, 1 Swap two qubits
|
|
29
|
+
MEASURE Add measurements (use in program)
|
|
30
|
+
|
|
31
|
+
MULTI-GATE LINES
|
|
32
|
+
10 H 0 : CX 0,1 Multiple gates on one line with ':'
|
|
33
|
+
|
|
34
|
+
REGISTERS & SUBROUTINES
|
|
35
|
+
REG data 3 Name a group of qubits
|
|
36
|
+
10 H data[0] Use register notation
|
|
37
|
+
REGS List registers
|
|
38
|
+
DEF BELL = H 0 : CX 0,1
|
|
39
|
+
Define a named gate sequence
|
|
40
|
+
10 BELL Use it in a program
|
|
41
|
+
10 BELL @2 Use with qubit offset
|
|
42
|
+
DEFS List subroutines
|
|
43
|
+
|
|
44
|
+
VARIABLES & LOOPS
|
|
45
|
+
LET angle = PI/4 Set a variable
|
|
46
|
+
10 RX angle, 0 Use in gate parameters
|
|
47
|
+
10 FOR I = 0 TO 3 Loop (variable substitution in body)
|
|
48
|
+
20 H I
|
|
49
|
+
30 NEXT I
|
|
50
|
+
VARS List variables
|
|
51
|
+
|
|
52
|
+
DISPLAY
|
|
53
|
+
STATE Show statevector (after RUN)
|
|
54
|
+
HIST Show measurement histogram
|
|
55
|
+
PROBS Show probability distribution
|
|
56
|
+
BLOCH [n] ASCII Bloch sphere for qubit n (or all)
|
|
57
|
+
STEP Step through program with state display
|
|
58
|
+
CIRCUIT Show circuit diagram
|
|
59
|
+
|
|
60
|
+
CONFIGURATION
|
|
61
|
+
QUBITS n Set number of qubits (default: 4)
|
|
62
|
+
SHOTS n Set number of shots (default: 1024)
|
|
63
|
+
METHOD name Set simulation method (automatic, statevector,
|
|
64
|
+
matrix_product_state, stabilizer, ...)
|
|
65
|
+
|
|
66
|
+
DEMOS
|
|
67
|
+
DEMO LIST List available demos
|
|
68
|
+
DEMO BELL Bell state
|
|
69
|
+
DEMO GHZ GHZ entanglement
|
|
70
|
+
DEMO TELEPORT Quantum teleportation
|
|
71
|
+
DEMO GROVER Grover's search
|
|
72
|
+
DEMO QFT Quantum Fourier transform
|
|
73
|
+
DEMO DEUTSCH Deutsch-Jozsa algorithm
|
|
74
|
+
DEMO BERNSTEIN Bernstein-Vazirani
|
|
75
|
+
DEMO SUPERDENSE Superdense coding
|
|
76
|
+
DEMO RANDOM Quantum random number generator
|
|
77
|
+
DEMO STRESS 20-qubit stress test
|
|
78
|
+
DEMO LOCC-TELEPORT Teleportation across A/B boundary (JOINT)
|
|
79
|
+
DEMO LOCC-COORD Classical coordination (SPLIT)
|
|
80
|
+
|
|
81
|
+
LOCC MODE (dual-register distributed quantum simulation)
|
|
82
|
+
LOCC <n_a> <n_b> SPLIT mode: two independent registers
|
|
83
|
+
LOCC JOINT <n_a> <n_b> JOINT mode: shared entanglement possible
|
|
84
|
+
LOCC OFF Back to normal Aer mode
|
|
85
|
+
LOCC STATUS Show register info
|
|
86
|
+
@A H 0 Gate on register A
|
|
87
|
+
@B CX 0,1 Gate on register B
|
|
88
|
+
SEND A 0 -> x Mid-circuit measure A[0], store in x
|
|
89
|
+
IF x THEN @B X 0 Conditional gate based on classical bit
|
|
90
|
+
SHARE A 2, B 0 Create Bell pair (JOINT mode only)
|
|
91
|
+
STATE A / STATE B Inspect register states
|
|
92
|
+
BLOCH A 0 / BLOCH B 0 Bloch spheres per register
|
|
93
|
+
|
|
94
|
+
SPLIT: max capacity (31+31), no cross-register entanglement
|
|
95
|
+
JOINT: shared entanglement, limited to ~32 total qubits
|
|
96
|
+
LOCCINFO Protocol metrics after run
|
|
97
|
+
CONNECT "host:port" AS C Attach remote register (stub — local sim only)
|
|
98
|
+
DISCONNECT C Detach remote register
|
|
99
|
+
|
|
100
|
+
BASIS MEASUREMENT
|
|
101
|
+
MEASURE_X qubit Measure in X basis (H before measure)
|
|
102
|
+
MEASURE_Y qubit Measure in Y basis (SDG+H before measure)
|
|
103
|
+
MEASURE_Z qubit Measure in Z basis (standard)
|
|
104
|
+
Results stored in mx_<q>, my_<q>, mz_<q> variables.
|
|
105
|
+
|
|
106
|
+
ERROR CORRECTION
|
|
107
|
+
SYNDROME ZZ 0 1 -> s0 Measure Pauli stabilizer non-destructively
|
|
108
|
+
Uses an ancilla (highest qubit index). Pauli string
|
|
109
|
+
length must match qubit count. I/X/Y/Z supported.
|
|
110
|
+
|
|
111
|
+
ADVANCED
|
|
112
|
+
UNITARY NAME = [[..]] Define gate from unitary matrix (standard basis order)
|
|
113
|
+
CTRL gate ctrl, tgt Controlled version of any gate
|
|
114
|
+
INV gate qubit Inverse/dagger of a gate
|
|
115
|
+
RESET qubit Reset qubit to |0>
|
|
116
|
+
SET_STATE |name> Set qubit state (|0>, |1>, |+>, |->, |BELL>, |GHZ>)
|
|
117
|
+
SET_STATE [a, b, ...] Set explicit amplitudes (auto-normalized)
|
|
118
|
+
SWEEP var s e [n] Run circuit sweeping a variable
|
|
119
|
+
NOISE type [p] Set noise model (depolarizing, etc.)
|
|
120
|
+
NOISE OFF Disable noise
|
|
121
|
+
EXPECT Z 0 Expectation value of Pauli operator
|
|
122
|
+
DENSITY Show density matrix
|
|
123
|
+
ENTROPY [qubits] Entanglement entropy
|
|
124
|
+
DECOMPOSE Gate count breakdown
|
|
125
|
+
EXPORT [file] Export circuit as OpenQASM
|
|
126
|
+
CSV [file] Export results as CSV
|
|
127
|
+
RAM Memory budget and parallelism estimates
|
|
128
|
+
BENCH Benchmark qubit scaling
|
|
129
|
+
INCLUDE file Merge another .qb file
|
|
130
|
+
DIR [path] List .qb files
|
|
131
|
+
CLEAR var Remove a variable or array
|
|
132
|
+
UNDO Undo last program edit
|
|
133
|
+
BANK n Switch to program slot n (auto-saves current)
|
|
134
|
+
BANK Show current slot and list used slots
|
|
135
|
+
|
|
136
|
+
FLOW CONTROL (in programs)
|
|
137
|
+
GOTO line Jump to line
|
|
138
|
+
GOSUB line / RETURN Subroutine call with stack
|
|
139
|
+
WHILE expr / WEND Conditional loop
|
|
140
|
+
IF expr THEN ... ELSE Conditional (supports expressions)
|
|
141
|
+
END Stop execution
|
|
142
|
+
PRINT expr Output during run
|
|
143
|
+
INPUT "prompt", var Read user input
|
|
144
|
+
DIM arr(size) Declare array
|
|
145
|
+
LET arr[i] = val Array assignment
|
|
146
|
+
|
|
147
|
+
EXPRESSIONS
|
|
148
|
+
PI, TAU, E, SQRT2, sin(), cos(), sqrt(), log(), etc.
|
|
149
|
+
Comparisons: ==, !=, <, >, <=, >=, AND, OR, NOT
|
|
150
|
+
Arrays: arr(i) or arr[i]
|
|
151
|
+
Example: LET theta = PI/4 + asin(0.5)
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
BANNER_ART = """\
|
|
155
|
+
|
|
156
|
+
██████╗ ██████╗ █████╗ ███████╗██╗ ██████╗
|
|
157
|
+
██╔═══██╗██╔══██╗██╔══██╗██╔════╝██║██╔════╝
|
|
158
|
+
██║ ██║██████╔╝███████║███████╗██║██║
|
|
159
|
+
██║▄▄ ██║██╔══██╗██╔══██║╚════██║██║██║
|
|
160
|
+
╚██████╔╝██████╔╝██║ ██║███████║██║╚██████╗
|
|
161
|
+
╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝
|
|
162
|
+
|
|
163
|
+
Quantum BASIC
|
|
164
|
+
{info_line}
|
|
165
|
+
{config_line}
|
|
166
|
+
Type HELP for commands, DEMO LIST for demos.
|
|
167
|
+
"""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""QBASIC I/O protocol — decouples execution engine from print/input."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@runtime_checkable
|
|
9
|
+
class IOPort(Protocol):
|
|
10
|
+
"""Abstract I/O port for execution output and user input."""
|
|
11
|
+
|
|
12
|
+
def write(self, text: str) -> None: ...
|
|
13
|
+
def writeln(self, text: str) -> None: ...
|
|
14
|
+
def read_line(self, prompt: str) -> str: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StdIOPort:
|
|
18
|
+
"""Default I/O: stdout/stdin."""
|
|
19
|
+
|
|
20
|
+
def write(self, text: str) -> None:
|
|
21
|
+
try:
|
|
22
|
+
print(text, end='', flush=True)
|
|
23
|
+
except UnicodeEncodeError:
|
|
24
|
+
print(text.encode('ascii', 'replace').decode('ascii'), end='', flush=True)
|
|
25
|
+
|
|
26
|
+
def writeln(self, text: str) -> None:
|
|
27
|
+
try:
|
|
28
|
+
print(text)
|
|
29
|
+
except UnicodeEncodeError:
|
|
30
|
+
print(text.encode('ascii', 'replace').decode('ascii'))
|
|
31
|
+
|
|
32
|
+
def read_line(self, prompt: str) -> str:
|
|
33
|
+
return input(prompt)
|
qbasic_core/locc.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""QBASIC LOCC mixin — re-exports from split modules."""
|
|
2
|
+
|
|
3
|
+
from qbasic_core.locc_commands import LOCCCommandsMixin
|
|
4
|
+
from qbasic_core.locc_display import LOCCDisplayMixin
|
|
5
|
+
from qbasic_core.locc_execution import LOCCExecutionMixin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LOCCMixin(LOCCCommandsMixin, LOCCDisplayMixin, LOCCExecutionMixin):
|
|
9
|
+
"""Combined LOCC mixin — commands, display, and execution."""
|
|
10
|
+
pass
|