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.
Files changed (48) hide show
  1. qbasic.py +113 -0
  2. qbasic_core/__init__.py +80 -0
  3. qbasic_core/__main__.py +6 -0
  4. qbasic_core/analysis.py +179 -0
  5. qbasic_core/backend.py +76 -0
  6. qbasic_core/classic.py +419 -0
  7. qbasic_core/control_flow.py +576 -0
  8. qbasic_core/debug.py +327 -0
  9. qbasic_core/demos.py +356 -0
  10. qbasic_core/display.py +274 -0
  11. qbasic_core/engine.py +126 -0
  12. qbasic_core/engine_state.py +109 -0
  13. qbasic_core/errors.py +37 -0
  14. qbasic_core/exec_context.py +24 -0
  15. qbasic_core/executor.py +861 -0
  16. qbasic_core/expression.py +228 -0
  17. qbasic_core/file_io.py +457 -0
  18. qbasic_core/gates.py +284 -0
  19. qbasic_core/help_text.py +167 -0
  20. qbasic_core/io_protocol.py +33 -0
  21. qbasic_core/locc.py +10 -0
  22. qbasic_core/locc_commands.py +221 -0
  23. qbasic_core/locc_display.py +61 -0
  24. qbasic_core/locc_engine.py +195 -0
  25. qbasic_core/locc_execution.py +389 -0
  26. qbasic_core/memory.py +369 -0
  27. qbasic_core/mock_backend.py +66 -0
  28. qbasic_core/noise_mixin.py +96 -0
  29. qbasic_core/parser.py +564 -0
  30. qbasic_core/patterns.py +186 -0
  31. qbasic_core/profiler.py +156 -0
  32. qbasic_core/program_mgmt.py +369 -0
  33. qbasic_core/protocol.py +77 -0
  34. qbasic_core/py.typed +0 -0
  35. qbasic_core/scope.py +74 -0
  36. qbasic_core/screen.py +115 -0
  37. qbasic_core/state_display.py +60 -0
  38. qbasic_core/statements.py +387 -0
  39. qbasic_core/strings.py +107 -0
  40. qbasic_core/subs.py +261 -0
  41. qbasic_core/sweep.py +82 -0
  42. qbasic_core/terminal.py +1697 -0
  43. qubasic-0.1.0.dist-info/METADATA +736 -0
  44. qubasic-0.1.0.dist-info/RECORD +48 -0
  45. qubasic-0.1.0.dist-info/WHEEL +5 -0
  46. qubasic-0.1.0.dist-info/entry_points.txt +2 -0
  47. qubasic-0.1.0.dist-info/licenses/LICENSE +21 -0
  48. 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))
@@ -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