qubasic 0.12.0__tar.gz → 0.13.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.12.0 → qubasic-0.13.0}/CHANGELOG.md +17 -0
- {qubasic-0.12.0/qubasic.egg-info → qubasic-0.13.0}/PKG-INFO +5 -3
- {qubasic-0.12.0 → qubasic-0.13.0}/README.md +4 -2
- {qubasic-0.12.0 → qubasic-0.13.0}/pyproject.toml +1 -1
- {qubasic-0.12.0 → qubasic-0.13.0/qubasic.egg-info}/PKG-INFO +5 -3
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/__init__.py +1 -1
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/cli.py +33 -1
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/control_flow.py +6 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/expression.py +25 -11
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/patterns.py +1 -1
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/strings.py +4 -1
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/terminal.py +41 -13
- {qubasic-0.12.0 → qubasic-0.13.0}/tests/test_features.py +22 -15
- {qubasic-0.12.0 → qubasic-0.13.0}/tests/test_qubasic.py +52 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/LICENSE +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/MANIFEST.in +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/examples/bell.qb +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/examples/grover3.qb +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/examples/locc_teleport.qb +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/examples/sweep_rx.qb +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic.egg-info/SOURCES.txt +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic.egg-info/entry_points.txt +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic.egg-info/top_level.txt +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/__main__.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/algorithms.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/algos2.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/analysis.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/backend.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/benchmarking.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/bosonic.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/classic.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/debug.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/demos.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/display.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/dynamics.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/engine.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/engine_state.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/errors.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/exec_context.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/executor.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/file_io.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/gates.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/help_text.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/locc.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/locc_commands.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/locc_display.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/locc_engine.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/locc_execution.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/memory.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/parser.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/pauliprop.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/profiler.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/program_mgmt.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/protocol.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/qec.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/qol.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/qudits.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/resources.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/scope.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/screen.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/state_display.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/statements.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/subs.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/qubasic_core/sweep.py +0 -0
- {qubasic-0.12.0 → qubasic-0.13.0}/setup.cfg +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0 (2026-06-19)
|
|
4
|
+
|
|
5
|
+
Closes the remaining audit gaps in the classic-BASIC layer (conventions,
|
|
6
|
+
inspection, and an agent contract). The quantum engine is unchanged.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `qubasic --spec` emits a machine-readable JSON contract (version, commands with arg flag and one-line help, gates, functions, constants, bit order) so an agent can load the exact surface of the installed version.
|
|
10
|
+
- `REDIM PRESERVE name(n)` keeps existing elements; plain `REDIM` clears to zeros (QBASIC semantics).
|
|
11
|
+
- `DENSITY` shows the density matrix after a measured `SET_DENSITY` run, not only a no-MEASURE one.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- `DIM` uses inclusive sizing: `DIM a(n)` spans indices base..n (the declared top index is valid), matching QBASIC instead of the previous C-style element count.
|
|
15
|
+
- `round()` rounds half away from zero (`round(2.5)` = 3), not Python's banker's rounding.
|
|
16
|
+
- `STR$(n)` reserves a leading space for non-negative numbers (`STR$(42)` = " 42").
|
|
17
|
+
- A chained comparison such as `a < b < c` now raises a clear "ambiguous" error instead of silently using Python chaining; write `(a < b) AND (b < c)`.
|
|
18
|
+
- `PRINT`ing a mid-circuit measurement bit (`MEAS`/`SYNDROME -> var`) shows "mid-circuit bit, resolved per shot" rather than the placeholder 0.
|
|
19
|
+
|
|
3
20
|
## 0.12.0 (2026-06-19)
|
|
4
21
|
|
|
5
22
|
Correctness and robustness pass on the classic-BASIC layer, from an extended
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -78,6 +78,7 @@ python -m qubasic_core Same, without installing
|
|
|
78
78
|
qubasic script.qb Run a script file
|
|
79
79
|
qubasic --quiet script Suppress banner, output results only
|
|
80
80
|
qubasic --json script Machine-readable JSON output
|
|
81
|
+
qubasic --spec Print a JSON contract (commands, gates, functions)
|
|
81
82
|
qubasic --help Show CLI help
|
|
82
83
|
```
|
|
83
84
|
|
|
@@ -256,12 +257,13 @@ DIM data(10) 1D array
|
|
|
256
257
|
DIM matrix(3, 3) Multi-dimensional (flat storage)
|
|
257
258
|
LET data(0) = PI
|
|
258
259
|
LET names$(0) = "alice" String array (name$ elements hold strings)
|
|
259
|
-
REDIM data(20) Resize
|
|
260
|
+
REDIM data(20) Resize, clearing to zeros
|
|
261
|
+
REDIM PRESERVE data(20) Resize, keeping existing data
|
|
260
262
|
ERASE data Delete array
|
|
261
263
|
OPTION BASE 1 Set array index base
|
|
262
264
|
```
|
|
263
265
|
|
|
264
|
-
A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
|
|
266
|
+
`DIM a(n)` is inclusive: it spans indices base..n, so the declared top index is valid. A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
|
|
265
267
|
|
|
266
268
|
## Control flow
|
|
267
269
|
|
|
@@ -45,6 +45,7 @@ python -m qubasic_core Same, without installing
|
|
|
45
45
|
qubasic script.qb Run a script file
|
|
46
46
|
qubasic --quiet script Suppress banner, output results only
|
|
47
47
|
qubasic --json script Machine-readable JSON output
|
|
48
|
+
qubasic --spec Print a JSON contract (commands, gates, functions)
|
|
48
49
|
qubasic --help Show CLI help
|
|
49
50
|
```
|
|
50
51
|
|
|
@@ -223,12 +224,13 @@ DIM data(10) 1D array
|
|
|
223
224
|
DIM matrix(3, 3) Multi-dimensional (flat storage)
|
|
224
225
|
LET data(0) = PI
|
|
225
226
|
LET names$(0) = "alice" String array (name$ elements hold strings)
|
|
226
|
-
REDIM data(20) Resize
|
|
227
|
+
REDIM data(20) Resize, clearing to zeros
|
|
228
|
+
REDIM PRESERVE data(20) Resize, keeping existing data
|
|
227
229
|
ERASE data Delete array
|
|
228
230
|
OPTION BASE 1 Set array index base
|
|
229
231
|
```
|
|
230
232
|
|
|
231
|
-
A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
|
|
233
|
+
`DIM a(n)` is inclusive: it spans indices base..n, so the declared top index is valid. A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
|
|
232
234
|
|
|
233
235
|
## Control flow
|
|
234
236
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -78,6 +78,7 @@ python -m qubasic_core Same, without installing
|
|
|
78
78
|
qubasic script.qb Run a script file
|
|
79
79
|
qubasic --quiet script Suppress banner, output results only
|
|
80
80
|
qubasic --json script Machine-readable JSON output
|
|
81
|
+
qubasic --spec Print a JSON contract (commands, gates, functions)
|
|
81
82
|
qubasic --help Show CLI help
|
|
82
83
|
```
|
|
83
84
|
|
|
@@ -256,12 +257,13 @@ DIM data(10) 1D array
|
|
|
256
257
|
DIM matrix(3, 3) Multi-dimensional (flat storage)
|
|
257
258
|
LET data(0) = PI
|
|
258
259
|
LET names$(0) = "alice" String array (name$ elements hold strings)
|
|
259
|
-
REDIM data(20) Resize
|
|
260
|
+
REDIM data(20) Resize, clearing to zeros
|
|
261
|
+
REDIM PRESERVE data(20) Resize, keeping existing data
|
|
260
262
|
ERASE data Delete array
|
|
261
263
|
OPTION BASE 1 Set array index base
|
|
262
264
|
```
|
|
263
265
|
|
|
264
|
-
A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
|
|
266
|
+
`DIM a(n)` is inclusive: it spans indices base..n, so the declared top index is valid. A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
|
|
265
267
|
|
|
266
268
|
## Control flow
|
|
267
269
|
|
|
@@ -57,8 +57,9 @@ def main():
|
|
|
57
57
|
quiet = '--quiet' in args or '-q' in args
|
|
58
58
|
json_mode = '--json' in args
|
|
59
59
|
agent_mode = '--agent' in args
|
|
60
|
+
spec_mode = '--spec' in args
|
|
60
61
|
seed_val = None
|
|
61
|
-
for flag in ('--quiet', '-q', '--json', '--agent'):
|
|
62
|
+
for flag in ('--quiet', '-q', '--json', '--agent', '--spec'):
|
|
62
63
|
args = [a for a in args if a != flag]
|
|
63
64
|
# Parse --seed N
|
|
64
65
|
filtered = []
|
|
@@ -93,6 +94,37 @@ def main():
|
|
|
93
94
|
print("Type HELP inside the REPL for full command reference.")
|
|
94
95
|
sys.exit(0)
|
|
95
96
|
|
|
97
|
+
if spec_mode:
|
|
98
|
+
# Machine-readable contract for agents: commands, gates, functions,
|
|
99
|
+
# constants, and conventions for the installed version.
|
|
100
|
+
from qubasic_core.engine import GATE_TABLE
|
|
101
|
+
from qubasic_core.expression import ExpressionMixin
|
|
102
|
+
|
|
103
|
+
def _help1(mname):
|
|
104
|
+
doc = (getattr(getattr(QBasicTerminal, mname, None), '__doc__', '') or '').strip()
|
|
105
|
+
return doc.split('\n')[0].strip()
|
|
106
|
+
|
|
107
|
+
cmds = []
|
|
108
|
+
for tbl, takes in ((QBasicTerminal._CMD_WITH_ARG, True),
|
|
109
|
+
(QBasicTerminal._CMD_NO_ARG, False)):
|
|
110
|
+
for cname, mname in tbl.items():
|
|
111
|
+
cmds.append({'name': cname, 'takes_arg': takes, 'help': _help1(mname)})
|
|
112
|
+
spec = {
|
|
113
|
+
'name': 'qubasic',
|
|
114
|
+
'version': __version__,
|
|
115
|
+
'bit_order': 'little-endian (qubit 0 = rightmost bit)',
|
|
116
|
+
'commands': sorted(cmds, key=lambda c: c['name']),
|
|
117
|
+
'gates': sorted(GATE_TABLE.keys()),
|
|
118
|
+
'functions': sorted(
|
|
119
|
+
set(ExpressionMixin._SAFE_FUNCS)
|
|
120
|
+
| {'RND', 'TIMER', 'POS', 'PEEK', 'USR', 'EOF', 'FRE',
|
|
121
|
+
'LEFT$', 'RIGHT$', 'MID$', 'CHR$', 'STR$', 'HEX$', 'BIN$',
|
|
122
|
+
'ASC', 'VAL', 'INSTR', 'LEN'}),
|
|
123
|
+
'constants': sorted(ExpressionMixin._SAFE_CONSTS.keys()),
|
|
124
|
+
}
|
|
125
|
+
print(_json.dumps(spec, indent=2))
|
|
126
|
+
sys.exit(0)
|
|
127
|
+
|
|
96
128
|
term = QBasicTerminal()
|
|
97
129
|
# JSON mode implies agent use, so confine file writes to the working dir.
|
|
98
130
|
term.agent_mode = agent_mode or json_mode
|
|
@@ -184,6 +184,12 @@ class ControlFlowMixin:
|
|
|
184
184
|
# Quoted literal: emit verbatim (no substitution, no SPC/TAB).
|
|
185
185
|
if (item[0] == '"' and item[-1] == '"') or (item[0] == "'" and item[-1] == "'"):
|
|
186
186
|
return item[1:-1]
|
|
187
|
+
# A mid-circuit measurement bit (MEAS/SYNDROME -> var) has no single
|
|
188
|
+
# value in standard mode; it is resolved per shot inside the if_test.
|
|
189
|
+
# Show that instead of the placeholder 0.
|
|
190
|
+
cb = getattr(self, '_classical_bits', None)
|
|
191
|
+
if cb and item in cb:
|
|
192
|
+
return f"<{item}: mid-circuit bit, resolved per shot>"
|
|
187
193
|
text = self._substitute_vars(item, run_vars)
|
|
188
194
|
|
|
189
195
|
def _spaces(m):
|
|
@@ -124,6 +124,18 @@ def _basic_xor(a: Any, b: Any) -> int:
|
|
|
124
124
|
return _as_int(a) ^ _as_int(b)
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
def _basic_round(x: Any, ndigits: Any = None) -> float:
|
|
128
|
+
"""Round half away from zero (BASIC convention), not Python's half-to-even.
|
|
129
|
+
|
|
130
|
+
round(2.5) == 3, round(-2.5) == -3, round(2.345, 2) == 2.35.
|
|
131
|
+
"""
|
|
132
|
+
nd = int(ndigits) if ndigits is not None else 0
|
|
133
|
+
f = 10 ** nd
|
|
134
|
+
y = float(x) * f
|
|
135
|
+
r = math.floor(y + 0.5) if y >= 0 else math.ceil(y - 0.5)
|
|
136
|
+
return (r / f) if nd else float(r)
|
|
137
|
+
|
|
138
|
+
|
|
127
139
|
class ExpressionMixin:
|
|
128
140
|
"""AST-based safe expression evaluation. No eval().
|
|
129
141
|
|
|
@@ -138,7 +150,7 @@ class ExpressionMixin:
|
|
|
138
150
|
# INT floors toward negative infinity, as in QBASIC (INT(-3.2) = -4).
|
|
139
151
|
# FIX truncates toward zero (FIX(-3.2) = -3) for the other convention.
|
|
140
152
|
'abs': abs, 'int': math.floor, 'fix': math.trunc, 'float': float,
|
|
141
|
-
'min': min, 'max': max, 'round':
|
|
153
|
+
'min': min, 'max': max, 'round': _basic_round, 'len': len,
|
|
142
154
|
'ceil': math.ceil, 'floor': math.floor,
|
|
143
155
|
}
|
|
144
156
|
_SAFE_CONSTS = {
|
|
@@ -203,16 +215,18 @@ class ExpressionMixin:
|
|
|
203
215
|
result = op(result, self._ast_eval(val, ns))
|
|
204
216
|
return result
|
|
205
217
|
if isinstance(node, ast.Compare):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
# A chained comparison (a < b < c) is ambiguous: Python reads it as
|
|
219
|
+
# (a<b) and (b<c), BASIC as the left-to-right (a<b)<c. Rather than
|
|
220
|
+
# pick one silently, require it to be written explicitly.
|
|
221
|
+
if len(node.ops) > 1:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
"AMBIGUOUS CHAINED COMPARISON: write it explicitly, "
|
|
224
|
+
"e.g. (a < b) AND (b < c)")
|
|
225
|
+
op = self._AST_OPS.get(type(node.ops[0]))
|
|
226
|
+
if op is None:
|
|
227
|
+
raise ValueError(f"UNSUPPORTED OP: {type(node.ops[0]).__name__}")
|
|
228
|
+
return op(self._ast_eval(node.left, ns),
|
|
229
|
+
self._ast_eval(node.comparators[0], ns))
|
|
216
230
|
if isinstance(node, ast.Call):
|
|
217
231
|
if not isinstance(node.func, ast.Name):
|
|
218
232
|
raise ValueError("ONLY SIMPLE FUNCTION CALLS ALLOWED")
|
|
@@ -18,7 +18,7 @@ RE_MEAS = re.compile(r'MEAS\s+(\S+)\s*->\s*(\w+)', re.IGNORECASE)
|
|
|
18
18
|
RE_RESET = re.compile(r'RESET\s+(\S+)', re.IGNORECASE)
|
|
19
19
|
RE_UNITARY = re.compile(r'UNITARY\s+(\w+)\s*=\s*(\[.+\])', re.IGNORECASE)
|
|
20
20
|
RE_DIM = re.compile(r'DIM\s+(\w+)\((\d+)\)', re.IGNORECASE)
|
|
21
|
-
RE_REDIM = re.compile(r'REDIM\s+(\w+)\((\d+)\)', re.IGNORECASE)
|
|
21
|
+
RE_REDIM = re.compile(r'REDIM\s+(PRESERVE\s+)?(\w+)\((\d+)\)', re.IGNORECASE)
|
|
22
22
|
RE_ERASE = re.compile(r'ERASE\s+(\w+)', re.IGNORECASE)
|
|
23
23
|
RE_GET = re.compile(r'GET\s+(\w+\$?)', re.IGNORECASE)
|
|
24
24
|
RE_INPUT = re.compile(r'INPUT\s+(?:"([^"]*)"\s*,\s*)?(\w+)', re.IGNORECASE)
|
|
@@ -29,8 +29,11 @@ def _chr_fn(n: float) -> str:
|
|
|
29
29
|
return chr(int(n))
|
|
30
30
|
|
|
31
31
|
def _str_fn(n: float) -> str:
|
|
32
|
+
# BASIC reserves a leading space for the sign of a non-negative number,
|
|
33
|
+
# so STR$(42) is " 42" and STR$(-5) is "-5".
|
|
32
34
|
v = float(n)
|
|
33
|
-
|
|
35
|
+
s = str(int(v)) if v == int(v) else str(v)
|
|
36
|
+
return s if v < 0 else ' ' + s
|
|
34
37
|
|
|
35
38
|
def _val_fn(s: str) -> float:
|
|
36
39
|
try:
|
|
@@ -1255,7 +1255,25 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1255
1255
|
2^n statevector that cannot be displayed anyway.
|
|
1256
1256
|
"""
|
|
1257
1257
|
if getattr(self, '_pending_set_density', None) is not None:
|
|
1258
|
-
|
|
1258
|
+
# Mixed state: no pure statevector. Capture the density matrix so
|
|
1259
|
+
# DENSITY works after a measured run, not just a no-MEASURE one.
|
|
1260
|
+
self.last_sv = None
|
|
1261
|
+
if self.num_qubits <= self._SV_EXTRACT_MAX_QUBITS:
|
|
1262
|
+
try:
|
|
1263
|
+
qc_sv.save_density_matrix()
|
|
1264
|
+
dm_backend = self._make_backend('density_matrix', include_noise=True)
|
|
1265
|
+
_kw = {}
|
|
1266
|
+
if self._seed is not None:
|
|
1267
|
+
_kw['seed_simulator'] = self._seed
|
|
1268
|
+
dm_result = dm_backend.run(
|
|
1269
|
+
transpile(qc_sv, dm_backend,
|
|
1270
|
+
optimization_level=self._transpile_opt_level),
|
|
1271
|
+
**_kw).result()
|
|
1272
|
+
data = dm_result.data(0)
|
|
1273
|
+
dm = data.get('density_matrix') if hasattr(data, 'get') else None
|
|
1274
|
+
self._last_density = np.array(dm) if dm is not None else None
|
|
1275
|
+
except Exception:
|
|
1276
|
+
self._last_density = None
|
|
1259
1277
|
return
|
|
1260
1278
|
if self.num_qubits > self._SV_EXTRACT_MAX_QUBITS:
|
|
1261
1279
|
self.last_sv = None
|
|
@@ -1943,13 +1961,17 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1943
1961
|
if not m:
|
|
1944
1962
|
return False
|
|
1945
1963
|
name = m.group(1)
|
|
1964
|
+
base = getattr(self, '_option_base', 0)
|
|
1946
1965
|
dims = [int(d.strip()) for d in m.group(2).split(',')]
|
|
1966
|
+
# Inclusive sizing (QBASIC): DIM a(n) spans indices base..n, so the
|
|
1967
|
+
# declared top index n is valid (n - base + 1 slots per dimension).
|
|
1968
|
+
sizes = [max(0, d - base + 1) for d in dims]
|
|
1947
1969
|
total = 1
|
|
1948
|
-
for
|
|
1949
|
-
total *=
|
|
1970
|
+
for s in sizes:
|
|
1971
|
+
total *= s
|
|
1950
1972
|
self.arrays[name] = [0.0] * total
|
|
1951
|
-
if len(
|
|
1952
|
-
self._array_dims[name] =
|
|
1973
|
+
if len(sizes) > 1:
|
|
1974
|
+
self._array_dims[name] = sizes
|
|
1953
1975
|
self._dimmed_arrays.add(name)
|
|
1954
1976
|
return True
|
|
1955
1977
|
|
|
@@ -1975,20 +1997,26 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1975
1997
|
return True
|
|
1976
1998
|
|
|
1977
1999
|
def _try_exec_redim(self, stmt: str) -> bool:
|
|
1978
|
-
"""Handle REDIM name(size) — resize an
|
|
2000
|
+
"""Handle REDIM [PRESERVE] name(size) — resize an array.
|
|
2001
|
+
|
|
2002
|
+
Plain REDIM clears to zeros (QBASIC semantics); REDIM PRESERVE keeps the
|
|
2003
|
+
existing elements. Sizing is inclusive (REDIM a(n) spans indices base..n).
|
|
2004
|
+
"""
|
|
1979
2005
|
m = RE_REDIM.match(stmt)
|
|
1980
2006
|
if not m:
|
|
1981
2007
|
return False
|
|
1982
|
-
|
|
1983
|
-
|
|
2008
|
+
preserve = bool(m.group(1))
|
|
2009
|
+
name = m.group(2)
|
|
2010
|
+
base = getattr(self, '_option_base', 0)
|
|
2011
|
+
new_len = max(0, int(m.group(3)) - base + 1)
|
|
1984
2012
|
old = self.arrays.get(name, [])
|
|
1985
|
-
if isinstance(old, list):
|
|
1986
|
-
if
|
|
1987
|
-
self.arrays[name] = old + [0.0] * (
|
|
2013
|
+
if preserve and isinstance(old, list):
|
|
2014
|
+
if new_len > len(old):
|
|
2015
|
+
self.arrays[name] = old + [0.0] * (new_len - len(old))
|
|
1988
2016
|
else:
|
|
1989
|
-
self.arrays[name] = old[:
|
|
2017
|
+
self.arrays[name] = old[:new_len]
|
|
1990
2018
|
else:
|
|
1991
|
-
self.arrays[name] = [0.0] *
|
|
2019
|
+
self.arrays[name] = [0.0] * new_len
|
|
1992
2020
|
self._dimmed_arrays.add(name)
|
|
1993
2021
|
return True
|
|
1994
2022
|
|
|
@@ -487,8 +487,9 @@ class TestStrings(unittest.TestCase):
|
|
|
487
487
|
self.assertEqual(_instr("HELLO", "XYZ"), 0.0)
|
|
488
488
|
self.assertEqual(_hex_fn(255), "FF")
|
|
489
489
|
self.assertEqual(_bin_fn(5), "101")
|
|
490
|
-
self.assertEqual(_str_fn(42.0), "42")
|
|
491
|
-
self.assertEqual(_str_fn(3.14), "3.14")
|
|
490
|
+
self.assertEqual(_str_fn(42.0), " 42") # leading space for non-negative
|
|
491
|
+
self.assertEqual(_str_fn(3.14), " 3.14")
|
|
492
|
+
self.assertEqual(_str_fn(-5.0), "-5")
|
|
492
493
|
self.assertEqual(_val_fn("42"), 42.0)
|
|
493
494
|
self.assertEqual(_val_fn("abc"), 0.0)
|
|
494
495
|
self.assertEqual(_len_fn("HELLO"), 5.0)
|
|
@@ -1048,7 +1049,7 @@ class TestProgramManagement(unittest.TestCase):
|
|
|
1048
1049
|
_, out = capture(t_dim.cmd_run)
|
|
1049
1050
|
self.assertIn('matrix', t_dim.arrays)
|
|
1050
1051
|
self.assertIsInstance(t_dim.arrays['matrix'], list)
|
|
1051
|
-
self.assertEqual(len(t_dim.arrays['matrix']),
|
|
1052
|
+
self.assertEqual(len(t_dim.arrays['matrix']), 16) # inclusive: (0..3)^2
|
|
1052
1053
|
|
|
1053
1054
|
# DIM single
|
|
1054
1055
|
t_dim2 = QBasicTerminal()
|
|
@@ -1056,7 +1057,7 @@ class TestProgramManagement(unittest.TestCase):
|
|
|
1056
1057
|
t_dim2.process('20 END')
|
|
1057
1058
|
capture(t_dim2.cmd_run)
|
|
1058
1059
|
self.assertIn('arr', t_dim2.arrays)
|
|
1059
|
-
self.assertEqual(len(t_dim2.arrays['arr']), 5
|
|
1060
|
+
self.assertEqual(len(t_dim2.arrays['arr']), 6) # inclusive: indices 0..5
|
|
1060
1061
|
|
|
1061
1062
|
# IMPORT namespace
|
|
1062
1063
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.qb', dir='.', delete=False) as f:
|
|
@@ -2305,20 +2306,26 @@ class TestGapCoverage(unittest.TestCase):
|
|
|
2305
2306
|
|
|
2306
2307
|
# 16 REDIM
|
|
2307
2308
|
def test_redim(self):
|
|
2308
|
-
"""REDIM resizes
|
|
2309
|
-
# DIM
|
|
2309
|
+
"""REDIM resizes (inclusive sizing); plain clears, PRESERVE keeps."""
|
|
2310
|
+
# DIM a(5) spans indices 0..5 -> 6 slots (inclusive sizing).
|
|
2310
2311
|
self.t._try_exec_dim('DIM arr(5)')
|
|
2311
|
-
self.assertEqual(len(self.t.arrays['arr']),
|
|
2312
|
-
|
|
2312
|
+
self.assertEqual(len(self.t.arrays['arr']), 6)
|
|
2313
|
+
self.t.arrays['arr'][2] = 99.0
|
|
2314
|
+
# Plain REDIM clears to zeros.
|
|
2313
2315
|
self.t._try_exec_redim('REDIM arr(8)')
|
|
2314
|
-
self.assertEqual(len(self.t.arrays['arr']),
|
|
2315
|
-
self.assertEqual(self.t.arrays['arr'][
|
|
2316
|
-
# REDIM
|
|
2317
|
-
self.t.
|
|
2318
|
-
self.
|
|
2319
|
-
|
|
2316
|
+
self.assertEqual(len(self.t.arrays['arr']), 9)
|
|
2317
|
+
self.assertEqual(self.t.arrays['arr'][2], 0.0)
|
|
2318
|
+
# REDIM PRESERVE keeps existing data.
|
|
2319
|
+
self.t.arrays['arr'][1] = 7.0
|
|
2320
|
+
self.t._try_exec_redim('REDIM PRESERVE arr(10)')
|
|
2321
|
+
self.assertEqual(len(self.t.arrays['arr']), 11)
|
|
2322
|
+
self.assertEqual(self.t.arrays['arr'][1], 7.0)
|
|
2323
|
+
# PRESERVE smaller truncates.
|
|
2324
|
+
self.t._try_exec_redim('REDIM PRESERVE arr(3)')
|
|
2325
|
+
self.assertEqual(len(self.t.arrays['arr']), 4)
|
|
2326
|
+
# REDIM on a non-existent array creates it.
|
|
2320
2327
|
self.t._try_exec_redim('REDIM newary(4)')
|
|
2321
|
-
self.assertEqual(len(self.t.arrays['newary']),
|
|
2328
|
+
self.assertEqual(len(self.t.arrays['newary']), 5)
|
|
2322
2329
|
|
|
2323
2330
|
|
|
2324
2331
|
# =====================================================================
|
|
@@ -2152,6 +2152,58 @@ class TestDeepFixRegressions(unittest.TestCase):
|
|
|
2152
2152
|
self.assertEqual(t.eval_expr('sq(5)'), 25.0)
|
|
2153
2153
|
|
|
2154
2154
|
|
|
2155
|
+
class TestConventionFixesV2(unittest.TestCase):
|
|
2156
|
+
"""round half-away, chained-comparison guard, inclusive DIM, REDIM
|
|
2157
|
+
PRESERVE, MEAS print hint, DENSITY after MEASURE."""
|
|
2158
|
+
|
|
2159
|
+
def setUp(self):
|
|
2160
|
+
self.t = QBasicTerminal(); self.t.num_qubits = 1
|
|
2161
|
+
|
|
2162
|
+
def _runp(self, lines, nq=1):
|
|
2163
|
+
t = QBasicTerminal(); t.num_qubits = nq
|
|
2164
|
+
for l in lines:
|
|
2165
|
+
t.process(l, track_undo=False)
|
|
2166
|
+
_, out = capture(t.cmd_run)
|
|
2167
|
+
t._out = out
|
|
2168
|
+
return t
|
|
2169
|
+
|
|
2170
|
+
def test_round_half_away_from_zero(self):
|
|
2171
|
+
self.assertEqual(self.t.eval_expr('round(2.5)'), 3.0)
|
|
2172
|
+
self.assertEqual(self.t.eval_expr('round(3.5)'), 4.0)
|
|
2173
|
+
self.assertEqual(self.t.eval_expr('round(-2.5)'), -3.0)
|
|
2174
|
+
self.assertAlmostEqual(self.t.eval_expr('round(2.345, 2)'), 2.35)
|
|
2175
|
+
|
|
2176
|
+
def test_chained_comparison_raises(self):
|
|
2177
|
+
self.assertTrue(bool(self.t._safe_eval('3 < 5')))
|
|
2178
|
+
with self.assertRaises(ValueError):
|
|
2179
|
+
self.t._safe_eval('1 < 2 < 3')
|
|
2180
|
+
|
|
2181
|
+
def test_dim_inclusive_top_index(self):
|
|
2182
|
+
t = self._runp(['10 DIM a(5)', '20 LET a(5)=7', '30 LET v=a(5)'])
|
|
2183
|
+
self.assertEqual(t.variables.get('v'), 7.0)
|
|
2184
|
+
t2 = self._runp(['10 DIM a(5)', '20 LET a(6)=1'])
|
|
2185
|
+
self.assertIn('OUT OF RANGE', t2._out)
|
|
2186
|
+
|
|
2187
|
+
def test_redim_clear_vs_preserve(self):
|
|
2188
|
+
t = self._runp(['10 DIM a(3)', '20 LET a(1)=9', '30 REDIM a(5)', '40 LET v=a(1)'])
|
|
2189
|
+
self.assertEqual(t.variables.get('v'), 0.0)
|
|
2190
|
+
t2 = self._runp(['10 DIM a(3)', '20 LET a(1)=9', '30 REDIM PRESERVE a(5)', '40 LET v=a(1)'])
|
|
2191
|
+
self.assertEqual(t2.variables.get('v'), 9.0)
|
|
2192
|
+
|
|
2193
|
+
def test_meas_print_hint(self):
|
|
2194
|
+
t = self._runp(['10 X 0', '20 MEAS 0 -> m', '30 PRINT m', '40 MEASURE'])
|
|
2195
|
+
self.assertIn('mid-circuit', t._out)
|
|
2196
|
+
|
|
2197
|
+
def test_density_after_measure(self):
|
|
2198
|
+
t = QBasicTerminal(); t.num_qubits = 1
|
|
2199
|
+
t.process('SET_DENSITY [[0.5,0],[0,0.5]]', track_undo=False)
|
|
2200
|
+
for l in ['10 H 0', '20 MEASURE']:
|
|
2201
|
+
t.process(l, track_undo=False)
|
|
2202
|
+
capture(t.cmd_run)
|
|
2203
|
+
_, dout = capture(t.cmd_density, '')
|
|
2204
|
+
self.assertIn('Density matrix', dout)
|
|
2205
|
+
|
|
2206
|
+
|
|
2155
2207
|
if __name__ == '__main__':
|
|
2156
2208
|
if hasattr(sys.stdout, 'reconfigure'):
|
|
2157
2209
|
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
|