qubasic 0.16.0__tar.gz → 0.18.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.16.0 → qubasic-0.18.0}/CHANGELOG.md +18 -0
- {qubasic-0.16.0/qubasic.egg-info → qubasic-0.18.0}/PKG-INFO +14 -1
- {qubasic-0.16.0 → qubasic-0.18.0}/README.md +13 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/pyproject.toml +1 -1
- {qubasic-0.16.0 → qubasic-0.18.0/qubasic.egg-info}/PKG-INFO +14 -1
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/__init__.py +1 -1
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/algorithms.py +2 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/algos2.py +4 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/classic.py +14 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/cli.py +2 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/control_flow.py +2 -1
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/display.py +28 -12
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/expression.py +4 -4
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/file_io.py +5 -1
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/parser.py +5 -1
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/patterns.py +2 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/profiler.py +1 -1
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/qec.py +6 -1
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/statements.py +4 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/sweep.py +2 -2
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/terminal.py +12 -4
- {qubasic-0.16.0 → qubasic-0.18.0}/tests/test_golden.py +50 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/LICENSE +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/MANIFEST.in +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/examples/bell.qb +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/examples/grover3.qb +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/examples/locc_teleport.qb +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/examples/sweep_rx.qb +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic.egg-info/SOURCES.txt +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic.egg-info/entry_points.txt +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic.egg-info/top_level.txt +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/__main__.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/analysis.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/backend.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/benchmarking.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/bosonic.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/debug.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/demos.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/dynamics.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/engine.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/engine_state.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/errors.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/exec_context.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/executor.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/gates.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/help_text.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/jupyter_kernel.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/locc.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/locc_commands.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/locc_display.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/locc_engine.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/locc_execution.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/logical.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/memory.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/pauliprop.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/program_mgmt.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/protocol.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/qchem.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/qec2.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/qol.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/qudits.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/resources.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/scope.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/screen.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/state_display.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/strings.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/subs.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/qubasic_core/web_repl.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/setup.cfg +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/tests/test_features.py +0 -0
- {qubasic-0.16.0 → qubasic-0.18.0}/tests/test_qubasic.py +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0 (2026-07-03)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `OPTION ENDIAN BIG|LITTLE` — bitstring display order, the BASIC-flavored sibling of `OPTION BASE`. BIG shows qubit 0 leftmost (the textbook order) across every displayed bitstring: histograms and their bit-order header, STATE, PROBS, STEP's live statevector, SWEEP lines, STATS, LOCC register and joint histograms (per-register, keeping the A|B|C order), CSV, and the JSON `counts` (whose `bit_order` field records the active convention, as does STATUS). Bitstring-shaped input follows the display, so the `AMPLIFY` target you type is the histogram line you read. Internal counts keys and statevector indexing keep the qiskit little-endian order, so programs that string-match keys are unaffected.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- `SAVE` persists `OPTION BASE` and `OPTION ENDIAN`, so a LOADed program restores its conventions.
|
|
10
|
+
|
|
11
|
+
## 0.17.0 (2026-07-02)
|
|
12
|
+
|
|
13
|
+
Hardening from an SMT-verifier triage (touchstone-prover over all 651
|
|
14
|
+
functions: 153 proved trap-free, 3 refutations traced, none reachable from
|
|
15
|
+
the language surface).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `_pmul` raises a clear ValueError on a non-Pauli character and `_emit_mcz` on an empty qubit list, instead of a raw KeyError/IndexError — both are internal preconditions (every caller feeds alphabet-constrained values), now self-documenting.
|
|
19
|
+
- `_eval_with_vars` declares the ParameterExpression passthrough in its return annotation instead of claiming `-> float`.
|
|
20
|
+
|
|
3
21
|
## 0.16.0 (2026-07-02)
|
|
4
22
|
|
|
5
23
|
Research-grade error correction, per-method scale, parametric variational
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -284,6 +284,8 @@ ERASE data Delete array
|
|
|
284
284
|
OPTION BASE 1 Set array index base
|
|
285
285
|
```
|
|
286
286
|
|
|
287
|
+
(`OPTION ENDIAN BIG|LITTLE`, the other OPTION toggle, lives under Display.)
|
|
288
|
+
|
|
287
289
|
`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.
|
|
288
290
|
|
|
289
291
|
## Control flow
|
|
@@ -396,6 +398,17 @@ DENSITY Density matrix
|
|
|
396
398
|
|
|
397
399
|
Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
|
|
398
400
|
|
|
401
|
+
```
|
|
402
|
+
OPTION ENDIAN BIG Display bitstrings big-endian (qubit 0 leftmost, the textbook order)
|
|
403
|
+
OPTION ENDIAN LITTLE Back to the default (qubit 0 rightmost, the qiskit order)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The toggle covers every displayed bitstring (histograms, STATE, PROBS, STEP,
|
|
407
|
+
SWEEP, STATS, LOCC registers, CSV, and the JSON `counts` — whose `bit_order`
|
|
408
|
+
field records the active convention) and the bitstring-shaped inputs that
|
|
409
|
+
mirror the display (`AMPLIFY`). Internal counts keys and statevector indexing
|
|
410
|
+
keep the qiskit order, so programs that string-match keys are unaffected.
|
|
411
|
+
|
|
399
412
|
### Screen modes
|
|
400
413
|
```
|
|
401
414
|
SCREEN 0 Text (default)
|
|
@@ -243,6 +243,8 @@ ERASE data Delete array
|
|
|
243
243
|
OPTION BASE 1 Set array index base
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
+
(`OPTION ENDIAN BIG|LITTLE`, the other OPTION toggle, lives under Display.)
|
|
247
|
+
|
|
246
248
|
`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.
|
|
247
249
|
|
|
248
250
|
## Control flow
|
|
@@ -355,6 +357,17 @@ DENSITY Density matrix
|
|
|
355
357
|
|
|
356
358
|
Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
|
|
357
359
|
|
|
360
|
+
```
|
|
361
|
+
OPTION ENDIAN BIG Display bitstrings big-endian (qubit 0 leftmost, the textbook order)
|
|
362
|
+
OPTION ENDIAN LITTLE Back to the default (qubit 0 rightmost, the qiskit order)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The toggle covers every displayed bitstring (histograms, STATE, PROBS, STEP,
|
|
366
|
+
SWEEP, STATS, LOCC registers, CSV, and the JSON `counts` — whose `bit_order`
|
|
367
|
+
field records the active convention) and the bitstring-shaped inputs that
|
|
368
|
+
mirror the display (`AMPLIFY`). Internal counts keys and statevector indexing
|
|
369
|
+
keep the qiskit order, so programs that string-match keys are unaffected.
|
|
370
|
+
|
|
358
371
|
### Screen modes
|
|
359
372
|
```
|
|
360
373
|
SCREEN 0 Text (default)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -284,6 +284,8 @@ ERASE data Delete array
|
|
|
284
284
|
OPTION BASE 1 Set array index base
|
|
285
285
|
```
|
|
286
286
|
|
|
287
|
+
(`OPTION ENDIAN BIG|LITTLE`, the other OPTION toggle, lives under Display.)
|
|
288
|
+
|
|
287
289
|
`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.
|
|
288
290
|
|
|
289
291
|
## Control flow
|
|
@@ -396,6 +398,17 @@ DENSITY Density matrix
|
|
|
396
398
|
|
|
397
399
|
Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
|
|
398
400
|
|
|
401
|
+
```
|
|
402
|
+
OPTION ENDIAN BIG Display bitstrings big-endian (qubit 0 leftmost, the textbook order)
|
|
403
|
+
OPTION ENDIAN LITTLE Back to the default (qubit 0 rightmost, the qiskit order)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The toggle covers every displayed bitstring (histograms, STATE, PROBS, STEP,
|
|
407
|
+
SWEEP, STATS, LOCC registers, CSV, and the JSON `counts` — whose `bit_order`
|
|
408
|
+
field records the active convention) and the bitstring-shaped inputs that
|
|
409
|
+
mirror the display (`AMPLIFY`). Internal counts keys and statevector indexing
|
|
410
|
+
keep the qiskit order, so programs that string-match keys are unaffected.
|
|
411
|
+
|
|
399
412
|
### Screen modes
|
|
400
413
|
```
|
|
401
414
|
SCREEN 0 Text (default)
|
|
@@ -119,6 +119,8 @@ class AlgorithmsMixin:
|
|
|
119
119
|
|
|
120
120
|
def _emit_mcz(self, qc, qs: list[int]) -> None:
|
|
121
121
|
"""Multi-controlled Z over qs (phase flip of the all-ones state)."""
|
|
122
|
+
if not qs:
|
|
123
|
+
raise ValueError("MCZ needs at least one qubit")
|
|
122
124
|
if len(qs) == 1:
|
|
123
125
|
qc.z(qs[0])
|
|
124
126
|
elif len(qs) == 2:
|
|
@@ -160,6 +160,10 @@ class Algorithms2Mixin:
|
|
|
160
160
|
n = self.num_qubits
|
|
161
161
|
if len(target) != n:
|
|
162
162
|
raise ValueError(f"AMPLIFY target must be {n} bits, got {len(target)}")
|
|
163
|
+
# The target reads in the active display convention, so what you see in
|
|
164
|
+
# the histogram is what you type here; internally it stays q_{n-1}..q0.
|
|
165
|
+
if getattr(self, '_endian_big', False):
|
|
166
|
+
target = target[::-1]
|
|
163
167
|
# Oracle: phase-flip |target> by X-conjugating the all-ones MCZ.
|
|
164
168
|
flip = [i for i, b in enumerate(target) if b == '0'] # bitstring is q_{n-1}..q0
|
|
165
169
|
qubits = list(range(n))
|
|
@@ -421,3 +421,17 @@ class ClassicMixin:
|
|
|
421
421
|
return None
|
|
422
422
|
self._option_base = int(m.group(1))
|
|
423
423
|
return True, ExecResult.ADVANCE
|
|
424
|
+
|
|
425
|
+
def _cf_option_endian(self, stmt: str, *, parsed=None) -> tuple[bool, ExecOutcome] | None:
|
|
426
|
+
"""OPTION ENDIAN BIG|LITTLE — bitstring display order (default LITTLE,
|
|
427
|
+
the qiskit convention: qubit 0 rightmost). Display-side only: internal
|
|
428
|
+
counts keys and statevector indexing keep the qiskit order."""
|
|
429
|
+
if parsed is not None:
|
|
430
|
+
self._endian_big = parsed.big
|
|
431
|
+
else:
|
|
432
|
+
from qubasic_core.patterns import RE_OPTION_ENDIAN
|
|
433
|
+
m = RE_OPTION_ENDIAN.match(stmt)
|
|
434
|
+
if not m:
|
|
435
|
+
return None
|
|
436
|
+
self._endian_big = m.group(1).upper() == 'BIG'
|
|
437
|
+
return True, ExecResult.ADVANCE
|
|
@@ -80,6 +80,8 @@ _SPEC_STATEMENTS = [
|
|
|
80
80
|
('FUNCTION', 'FUNCTION <name>(<args>)', 'function returning a value (END FUNCTION)'),
|
|
81
81
|
('DIM', 'DIM <name>(<size>[,...])', 'declare an array (name$ for strings; inclusive sizing)'),
|
|
82
82
|
('REDIM', 'REDIM [PRESERVE] <name>(<size>)', 'resize an array; PRESERVE keeps existing data'),
|
|
83
|
+
('OPTION', 'OPTION BASE 0|1 | OPTION ENDIAN BIG|LITTLE',
|
|
84
|
+
'array index base; bitstring display order (default LITTLE, qubit 0 rightmost)'),
|
|
83
85
|
]
|
|
84
86
|
|
|
85
87
|
|
|
@@ -17,7 +17,7 @@ from qubasic_core.statements import (
|
|
|
17
17
|
DataStmt, ReadStmt, OnGotoStmt, OnGosubStmt,
|
|
18
18
|
SelectCaseStmt, CaseStmt, EndSelectStmt, ElseStmt, EndIfStmt,
|
|
19
19
|
DoStmt, LoopStmt, ExitStmt,
|
|
20
|
-
SwapStmt, DefFnStmt, OptionBaseStmt,
|
|
20
|
+
SwapStmt, DefFnStmt, OptionBaseStmt, OptionEndianStmt,
|
|
21
21
|
SubStmt, EndSubStmt, FunctionStmt, EndFunctionStmt, CallStmt,
|
|
22
22
|
LocalStmt, StaticStmt, SharedStmt,
|
|
23
23
|
OnErrorStmt, ResumeStmt, ErrorStmt, AssertStmt, StopStmt,
|
|
@@ -463,6 +463,7 @@ class ControlFlowMixin:
|
|
|
463
463
|
SwapStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_swap(st, rv, parsed=p),
|
|
464
464
|
DefFnStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_def_fn(st, rv, parsed=p),
|
|
465
465
|
OptionBaseStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_option_base(st, parsed=p),
|
|
466
|
+
OptionEndianStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_option_endian(st, parsed=p),
|
|
466
467
|
# Handlers defined in subs.py (parsed is keyword-only)
|
|
467
468
|
SubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_sub(st, sl, ip, parsed=p),
|
|
468
469
|
EndSubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_end_sub(st, parsed=p),
|
|
@@ -40,22 +40,38 @@ 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 _bits(self, s: str) -> str:
|
|
44
|
+
"""A bitstring in the active display convention.
|
|
45
|
+
|
|
46
|
+
Little-endian (qubit 0 rightmost, the qiskit order) unless
|
|
47
|
+
OPTION ENDIAN BIG reversed it. LOCC joint keys ('0|01|1') reverse
|
|
48
|
+
per register segment, keeping the A|B|C register order.
|
|
49
|
+
"""
|
|
50
|
+
if not getattr(self, '_endian_big', False):
|
|
51
|
+
return s
|
|
52
|
+
if '|' in s:
|
|
53
|
+
return '|'.join(seg[::-1] for seg in s.split('|'))
|
|
54
|
+
return s[::-1]
|
|
55
|
+
|
|
43
56
|
def _bit_order_note(self, display: list) -> str | None:
|
|
44
57
|
"""Human-facing reminder of which bit is which qubit.
|
|
45
58
|
|
|
46
59
|
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
|
-
|
|
49
|
-
|
|
60
|
+
most common source of off-by-reverse mistakes when reading a histogram,
|
|
61
|
+
unless OPTION ENDIAN BIG flipped the display order. When the result
|
|
62
|
+
covers the whole register the positions are labelled per qubit; for a
|
|
63
|
+
measured subset only the convention is shown.
|
|
50
64
|
"""
|
|
51
65
|
if not display:
|
|
52
66
|
return None
|
|
67
|
+
big = getattr(self, '_endian_big', False)
|
|
53
68
|
nbits = len(display[0][0])
|
|
54
69
|
nq = getattr(self, 'num_qubits', nbits)
|
|
55
70
|
if nbits == nq and nbits <= 16:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
order = range(nbits) if big else range(nbits - 1, -1, -1)
|
|
72
|
+
labels = ' '.join(f'q{i}' for i in order)
|
|
73
|
+
return f" bit order {labels} (qubit 0 = {'leftmost' if big else 'rightmost'})"
|
|
74
|
+
return f" bit order qubit 0 = {'leftmost' if big else 'rightmost'} bit"
|
|
59
75
|
|
|
60
76
|
def print_histogram(self, counts: dict[str, int]) -> None:
|
|
61
77
|
"""Measurement histogram with optional rich-table formatting."""
|
|
@@ -96,7 +112,7 @@ class DisplayMixin:
|
|
|
96
112
|
bar = '\u2588' * bar_len
|
|
97
113
|
color = "green" if pct > 40 else "yellow" if pct > 10 else "dim"
|
|
98
114
|
table.add_row(
|
|
99
|
-
f"|{state}\u27E9",
|
|
115
|
+
f"|{self._bits(state)}\u27E9",
|
|
100
116
|
str(count),
|
|
101
117
|
f"{pct:5.1f}%",
|
|
102
118
|
f"[{color}]{bar}[/{color}]")
|
|
@@ -129,7 +145,7 @@ class DisplayMixin:
|
|
|
129
145
|
pct = 100 * count / total
|
|
130
146
|
bar_len = int(HISTOGRAM_BAR_WIDTH * count / max_count)
|
|
131
147
|
bar = '\u2588' * bar_len
|
|
132
|
-
ket = f"|{state}\u27E9"
|
|
148
|
+
ket = f"|{self._bits(state)}\u27E9"
|
|
133
149
|
# Colorize bar by probability
|
|
134
150
|
if _theme and sys.stdout.isatty():
|
|
135
151
|
rst = _theme.get('reset', '')
|
|
@@ -162,7 +178,7 @@ class DisplayMixin:
|
|
|
162
178
|
count = 0
|
|
163
179
|
for i, amp in enumerate(sv):
|
|
164
180
|
if abs(amp) > AMPLITUDE_THRESHOLD:
|
|
165
|
-
state = format(i, f'0{n}b')
|
|
181
|
+
state = self._bits(format(i, f'0{n}b'))
|
|
166
182
|
prob = abs(amp)**2
|
|
167
183
|
table.add_row(
|
|
168
184
|
f"|{state}\u27E9",
|
|
@@ -186,7 +202,7 @@ class DisplayMixin:
|
|
|
186
202
|
count = 0
|
|
187
203
|
for i, amp in enumerate(sv):
|
|
188
204
|
if abs(amp) > AMPLITUDE_THRESHOLD:
|
|
189
|
-
state = format(i, f'0{n}b')
|
|
205
|
+
state = self._bits(format(i, f'0{n}b'))
|
|
190
206
|
prob = abs(amp)**2
|
|
191
207
|
self.io.writeln(f" |{state}\u27E9 {amp.real:+.4f}{amp.imag:+.4f}j "
|
|
192
208
|
f"(P={prob:.4f})")
|
|
@@ -204,7 +220,7 @@ class DisplayMixin:
|
|
|
204
220
|
parts = []
|
|
205
221
|
for i, amp in enumerate(sv):
|
|
206
222
|
if abs(amp) > AMPLITUDE_THRESHOLD:
|
|
207
|
-
state = format(i, f'0{n}b')
|
|
223
|
+
state = self._bits(format(i, f'0{n}b'))
|
|
208
224
|
if abs(amp.imag) < AMPLITUDE_THRESHOLD:
|
|
209
225
|
parts.append(f"{amp.real:+.3f}|{state}\u27E9")
|
|
210
226
|
else:
|
|
@@ -221,7 +237,7 @@ class DisplayMixin:
|
|
|
221
237
|
for i, amp in enumerate(sv):
|
|
222
238
|
p = abs(amp)**2
|
|
223
239
|
if p > AMPLITUDE_THRESHOLD:
|
|
224
|
-
state = format(i, f'0{n}b')
|
|
240
|
+
state = self._bits(format(i, f'0{n}b'))
|
|
225
241
|
probs.append((state, p))
|
|
226
242
|
|
|
227
243
|
probs.sort(key=lambda x: -x[1])
|
|
@@ -420,12 +420,12 @@ class ExpressionMixin:
|
|
|
420
420
|
else self._safe_eval(expr)
|
|
421
421
|
return int(val)
|
|
422
422
|
|
|
423
|
-
def _eval_with_vars(self, expr: str, run_vars: dict[str, Any]) -> float:
|
|
423
|
+
def _eval_with_vars(self, expr: str, run_vars: dict[str, Any]) -> float | Any:
|
|
424
424
|
"""Evaluate expression with runtime variables.
|
|
425
425
|
|
|
426
|
-
|
|
427
|
-
MINIMIZE/GRADIENT/SWEEP build) passes
|
|
428
|
-
in the circuit as a bindable parameter.
|
|
426
|
+
Returns a float, except that a qiskit ParameterExpression (present
|
|
427
|
+
only during a parametric MINIMIZE/GRADIENT/SWEEP build) passes
|
|
428
|
+
through unfloated so it lands in the circuit as a bindable parameter.
|
|
429
429
|
"""
|
|
430
430
|
val = self._safe_eval(expr, extra_ns=run_vars)
|
|
431
431
|
try:
|
|
@@ -54,6 +54,10 @@ class FileIOMixin:
|
|
|
54
54
|
f.write(f"METHOD {self.sim_device}\n")
|
|
55
55
|
if getattr(self, '_screen_mode', 0):
|
|
56
56
|
f.write(f"SCREEN {self._screen_mode}\n")
|
|
57
|
+
if getattr(self, '_option_base', 0):
|
|
58
|
+
f.write(f"OPTION BASE {self._option_base}\n")
|
|
59
|
+
if getattr(self, '_endian_big', False):
|
|
60
|
+
f.write("OPTION ENDIAN BIG\n")
|
|
57
61
|
if getattr(self, '_seed', None) is not None:
|
|
58
62
|
f.write(f"SEED {self._seed}\n")
|
|
59
63
|
if getattr(self, '_noise_spec', None):
|
|
@@ -489,7 +493,7 @@ class FileIOMixin:
|
|
|
489
493
|
total = sum(self.last_counts.values())
|
|
490
494
|
lines.append("state,count,probability")
|
|
491
495
|
for state, count in sorted(self.last_counts.items(), key=lambda x: -x[1]):
|
|
492
|
-
lines.append(f"{state},{count},{count/total:.6f}")
|
|
496
|
+
lines.append(f"{self._bits(state)},{count},{count/total:.6f}")
|
|
493
497
|
if self.last_sv is not None:
|
|
494
498
|
lines.append("")
|
|
495
499
|
lines.append("state,amplitude_re,amplitude_im,probability")
|
|
@@ -33,7 +33,7 @@ from qubasic_core.statements import (
|
|
|
33
33
|
OnGotoStmt, OnGosubStmt, SelectCaseStmt, CaseStmt,
|
|
34
34
|
CallStmt, SubStmt, FunctionStmt,
|
|
35
35
|
OnErrorStmt, ResumeStmt, ErrorStmt, AssertStmt,
|
|
36
|
-
SwapStmt, DefFnStmt, OptionBaseStmt,
|
|
36
|
+
SwapStmt, DefFnStmt, OptionBaseStmt, OptionEndianStmt,
|
|
37
37
|
OnMeasureStmt, OnTimerStmt, DataStmt, ReadStmt,
|
|
38
38
|
LocalStmt, StaticStmt, SharedStmt,
|
|
39
39
|
LetStmt, LetArrayStmt, LetStrStmt, PrintStmt, PrintUsingStmt,
|
|
@@ -223,6 +223,10 @@ def _handle_option(text, raw):
|
|
|
223
223
|
m = RE_OPTION_BASE.match(text)
|
|
224
224
|
if m:
|
|
225
225
|
return OptionBaseStmt(raw=raw, base=int(m.group(1)))
|
|
226
|
+
from qubasic_core.patterns import RE_OPTION_ENDIAN
|
|
227
|
+
m = RE_OPTION_ENDIAN.match(text)
|
|
228
|
+
if m:
|
|
229
|
+
return OptionEndianStmt(raw=raw, big=m.group(1).upper() == 'BIG')
|
|
226
230
|
return None
|
|
227
231
|
|
|
228
232
|
def _handle_data(text, raw):
|
|
@@ -79,6 +79,7 @@ RE_INPUT_FILE = re.compile(r'INPUT\s+#(\d+)\s*,\s*(\w+\$?)', re.IGNORECASE)
|
|
|
79
79
|
RE_LINE_INPUT = re.compile(
|
|
80
80
|
r'LINE\s+INPUT\s+(?:"([^"]*)"\s*,\s*)?(\w+\$?)', re.IGNORECASE)
|
|
81
81
|
RE_OPTION_BASE = re.compile(r'OPTION\s+BASE\s+([01])', re.IGNORECASE)
|
|
82
|
+
RE_OPTION_ENDIAN = re.compile(r'OPTION\s+ENDIAN\s+(BIG|LITTLE)', re.IGNORECASE)
|
|
82
83
|
RE_IMPORT = re.compile(r'IMPORT\s+"?([^"]+)"?', re.IGNORECASE)
|
|
83
84
|
RE_SAVE_EXPECT = re.compile(r'SAVE_EXPECT\s+(\w+)((?:[\s,]+\d+)*)\s*->\s*(\w+)', re.IGNORECASE)
|
|
84
85
|
RE_SAVE_PROBS = re.compile(r'SAVE_PROBS\s+([\d\s,]+)\s*->\s*(\w+)', re.IGNORECASE)
|
|
@@ -169,6 +170,7 @@ __all__ = [
|
|
|
169
170
|
"RE_INPUT_FILE",
|
|
170
171
|
"RE_LINE_INPUT",
|
|
171
172
|
"RE_OPTION_BASE",
|
|
173
|
+
"RE_OPTION_ENDIAN",
|
|
172
174
|
"RE_IMPORT",
|
|
173
175
|
"RE_SAVE_EXPECT",
|
|
174
176
|
"RE_SAVE_PROBS",
|
|
@@ -186,7 +186,7 @@ class ProfilerMixin:
|
|
|
186
186
|
variance = sum((v - mean) ** 2 for v in vals) / len(vals)
|
|
187
187
|
std = math.sqrt(variance)
|
|
188
188
|
prob = mean / avg_shots if avg_shots > 0 else 0
|
|
189
|
-
self.io.writeln(f" |{state}\u27E9 {mean:>8.1f} {std:>8.2f} {min(vals):>6} {max(vals):>6} {prob:>7.4f}")
|
|
189
|
+
self.io.writeln(f" |{self._bits(state)}\u27E9 {mean:>8.1f} {std:>8.2f} {min(vals):>6} {max(vals):>6} {prob:>7.4f}")
|
|
190
190
|
self.io.writeln('')
|
|
191
191
|
|
|
192
192
|
def _stats_export_csv(self, path: str) -> None:
|
|
@@ -38,7 +38,12 @@ def _anticommute(a: str, b: str) -> int:
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def _pmul(a: str, b: str) -> str:
|
|
41
|
-
|
|
41
|
+
# Precondition: both strings over the IXYZ alphabet. The try keeps the
|
|
42
|
+
# per-trial hot path free of per-character validation.
|
|
43
|
+
try:
|
|
44
|
+
return ''.join(_PMUL[(pa, pb)] for pa, pb in zip(a, b))
|
|
45
|
+
except KeyError as e:
|
|
46
|
+
raise ValueError(f"Pauli strings must be over IXYZ (got {e.args[0]})") from None
|
|
42
47
|
|
|
43
48
|
|
|
44
49
|
def _weight(p: str) -> int:
|
|
@@ -175,6 +175,10 @@ class DefFnStmt(Stmt):
|
|
|
175
175
|
class OptionBaseStmt(Stmt):
|
|
176
176
|
base: int
|
|
177
177
|
|
|
178
|
+
@dataclass(frozen=True, slots=True)
|
|
179
|
+
class OptionEndianStmt(Stmt):
|
|
180
|
+
big: bool
|
|
181
|
+
|
|
178
182
|
@dataclass(frozen=True, slots=True)
|
|
179
183
|
class OnMeasureStmt(Stmt):
|
|
180
184
|
target: int
|
|
@@ -95,10 +95,10 @@ class SweepMixin:
|
|
|
95
95
|
ranked = sorted(counts.items(), key=lambda x: -x[1])
|
|
96
96
|
top = ranked[0]
|
|
97
97
|
bar_len = int(30 * top[1] / self.shots)
|
|
98
|
-
top2 = f" |{ranked[1][0]}\u27E9={ranked[1][1]}" if len(ranked) > 1 else ""
|
|
98
|
+
top2 = f" |{self._bits(ranked[1][0])}\u27E9={ranked[1][1]}" if len(ranked) > 1 else ""
|
|
99
99
|
n_unique = len(ranked)
|
|
100
100
|
bar = '\u2588' * bar_len
|
|
101
|
-
self.io.writeln(f" {var}={val:8.4f} |{top[0]}\u27E9 {top[1]:>5}/{self.shots} "
|
|
101
|
+
self.io.writeln(f" {var}={val:8.4f} |{self._bits(top[0])}\u27E9 {top[1]:>5}/{self.shots} "
|
|
102
102
|
f"{bar}{top2} ({n_unique} states)")
|
|
103
103
|
sweep_xs.append(val)
|
|
104
104
|
sweep_ys.append(top[1] / self.shots)
|
|
@@ -1165,7 +1165,9 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1165
1165
|
'method': self.sim_method,
|
|
1166
1166
|
'device': self.sim_device,
|
|
1167
1167
|
'seed': getattr(self, '_seed', None),
|
|
1168
|
-
'bit_order': '
|
|
1168
|
+
'bit_order': ('big-endian (qubit 0 = leftmost bit)'
|
|
1169
|
+
if getattr(self, '_endian_big', False)
|
|
1170
|
+
else 'little-endian (qubit 0 = rightmost bit)'),
|
|
1169
1171
|
'option_base': getattr(self, '_option_base', 0),
|
|
1170
1172
|
'locc_mode': bool(getattr(self, 'locc_mode', False)),
|
|
1171
1173
|
'locc_registers': (list(self.locc.names)
|
|
@@ -1677,12 +1679,18 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
1677
1679
|
JSON-serializable: counts, qubit/shot config, user variables, the
|
|
1678
1680
|
statevector (when small enough), and key run-manifest fields.
|
|
1679
1681
|
"""
|
|
1682
|
+
_big = getattr(self, '_endian_big', False)
|
|
1683
|
+
_counts = self.last_counts or {}
|
|
1684
|
+
if _big and _counts:
|
|
1685
|
+
_counts = {k[::-1]: v for k, v in _counts.items()}
|
|
1680
1686
|
out: dict = {
|
|
1681
|
-
'counts':
|
|
1687
|
+
'counts': _counts,
|
|
1682
1688
|
'num_qubits': self.num_qubits,
|
|
1689
|
+
# Bitstring keys follow the active OPTION ENDIAN convention; the
|
|
1690
|
+
# bit_order field is the self-describing record of which one.
|
|
1683
1691
|
'shots': self.shots,
|
|
1684
|
-
|
|
1685
|
-
|
|
1692
|
+
'bit_order': ('big-endian (qubit 0 = leftmost bit)' if _big
|
|
1693
|
+
else 'little-endian (qubit 0 = rightmost bit)'),
|
|
1686
1694
|
}
|
|
1687
1695
|
uvars = {k: v for k, v in self.variables.items()
|
|
1688
1696
|
if not k.startswith('_') and isinstance(v, (int, float, str, bool))}
|
|
@@ -204,6 +204,56 @@ class TestAuditRegressions(unittest.TestCase):
|
|
|
204
204
|
_, out = run_script(['METHOD statevector', 'QUBITS 100'])
|
|
205
205
|
self.assertIn('RANGE', out)
|
|
206
206
|
|
|
207
|
+
def test_option_endian_big_display(self):
|
|
208
|
+
t, out = run_script([
|
|
209
|
+
'QUBITS 3',
|
|
210
|
+
'SHOTS 128',
|
|
211
|
+
'OPTION ENDIAN BIG',
|
|
212
|
+
'10 X 0',
|
|
213
|
+
'20 MEASURE',
|
|
214
|
+
'RUN',
|
|
215
|
+
'STATE',
|
|
216
|
+
])
|
|
217
|
+
self.assertIn('qubit 0 = leftmost', out)
|
|
218
|
+
self.assertIn('|100', out) # displayed big-endian
|
|
219
|
+
self.assertEqual(dict(t.last_counts), {'001': 128}) # internal keys unchanged
|
|
220
|
+
self.assertEqual(t.result()['counts'], {'100': 128}) # JSON follows the toggle
|
|
221
|
+
self.assertIn('big-endian', t.result()['bit_order'])
|
|
222
|
+
|
|
223
|
+
def test_option_endian_amplify_matches_display(self):
|
|
224
|
+
# Under BIG, the AMPLIFY target reads as displayed: '100' marks the
|
|
225
|
+
# state whose histogram line says |100> (internally |001>).
|
|
226
|
+
t, _ = run_script([
|
|
227
|
+
'QUBITS 3',
|
|
228
|
+
'SHOTS 512',
|
|
229
|
+
'OPTION ENDIAN BIG',
|
|
230
|
+
'10 H 0 : H 1 : H 2',
|
|
231
|
+
'20 AMPLIFY 100',
|
|
232
|
+
'30 AMPLIFY 100',
|
|
233
|
+
'40 MEASURE',
|
|
234
|
+
'RUN',
|
|
235
|
+
])
|
|
236
|
+
top = max(t.last_counts, key=t.last_counts.get)
|
|
237
|
+
self.assertEqual(top, '001')
|
|
238
|
+
|
|
239
|
+
def test_option_endian_save_roundtrip(self):
|
|
240
|
+
import os
|
|
241
|
+
import tempfile
|
|
242
|
+
with tempfile.TemporaryDirectory() as td:
|
|
243
|
+
old_cwd = os.getcwd()
|
|
244
|
+
os.chdir(td)
|
|
245
|
+
try:
|
|
246
|
+
run_script([
|
|
247
|
+
'OPTION ENDIAN BIG',
|
|
248
|
+
'10 H 0',
|
|
249
|
+
'20 MEASURE',
|
|
250
|
+
'SAVE endian.qb',
|
|
251
|
+
])
|
|
252
|
+
t2, _ = run_script(['LOAD endian.qb'])
|
|
253
|
+
self.assertTrue(getattr(t2, '_endian_big', False))
|
|
254
|
+
finally:
|
|
255
|
+
os.chdir(old_cwd)
|
|
256
|
+
|
|
207
257
|
|
|
208
258
|
class TestResearchQEC(unittest.TestCase):
|
|
209
259
|
def test_bb_code_parameters(self):
|
|
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
|