qubasic 0.10.1__tar.gz → 0.11.1__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.10.1 → qubasic-0.11.1}/CHANGELOG.md +22 -0
- {qubasic-0.10.1/qubasic.egg-info → qubasic-0.11.1}/PKG-INFO +10 -4
- {qubasic-0.10.1 → qubasic-0.11.1}/README.md +9 -3
- {qubasic-0.10.1 → qubasic-0.11.1}/pyproject.toml +1 -1
- {qubasic-0.10.1 → qubasic-0.11.1/qubasic.egg-info}/PKG-INFO +10 -4
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/__init__.py +1 -1
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/control_flow.py +2 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/display.py +21 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/executor.py +13 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/expression.py +13 -1
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/parser.py +10 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/patterns.py +6 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/terminal.py +95 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/tests/test_qubasic.py +81 -1
- {qubasic-0.10.1 → qubasic-0.11.1}/LICENSE +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/MANIFEST.in +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/examples/bell.qb +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/examples/grover3.qb +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/examples/locc_teleport.qb +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/examples/sweep_rx.qb +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic.egg-info/SOURCES.txt +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic.egg-info/entry_points.txt +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic.egg-info/top_level.txt +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/__main__.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/algorithms.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/algos2.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/analysis.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/backend.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/benchmarking.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/bosonic.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/classic.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/cli.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/debug.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/demos.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/dynamics.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/engine.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/engine_state.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/errors.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/exec_context.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/file_io.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/gates.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/help_text.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/locc.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/locc_commands.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/locc_display.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/locc_engine.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/locc_execution.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/memory.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/pauliprop.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/profiler.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/program_mgmt.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/protocol.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/qec.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/qol.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/qudits.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/resources.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/scope.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/screen.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/state_display.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/statements.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/strings.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/subs.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/qubasic_core/sweep.py +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/setup.cfg +0 -0
- {qubasic-0.10.1 → qubasic-0.11.1}/tests/test_features.py +0 -0
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.1 (2026-06-19)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- A configuration/session command (`QUBITS`, `SHOTS`, `METHOD`, `LOCC`, ...) placed on a numbered program line now reports a clear error explaining it must run without a line number (before `RUN`), instead of the misleading `UNKNOWN GATE`. Unnumbered config in a script was, and remains, the correct usage.
|
|
7
|
+
|
|
8
|
+
## 0.11.0 (2026-06-19)
|
|
9
|
+
|
|
10
|
+
Convention-conformance and observability pass: make the language behave the way
|
|
11
|
+
a Qiskit/Python/BASIC user already expects, and make hidden state legible. The
|
|
12
|
+
quantum engine is unchanged.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `STATUS` (and `STATUS JSON`): dumps every active mode (qubits, shots, method, device, seed, `OPTION BASE`, LOCC mode, noise, coupling/basis, pending `SET_STATE`/`SET_DENSITY`, bank) so behavior can be read instead of inferred.
|
|
16
|
+
- Implicit `LET`: a bare assignment works without the keyword (`x = 5`, `s$ = "hi"`, `a(1) = 5`), as in every BASIC.
|
|
17
|
+
- `FIX(x)` truncates toward zero, complementing `INT`.
|
|
18
|
+
- Histograms print a `q(n-1) ... q1 q0` bit-order header and the JSON result carries a `bit_order` field, making the little-endian convention (qubit 0 = rightmost) explicit.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `INT(x)` floors toward negative infinity (`INT(-3.2)` = -4), matching QBASIC; use `FIX` for truncation toward zero.
|
|
22
|
+
- Built-in constants (`PI`, `E`, `TAU`, `SQRT2`) are reserved: a same-named variable can no longer silently shadow them, and assigning one is an error.
|
|
23
|
+
- `MEAS` without a target bit reports a clear error distinguishing it from `MEASURE` (mid-circuit measurement into a classical bit vs collapse into the histogram) instead of `UNKNOWN GATE`.
|
|
24
|
+
|
|
3
25
|
## 0.10.1 (2026-06-19)
|
|
4
26
|
|
|
5
27
|
Expression, string, and PRINT fixes in the classic-BASIC layer. The quantum
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -209,6 +209,8 @@ QUBITS 8 Set qubit count (1-32)
|
|
|
209
209
|
SHOTS 2048 Set measurement shots
|
|
210
210
|
METHOD statevector Set simulation method
|
|
211
211
|
METHOD GPU Set simulation device
|
|
212
|
+
STATUS Show every active mode (qubits, method, LOCC, noise, ...)
|
|
213
|
+
STATUS JSON Same, as machine-readable JSON
|
|
212
214
|
```
|
|
213
215
|
|
|
214
216
|
### Simulation methods
|
|
@@ -220,17 +222,19 @@ Automatic selection: stabilizer for Clifford-only circuits, MPS for >28 qubits,
|
|
|
220
222
|
|
|
221
223
|
```
|
|
222
224
|
LET angle = PI/4
|
|
223
|
-
|
|
225
|
+
x = sin(angle) * 2 LET is optional (x = 5 also works)
|
|
224
226
|
10 RX angle, 0 Use in gate parameters
|
|
225
227
|
VARS List all variables
|
|
226
228
|
CLEAR x Remove a variable
|
|
227
229
|
```
|
|
228
230
|
|
|
231
|
+
Functions and keywords are case-insensitive (`SQRT` and `sqrt` both work).
|
|
232
|
+
|
|
229
233
|
### Constants
|
|
230
|
-
`PI`, `TAU`, `E`, `SQRT2`, `True`, `False`
|
|
234
|
+
`PI`, `TAU`, `E`, `SQRT2`, `True`, `False` (reserved; not usable as variable names)
|
|
231
235
|
|
|
232
236
|
### Math functions
|
|
233
|
-
`sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sqrt`, `log`, `exp`, `abs`, `int
|
|
237
|
+
`sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sqrt`, `log`, `exp`, `abs`, `int` (floors), `fix` (truncates), `float`, `min`, `max`, `round`, `ceil`, `floor`, `len`
|
|
234
238
|
|
|
235
239
|
### Runtime functions
|
|
236
240
|
`RND(x)` random, `TIMER` elapsed seconds, `FRE(0)` free RAM bytes, `POS(0)` cursor column, `PEEK(addr)` memory read, `USR(addr)` call routine
|
|
@@ -364,6 +368,8 @@ DECOMPOSE Gate count breakdown
|
|
|
364
368
|
DENSITY Density matrix
|
|
365
369
|
```
|
|
366
370
|
|
|
371
|
+
Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
|
|
372
|
+
|
|
367
373
|
### Screen modes
|
|
368
374
|
```
|
|
369
375
|
SCREEN 0 Text (default)
|
|
@@ -176,6 +176,8 @@ QUBITS 8 Set qubit count (1-32)
|
|
|
176
176
|
SHOTS 2048 Set measurement shots
|
|
177
177
|
METHOD statevector Set simulation method
|
|
178
178
|
METHOD GPU Set simulation device
|
|
179
|
+
STATUS Show every active mode (qubits, method, LOCC, noise, ...)
|
|
180
|
+
STATUS JSON Same, as machine-readable JSON
|
|
179
181
|
```
|
|
180
182
|
|
|
181
183
|
### Simulation methods
|
|
@@ -187,17 +189,19 @@ Automatic selection: stabilizer for Clifford-only circuits, MPS for >28 qubits,
|
|
|
187
189
|
|
|
188
190
|
```
|
|
189
191
|
LET angle = PI/4
|
|
190
|
-
|
|
192
|
+
x = sin(angle) * 2 LET is optional (x = 5 also works)
|
|
191
193
|
10 RX angle, 0 Use in gate parameters
|
|
192
194
|
VARS List all variables
|
|
193
195
|
CLEAR x Remove a variable
|
|
194
196
|
```
|
|
195
197
|
|
|
198
|
+
Functions and keywords are case-insensitive (`SQRT` and `sqrt` both work).
|
|
199
|
+
|
|
196
200
|
### Constants
|
|
197
|
-
`PI`, `TAU`, `E`, `SQRT2`, `True`, `False`
|
|
201
|
+
`PI`, `TAU`, `E`, `SQRT2`, `True`, `False` (reserved; not usable as variable names)
|
|
198
202
|
|
|
199
203
|
### Math functions
|
|
200
|
-
`sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sqrt`, `log`, `exp`, `abs`, `int
|
|
204
|
+
`sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sqrt`, `log`, `exp`, `abs`, `int` (floors), `fix` (truncates), `float`, `min`, `max`, `round`, `ceil`, `floor`, `len`
|
|
201
205
|
|
|
202
206
|
### Runtime functions
|
|
203
207
|
`RND(x)` random, `TIMER` elapsed seconds, `FRE(0)` free RAM bytes, `POS(0)` cursor column, `PEEK(addr)` memory read, `USR(addr)` call routine
|
|
@@ -331,6 +335,8 @@ DECOMPOSE Gate count breakdown
|
|
|
331
335
|
DENSITY Density matrix
|
|
332
336
|
```
|
|
333
337
|
|
|
338
|
+
Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
|
|
339
|
+
|
|
334
340
|
### Screen modes
|
|
335
341
|
```
|
|
336
342
|
SCREEN 0 Text (default)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -209,6 +209,8 @@ QUBITS 8 Set qubit count (1-32)
|
|
|
209
209
|
SHOTS 2048 Set measurement shots
|
|
210
210
|
METHOD statevector Set simulation method
|
|
211
211
|
METHOD GPU Set simulation device
|
|
212
|
+
STATUS Show every active mode (qubits, method, LOCC, noise, ...)
|
|
213
|
+
STATUS JSON Same, as machine-readable JSON
|
|
212
214
|
```
|
|
213
215
|
|
|
214
216
|
### Simulation methods
|
|
@@ -220,17 +222,19 @@ Automatic selection: stabilizer for Clifford-only circuits, MPS for >28 qubits,
|
|
|
220
222
|
|
|
221
223
|
```
|
|
222
224
|
LET angle = PI/4
|
|
223
|
-
|
|
225
|
+
x = sin(angle) * 2 LET is optional (x = 5 also works)
|
|
224
226
|
10 RX angle, 0 Use in gate parameters
|
|
225
227
|
VARS List all variables
|
|
226
228
|
CLEAR x Remove a variable
|
|
227
229
|
```
|
|
228
230
|
|
|
231
|
+
Functions and keywords are case-insensitive (`SQRT` and `sqrt` both work).
|
|
232
|
+
|
|
229
233
|
### Constants
|
|
230
|
-
`PI`, `TAU`, `E`, `SQRT2`, `True`, `False`
|
|
234
|
+
`PI`, `TAU`, `E`, `SQRT2`, `True`, `False` (reserved; not usable as variable names)
|
|
231
235
|
|
|
232
236
|
### Math functions
|
|
233
|
-
`sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sqrt`, `log`, `exp`, `abs`, `int
|
|
237
|
+
`sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sqrt`, `log`, `exp`, `abs`, `int` (floors), `fix` (truncates), `float`, `min`, `max`, `round`, `ceil`, `floor`, `len`
|
|
234
238
|
|
|
235
239
|
### Runtime functions
|
|
236
240
|
`RND(x)` random, `TIMER` elapsed seconds, `FRE(0)` free RAM bytes, `POS(0)` cursor column, `PEEK(addr)` memory read, `USR(addr)` call routine
|
|
@@ -364,6 +368,8 @@ DECOMPOSE Gate count breakdown
|
|
|
364
368
|
DENSITY Density matrix
|
|
365
369
|
```
|
|
366
370
|
|
|
371
|
+
Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
|
|
372
|
+
|
|
367
373
|
### Screen modes
|
|
368
374
|
```
|
|
369
375
|
SCREEN 0 Text (default)
|
|
@@ -73,6 +73,7 @@ class ControlFlowMixin:
|
|
|
73
73
|
def _cf_let_array(self, stmt: str, run_vars: dict[str, Any],
|
|
74
74
|
parsed: LetArrayStmt) -> tuple[bool, ExecOutcome]:
|
|
75
75
|
name, idx_expr, val_expr = parsed.name, parsed.index_expr, parsed.value_expr
|
|
76
|
+
self._assert_assignable(name)
|
|
76
77
|
base = getattr(self, '_option_base', 0)
|
|
77
78
|
val = self._eval_with_vars(val_expr, run_vars)
|
|
78
79
|
parts = self._split_arg_list(idx_expr)
|
|
@@ -106,6 +107,7 @@ class ControlFlowMixin:
|
|
|
106
107
|
def _cf_let_var(self, stmt: str, run_vars: dict[str, Any],
|
|
107
108
|
parsed: LetStmt) -> tuple[bool, ExecOutcome]:
|
|
108
109
|
name, expr = parsed.name, parsed.expr
|
|
110
|
+
self._assert_assignable(name)
|
|
109
111
|
raw = self._safe_eval(expr, extra_ns=run_vars)
|
|
110
112
|
if isinstance(raw, str):
|
|
111
113
|
raise RuntimeError(
|
|
@@ -40,12 +40,33 @@ class DisplayMixin:
|
|
|
40
40
|
Console(file=buf, highlight=False, force_terminal=force).print(*renderables)
|
|
41
41
|
self.io.write(buf.getvalue())
|
|
42
42
|
|
|
43
|
+
def _bit_order_note(self, display: list) -> str | None:
|
|
44
|
+
"""Human-facing reminder of which bit is which qubit.
|
|
45
|
+
|
|
46
|
+
Bitstrings are little-endian (qubit 0 is the rightmost character), the
|
|
47
|
+
most common source of off-by-reverse mistakes when reading a histogram.
|
|
48
|
+
When the result covers the whole register the positions are labelled
|
|
49
|
+
q(n-1) ... q1 q0; for a measured subset only the convention is shown.
|
|
50
|
+
"""
|
|
51
|
+
if not display:
|
|
52
|
+
return None
|
|
53
|
+
nbits = len(display[0][0])
|
|
54
|
+
nq = getattr(self, 'num_qubits', nbits)
|
|
55
|
+
if nbits == nq and nbits <= 16:
|
|
56
|
+
labels = ' '.join(f'q{i}' for i in range(nbits - 1, -1, -1))
|
|
57
|
+
return f" bit order {labels} (qubit 0 = rightmost)"
|
|
58
|
+
return " bit order qubit 0 = rightmost bit"
|
|
59
|
+
|
|
43
60
|
def print_histogram(self, counts: dict[str, int]) -> None:
|
|
44
61
|
"""Measurement histogram with optional rich-table formatting."""
|
|
45
62
|
total = sum(counts.values())
|
|
46
63
|
sorted_counts = sorted(counts.items(), key=lambda x: -x[1])
|
|
47
64
|
display = sorted_counts[:MAX_HISTOGRAM_STATES]
|
|
48
65
|
|
|
66
|
+
note = self._bit_order_note(display)
|
|
67
|
+
if note:
|
|
68
|
+
self.io.writeln(note)
|
|
69
|
+
|
|
49
70
|
if _RICH:
|
|
50
71
|
self._print_histogram_rich(display, sorted_counts, total)
|
|
51
72
|
else:
|
|
@@ -333,6 +333,19 @@ class ExecutorMixin:
|
|
|
333
333
|
return ExecResult.ADVANCE
|
|
334
334
|
# Fall through to _apply_gate_str for custom gates not in GATE_TABLE
|
|
335
335
|
|
|
336
|
+
# A REPL/config command (QUBITS, SHOTS, METHOD, LOCC, ...) used as a
|
|
337
|
+
# numbered program line reaches here only because it is neither a gate
|
|
338
|
+
# nor a statement. Explain it instead of failing with "UNKNOWN GATE".
|
|
339
|
+
# SEND/SHARE are LOCC statements (handled on the LOCC path), so leave
|
|
340
|
+
# them to their own error.
|
|
341
|
+
_first = stmt.split(None, 1)[0].upper() if stmt.split() else ''
|
|
342
|
+
if ((_first in self._CMD_WITH_ARG or _first in self._CMD_NO_ARG)
|
|
343
|
+
and _first not in ('SEND', 'SHARE')):
|
|
344
|
+
raise QBasicBuildError(
|
|
345
|
+
f"{_first} is a configuration/session command, not a program "
|
|
346
|
+
f"statement; run it without a line number (before RUN), e.g. "
|
|
347
|
+
f"QUBITS 3 / SHOTS 1024 / METHOD statevector / LOCC 2 2")
|
|
348
|
+
|
|
336
349
|
# Slow path: subroutine expansion + gate dispatch
|
|
337
350
|
expanded = self._expand_statement(stmt)
|
|
338
351
|
for gate_str in expanded:
|
|
@@ -135,7 +135,9 @@ class ExpressionMixin:
|
|
|
135
135
|
'asin': math.asin, 'acos': math.acos, 'atan': math.atan,
|
|
136
136
|
'atan2': math.atan2,
|
|
137
137
|
'sqrt': math.sqrt, 'log': math.log, 'exp': math.exp,
|
|
138
|
-
|
|
138
|
+
# INT floors toward negative infinity, as in QBASIC (INT(-3.2) = -4).
|
|
139
|
+
# FIX truncates toward zero (FIX(-3.2) = -3) for the other convention.
|
|
140
|
+
'abs': abs, 'int': math.floor, 'fix': math.trunc, 'float': float,
|
|
139
141
|
'min': min, 'max': max, 'round': round, 'len': len,
|
|
140
142
|
'ceil': math.ceil, 'floor': math.floor,
|
|
141
143
|
}
|
|
@@ -406,6 +408,16 @@ class ExpressionMixin:
|
|
|
406
408
|
"""
|
|
407
409
|
return bool(self._safe_eval(cond, extra_ns=run_vars))
|
|
408
410
|
|
|
411
|
+
# Built-in constant names that may not be used as variable/array names, so a
|
|
412
|
+
# value like E never silently shadows (or is shadowed by) the constant.
|
|
413
|
+
_RESERVED_CONST_NAMES = frozenset({'PI', 'TAU', 'E', 'SQRT2', 'TRUE', 'FALSE'})
|
|
414
|
+
|
|
415
|
+
def _assert_assignable(self, name: str) -> None:
|
|
416
|
+
"""Reject assignment to a built-in constant name (case-insensitive)."""
|
|
417
|
+
if name.upper() in self._RESERVED_CONST_NAMES:
|
|
418
|
+
raise ValueError(
|
|
419
|
+
f"RESERVED: '{name}' is a built-in constant; choose another name")
|
|
420
|
+
|
|
409
421
|
def _run_timer(self) -> float:
|
|
410
422
|
"""Return elapsed time since terminal start."""
|
|
411
423
|
return time.time() - getattr(self, '_start_time', time.time())
|
|
@@ -22,6 +22,7 @@ from qubasic_core.patterns import (
|
|
|
22
22
|
RE_LPRINT, RE_SCREEN, RE_COLOR, RE_LOCATE,
|
|
23
23
|
RE_ON_MEASURE, RE_ON_TIMER, RE_IMPORT, RE_CHAIN, RE_MERGE,
|
|
24
24
|
RE_LET_STR, RE_DIM_MULTI, RE_MEASURE_BASIS, RE_SYNDROME,
|
|
25
|
+
RE_IMPLICIT_ASSIGN,
|
|
25
26
|
)
|
|
26
27
|
from qubasic_core.statements import (
|
|
27
28
|
Stmt, RawStmt, GateStmt, RemStmt, MeasureStmt, EndStmt, ReturnStmt,
|
|
@@ -578,6 +579,15 @@ def parse_stmt(raw: str) -> Stmt:
|
|
|
578
579
|
if len(parts) > 1:
|
|
579
580
|
return CompoundStmt(raw=raw, parts=tuple(parts))
|
|
580
581
|
|
|
582
|
+
# ── Implicit LET (assignment without the LET keyword) ──────────
|
|
583
|
+
# A line whose lvalue is followed by a single '=' is an assignment, even
|
|
584
|
+
# when it collides with a gate name (X = 5 is "assign X", not the X gate).
|
|
585
|
+
# Tried before gate dispatch so bare assignment works as in every BASIC.
|
|
586
|
+
if RE_IMPLICIT_ASSIGN.match(text):
|
|
587
|
+
result = _handle_let(f"LET {text}", raw)
|
|
588
|
+
if result is not None:
|
|
589
|
+
return result
|
|
590
|
+
|
|
581
591
|
# ── Gate application ────────────────────────────────────────────
|
|
582
592
|
canonical = GATE_ALIASES.get(first_word, first_word)
|
|
583
593
|
if canonical in GATE_TABLE:
|
|
@@ -102,6 +102,11 @@ RE_ON_MEASURE = re.compile(r'ON\s+MEASURE\s+GOSUB\s+(\d+)', re.IGNORECASE)
|
|
|
102
102
|
RE_ON_TIMER = re.compile(r'ON\s+TIMER\s*\((\d+)\)\s+GOSUB\s+(\d+)', re.IGNORECASE)
|
|
103
103
|
RE_DIM_MULTI = re.compile(r'DIM\s+(\w+)\((\d+(?:\s*,\s*\d+)*)\)', re.IGNORECASE)
|
|
104
104
|
RE_LET_STR = re.compile(r'LET\s+(\w+\$)\s*=\s*(.*)', re.IGNORECASE)
|
|
105
|
+
# Implicit LET: an assignment written without the LET keyword (x = 5, s$ = "hi",
|
|
106
|
+
# a(1) = 5, p.x = 3). Anchored lvalue followed by a single '=' that is not part
|
|
107
|
+
# of '==' / '<=' / '>=' / '!=' / '<>'. Routed through the LET handlers.
|
|
108
|
+
RE_IMPLICIT_ASSIGN = re.compile(
|
|
109
|
+
r'[A-Za-z_]\w*\$?(?:\.\w+)?(?:\([^)]*\))?\s*=(?!=)', re.IGNORECASE)
|
|
105
110
|
|
|
106
111
|
__all__ = [
|
|
107
112
|
"RE_LINE_NUM",
|
|
@@ -185,4 +190,5 @@ __all__ = [
|
|
|
185
190
|
"RE_ON_TIMER",
|
|
186
191
|
"RE_DIM_MULTI",
|
|
187
192
|
"RE_LET_STR",
|
|
193
|
+
"RE_IMPLICIT_ASSIGN",
|
|
188
194
|
]
|
|
@@ -508,6 +508,7 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
508
508
|
'LIST': 'cmd_list', 'QUBITS': 'cmd_qubits', 'SHOTS': 'cmd_shots',
|
|
509
509
|
'METHOD': 'cmd_method', 'DEF': 'cmd_def', 'REG': 'cmd_reg',
|
|
510
510
|
'LET': 'cmd_let', 'STATE': 'cmd_state', 'BLOCH': 'cmd_bloch',
|
|
511
|
+
'STATUS': 'cmd_status',
|
|
511
512
|
'DEMO': 'cmd_demo', 'DELETE': 'cmd_delete', 'RENUM': 'cmd_renum',
|
|
512
513
|
'SAVE': 'cmd_save', 'LOAD': 'cmd_load', 'SWEEP': 'cmd_sweep',
|
|
513
514
|
'INCLUDE': 'cmd_include', 'EXPORT': 'cmd_export',
|
|
@@ -593,6 +594,17 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
593
594
|
except Exception as e:
|
|
594
595
|
self.io.writeln(f"?ERROR: {e}")
|
|
595
596
|
else:
|
|
597
|
+
# Implicit LET: a bare assignment (x = 5, s$ = "hi") routed to the
|
|
598
|
+
# LET handler so it works without the keyword, as in every BASIC.
|
|
599
|
+
from qubasic_core.patterns import RE_IMPLICIT_ASSIGN
|
|
600
|
+
if RE_IMPLICIT_ASSIGN.match(line.strip()):
|
|
601
|
+
try:
|
|
602
|
+
self.cmd_let(line.strip())
|
|
603
|
+
except QBasicError as e:
|
|
604
|
+
self.io.writeln(f"?{e.message}")
|
|
605
|
+
except Exception as e:
|
|
606
|
+
self.io.writeln(f"?ERROR: {e}")
|
|
607
|
+
return
|
|
596
608
|
# Try as immediate gate / subroutine
|
|
597
609
|
try:
|
|
598
610
|
self.run_immediate(line)
|
|
@@ -1066,6 +1078,7 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1066
1078
|
self.io.writeln("?USAGE: LET <var> = <expr>")
|
|
1067
1079
|
return
|
|
1068
1080
|
name = m.group(1)
|
|
1081
|
+
self._assert_assignable(name)
|
|
1069
1082
|
raw = self._safe_eval(m.group(2))
|
|
1070
1083
|
if isinstance(raw, str):
|
|
1071
1084
|
self.io.writeln(
|
|
@@ -1080,6 +1093,78 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1080
1093
|
rec[field] = val
|
|
1081
1094
|
self.io.writeln(f"{name} = {val}")
|
|
1082
1095
|
|
|
1096
|
+
def _status_dict(self) -> dict:
|
|
1097
|
+
"""Snapshot every active mode that silently changes how a line behaves.
|
|
1098
|
+
|
|
1099
|
+
Returned as plain JSON-serializable values so a caller can read context
|
|
1100
|
+
instead of inferring it from prior commands.
|
|
1101
|
+
"""
|
|
1102
|
+
cmap = getattr(self, '_coupling_map', None)
|
|
1103
|
+
return {
|
|
1104
|
+
'qubits': self.num_qubits,
|
|
1105
|
+
'shots': self.shots,
|
|
1106
|
+
'method': self.sim_method,
|
|
1107
|
+
'device': self.sim_device,
|
|
1108
|
+
'seed': getattr(self, '_seed', None),
|
|
1109
|
+
'bit_order': 'little-endian (qubit 0 = rightmost bit)',
|
|
1110
|
+
'option_base': getattr(self, '_option_base', 0),
|
|
1111
|
+
'locc_mode': bool(getattr(self, 'locc_mode', False)),
|
|
1112
|
+
'locc_registers': (list(self.locc.names)
|
|
1113
|
+
if getattr(self, 'locc_mode', False) and getattr(self, 'locc', None)
|
|
1114
|
+
else []),
|
|
1115
|
+
'noise_active': getattr(self, '_noise_model', None) is not None,
|
|
1116
|
+
'noise_spec': getattr(self, '_noise_spec', None),
|
|
1117
|
+
'coupling_map': ([list(e) for e in cmap] if cmap else None),
|
|
1118
|
+
'basis_gates': getattr(self, '_basis_gates', None),
|
|
1119
|
+
'pending_set_state': getattr(self, '_pending_set_state', None) is not None,
|
|
1120
|
+
'pending_set_density': getattr(self, '_pending_set_density', None) is not None,
|
|
1121
|
+
'screen_mode': getattr(self, '_screen_mode', 0),
|
|
1122
|
+
'trace_mode': bool(getattr(self, '_trace_mode', False)),
|
|
1123
|
+
'bank': getattr(self, '_current_slot', 0),
|
|
1124
|
+
'program_lines': len(self.program),
|
|
1125
|
+
'variables': len(self.variables),
|
|
1126
|
+
'arrays': len(self.arrays),
|
|
1127
|
+
'subroutines': len(self.subroutines),
|
|
1128
|
+
'custom_gates': sorted(self._custom_gates.keys()),
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
def cmd_status(self, rest: str = '') -> None:
|
|
1132
|
+
"""STATUS [JSON] — show every active mode (qubits, method, LOCC, noise,
|
|
1133
|
+
OPTION BASE, pending state injections, bank, ...) so behavior is readable
|
|
1134
|
+
rather than inferred. STATUS JSON emits the same data as JSON."""
|
|
1135
|
+
d = self._status_dict()
|
|
1136
|
+
if rest.strip().upper() == 'JSON':
|
|
1137
|
+
import json
|
|
1138
|
+
self.io.writeln(json.dumps(d, indent=2, default=str))
|
|
1139
|
+
return
|
|
1140
|
+
w = self.io.writeln
|
|
1141
|
+
w(" QUBASIC status")
|
|
1142
|
+
w(f" qubits {d['qubits']} shots {d['shots']} "
|
|
1143
|
+
f"method {d['method']} device {d['device']}")
|
|
1144
|
+
w(f" seed {d['seed']}")
|
|
1145
|
+
w(f" bit order {d['bit_order']}")
|
|
1146
|
+
w(f" option base {d['option_base']}")
|
|
1147
|
+
if d['locc_mode']:
|
|
1148
|
+
w(f" LOCC mode on registers {d['locc_registers']}")
|
|
1149
|
+
w(f" noise {('on ' + str(d['noise_spec'])) if d['noise_active'] else 'off'}")
|
|
1150
|
+
if d['coupling_map']:
|
|
1151
|
+
w(f" coupling {d['coupling_map']}")
|
|
1152
|
+
if d['basis_gates']:
|
|
1153
|
+
w(f" basis gates {d['basis_gates']}")
|
|
1154
|
+
if d['pending_set_state']:
|
|
1155
|
+
w(" pending SET_STATE applies on next RUN")
|
|
1156
|
+
if d['pending_set_density']:
|
|
1157
|
+
w(" pending SET_DENSITY applies on next RUN")
|
|
1158
|
+
if d['screen_mode']:
|
|
1159
|
+
w(f" screen mode {d['screen_mode']}")
|
|
1160
|
+
if d['trace_mode']:
|
|
1161
|
+
w(" trace on")
|
|
1162
|
+
w(f" bank {d['bank']}")
|
|
1163
|
+
w(f" program {d['program_lines']} lines, {d['variables']} vars, "
|
|
1164
|
+
f"{d['arrays']} arrays, {d['subroutines']} subs")
|
|
1165
|
+
if d['custom_gates']:
|
|
1166
|
+
w(f" custom gates {d['custom_gates']}")
|
|
1167
|
+
|
|
1083
1168
|
# cmd_defs, cmd_regs, cmd_vars provided by ProgramMgmtMixin.
|
|
1084
1169
|
|
|
1085
1170
|
# ── Run ───────────────────────────────────────────────────────────
|
|
@@ -1500,6 +1585,8 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1500
1585
|
'counts': self.last_counts or {},
|
|
1501
1586
|
'num_qubits': self.num_qubits,
|
|
1502
1587
|
'shots': self.shots,
|
|
1588
|
+
# Bitstrings are little-endian: the rightmost character is qubit 0.
|
|
1589
|
+
'bit_order': 'little-endian (qubit 0 = rightmost bit)',
|
|
1503
1590
|
}
|
|
1504
1591
|
uvars = {k: v for k, v in self.variables.items()
|
|
1505
1592
|
if not k.startswith('_') and isinstance(v, (int, float, str, bool))}
|
|
@@ -1760,6 +1847,14 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1760
1847
|
"""
|
|
1761
1848
|
m = RE_MEAS.match(stmt)
|
|
1762
1849
|
if not m:
|
|
1850
|
+
# Bare MEAS (no "-> bit") is the classic confusion with MEASURE:
|
|
1851
|
+
# MEAS is a mid-circuit measurement that must name a classical bit,
|
|
1852
|
+
# MEASURE collapses qubits into the result histogram.
|
|
1853
|
+
if re.match(r'MEAS\b', stmt, re.IGNORECASE):
|
|
1854
|
+
raise ValueError(
|
|
1855
|
+
"MEAS needs a target bit: MEAS <qubit> -> <bit> "
|
|
1856
|
+
"(mid-circuit measurement used by IF). To collapse qubits "
|
|
1857
|
+
"into the result histogram, use MEASURE <qubit>.")
|
|
1763
1858
|
return False
|
|
1764
1859
|
qubit = int(self._eval_with_vars(m.group(1), run_vars))
|
|
1765
1860
|
var = m.group(2)
|
|
@@ -1963,7 +1963,8 @@ class TestExpressionStringRegressions(unittest.TestCase):
|
|
|
1963
1963
|
self.assertAlmostEqual(self.t.eval_expr('SQRT(2)'), self.t.eval_expr('sqrt(2)'))
|
|
1964
1964
|
self.assertAlmostEqual(self.t.eval_expr('SIN(0)'), 0.0)
|
|
1965
1965
|
self.assertEqual(self.t.eval_expr('ABS(-5)'), 5.0)
|
|
1966
|
-
self.assertEqual(self.t.eval_expr('INT(-3.2)'),
|
|
1966
|
+
self.assertEqual(self.t.eval_expr('INT(-3.2)'), -4.0) # BASIC INT floors
|
|
1967
|
+
self.assertEqual(self.t.eval_expr('FIX(-3.2)'), -3.0) # FIX truncates toward zero
|
|
1967
1968
|
|
|
1968
1969
|
def test_rnd_case_insensitive(self):
|
|
1969
1970
|
for expr in ('RND(1)', 'rnd(1)'):
|
|
@@ -2016,6 +2017,85 @@ class TestExpressionStringRegressions(unittest.TestCase):
|
|
|
2016
2017
|
self.t._eval_print_item('GARBAGEFUNC(3)', {})
|
|
2017
2018
|
|
|
2018
2019
|
|
|
2020
|
+
class TestConventionAndStateRegressions(unittest.TestCase):
|
|
2021
|
+
"""Convention-conformance and hidden-state fixes: INT floors, implicit LET,
|
|
2022
|
+
MEAS/MEASURE disambiguation, reserved constants, STATUS, bit-order labels."""
|
|
2023
|
+
|
|
2024
|
+
def setUp(self):
|
|
2025
|
+
self.t = QBasicTerminal()
|
|
2026
|
+
self.t.num_qubits = 2
|
|
2027
|
+
|
|
2028
|
+
def test_int_floors_fix_truncates(self):
|
|
2029
|
+
self.assertEqual(self.t.eval_expr('INT(-3.2)'), -4.0)
|
|
2030
|
+
self.assertEqual(self.t.eval_expr('INT(3.7)'), 3.0)
|
|
2031
|
+
self.assertEqual(self.t.eval_expr('FIX(-3.2)'), -3.0)
|
|
2032
|
+
self.assertEqual(self.t.eval_expr('FIX(3.7)'), 3.0)
|
|
2033
|
+
|
|
2034
|
+
def test_implicit_let_parses_as_assignment(self):
|
|
2035
|
+
from qubasic_core.parser import parse_stmt
|
|
2036
|
+
from qubasic_core.statements import LetStmt, LetStrStmt, LetArrayStmt
|
|
2037
|
+
self.assertIsInstance(parse_stmt('x = 5'), LetStmt)
|
|
2038
|
+
self.assertIsInstance(parse_stmt('s$ = "hi"'), LetStrStmt)
|
|
2039
|
+
self.assertIsInstance(parse_stmt('a(1) = 5'), LetArrayStmt)
|
|
2040
|
+
# '==' is a comparison, never an implicit assignment.
|
|
2041
|
+
self.assertNotIsInstance(parse_stmt('x == 5'), LetStmt)
|
|
2042
|
+
|
|
2043
|
+
def test_implicit_let_runs(self):
|
|
2044
|
+
t = QBasicTerminal(); t.num_qubits = 1
|
|
2045
|
+
t.program = {10: 'x = 5', 20: 's$ = "ok"', 30: 'y = x + 1'}
|
|
2046
|
+
capture(t.cmd_run)
|
|
2047
|
+
self.assertEqual(t.variables['x'], 5.0)
|
|
2048
|
+
self.assertEqual(t.variables['s$'], 'ok')
|
|
2049
|
+
self.assertEqual(t.variables['y'], 6.0)
|
|
2050
|
+
|
|
2051
|
+
def test_meas_bare_form_errors_measure_does_not(self):
|
|
2052
|
+
# MEAS without a target bit raises a helpful error...
|
|
2053
|
+
with self.assertRaises(ValueError):
|
|
2054
|
+
self.t._try_exec_meas('MEAS 0', None, {})
|
|
2055
|
+
# ...while MEASURE is a different statement and is left untouched here.
|
|
2056
|
+
self.assertFalse(self.t._try_exec_meas('MEASURE 0', None, {}))
|
|
2057
|
+
|
|
2058
|
+
def test_reserved_constants_not_assignable(self):
|
|
2059
|
+
for name in ('E', 'e', 'PI', 'pi', 'TAU', 'SQRT2'):
|
|
2060
|
+
with self.assertRaises(ValueError):
|
|
2061
|
+
self.t._assert_assignable(name)
|
|
2062
|
+
self.t._assert_assignable('x') # ordinary names are fine
|
|
2063
|
+
self.t._assert_assignable('e1')
|
|
2064
|
+
|
|
2065
|
+
def test_status_dict_reports_modes(self):
|
|
2066
|
+
t = QBasicTerminal(); t.num_qubits = 3; t.shots = 128
|
|
2067
|
+
d = t._status_dict()
|
|
2068
|
+
self.assertEqual(d['qubits'], 3)
|
|
2069
|
+
self.assertEqual(d['shots'], 128)
|
|
2070
|
+
self.assertIn('rightmost', d['bit_order'])
|
|
2071
|
+
self.assertFalse(d['noise_active'])
|
|
2072
|
+
self.assertIn('option_base', d)
|
|
2073
|
+
|
|
2074
|
+
def test_bit_order_label_and_result(self):
|
|
2075
|
+
t = QBasicTerminal(); t.num_qubits = 3
|
|
2076
|
+
note = t._bit_order_note([('001', 5)])
|
|
2077
|
+
self.assertIn('q2 q1 q0', note)
|
|
2078
|
+
self.assertIn('rightmost', note)
|
|
2079
|
+
self.assertIn('bit_order', t.result())
|
|
2080
|
+
|
|
2081
|
+
def test_config_command_as_program_line_errors_clearly(self):
|
|
2082
|
+
# A config/REPL command used as a numbered program line explains itself
|
|
2083
|
+
# instead of failing with the misleading "UNKNOWN GATE".
|
|
2084
|
+
for cmd in ('QUBITS 3', 'SHOTS 256', 'METHOD stabilizer', 'LOCC 2 2'):
|
|
2085
|
+
t = QBasicTerminal(); t.num_qubits = 2
|
|
2086
|
+
t.program = {10: cmd, 20: 'H 0', 30: 'MEASURE'}
|
|
2087
|
+
_, out = capture(t.cmd_run)
|
|
2088
|
+
self.assertIn('configuration', out.lower())
|
|
2089
|
+
self.assertNotIn('UNKNOWN GATE', out)
|
|
2090
|
+
|
|
2091
|
+
def test_subroutine_call_not_treated_as_config_command(self):
|
|
2092
|
+
t = QBasicTerminal(); t.num_qubits = 2
|
|
2093
|
+
t.process('DEF BELL = H 0 : CX 0,1', track_undo=False)
|
|
2094
|
+
t.program = {10: 'BELL', 20: 'MEASURE'}
|
|
2095
|
+
capture(t.cmd_run)
|
|
2096
|
+
self.assertIsNotNone(t.last_counts) # ran; not blocked as a "command"
|
|
2097
|
+
|
|
2098
|
+
|
|
2019
2099
|
if __name__ == '__main__':
|
|
2020
2100
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
2021
2101
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|