qubasic 0.5.1__tar.gz → 0.6.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.5.1 → qubasic-0.6.1}/CHANGELOG.md +37 -0
- {qubasic-0.5.1/qubasic.egg-info → qubasic-0.6.1}/PKG-INFO +15 -17
- {qubasic-0.5.1 → qubasic-0.6.1}/README.md +14 -16
- {qubasic-0.5.1 → qubasic-0.6.1}/pyproject.toml +16 -7
- {qubasic-0.5.1 → qubasic-0.6.1/qubasic.egg-info}/PKG-INFO +15 -17
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic.egg-info/SOURCES.txt +4 -2
- qubasic-0.6.1/qubasic.egg-info/entry_points.txt +2 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic.egg-info/top_level.txt +0 -1
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/__init__.py +1 -1
- qubasic-0.6.1/qubasic_core/__main__.py +6 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/analysis.py +53 -25
- qubasic-0.5.1/qubasic.py → qubasic-0.6.1/qubasic_core/cli.py +49 -34
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/control_flow.py +89 -20
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/debug.py +1 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/demos.py +1 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/display.py +22 -7
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/engine.py +2 -1
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/engine_state.py +11 -1
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/exec_context.py +1 -1
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/executor.py +35 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/expression.py +96 -20
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/file_io.py +62 -15
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/gates.py +2 -2
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/help_text.py +3 -1
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/locc_commands.py +30 -4
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/locc_engine.py +86 -34
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/locc_execution.py +21 -10
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/memory.py +62 -10
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/noise_mixin.py +17 -5
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/parser.py +8 -4
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/patterns.py +2 -2
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/profiler.py +36 -20
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/program_mgmt.py +2 -4
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/qol.py +8 -16
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/statements.py +8 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/terminal.py +225 -54
- qubasic-0.6.1/tests/test_features.py +2654 -0
- qubasic-0.6.1/tests/test_qubasic.py +1438 -0
- qubasic-0.5.1/qubasic.egg-info/entry_points.txt +0 -2
- qubasic-0.5.1/qubasic_core/__main__.py +0 -6
- {qubasic-0.5.1 → qubasic-0.6.1}/LICENSE +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/MANIFEST.in +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/examples/bell.qb +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/examples/grover3.qb +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/examples/locc_teleport.qb +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/examples/sweep_rx.qb +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/backend.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/classic.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/errors.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/locc.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/locc_display.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/protocol.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/scope.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/screen.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/state_display.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/strings.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/subs.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/qubasic_core/sweep.py +0 -0
- {qubasic-0.5.1 → qubasic-0.6.1}/setup.cfg +0 -0
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.1 (2026-06-18)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Changing noise strength between runs (for example `NOISE amplitude_damping 0.1` then `0.9`) now rebuilds the circuit instead of reusing the first noise level, so sequential noise comparisons are correct.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- The CLI now lives in `qubasic_core/cli.py`; the `qubasic` console script and `python -m qubasic_core` are unchanged, but the former top-level `qubasic` module is removed. Test suites moved under `tests/`.
|
|
10
|
+
|
|
11
|
+
## 0.6.0 (2026-06-18)
|
|
12
|
+
|
|
13
|
+
Correctness, scalability, and usability improvements.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Time-travel debugging now works**: STEP records statevector checkpoints, so REWIND/FORWARD/HISTORY operate (previously `_checkpoint_sv` was never called).
|
|
17
|
+
- **TRON, breakpoints, ON TIMER, ON MEASURE now fire**: the debug hooks are wired into the execution loop instead of being dead code.
|
|
18
|
+
- **PLOT and ANIMATE sweep correctly**: the transpiled-circuit cache key now includes variable bindings, so parameter sweeps no longer reuse the first circuit.
|
|
19
|
+
- **`$XXXX` memory addresses parse**: PEEK/POKE/DUMP/SYS/WAIT accept the canonical hex-address notation, not only `0x`/decimal/`&H`.
|
|
20
|
+
- **Multi-line IF/ELSE/END IF**: the untaken branch is properly skipped (previously both branches ran).
|
|
21
|
+
- **ON ERROR** reports the real ERR/ERL for build-path errors and runs handler lines through the program executor (no spurious state dumps).
|
|
22
|
+
- **OPTION BASE 1** is honored in array indexing; FOR with STEP 0 is rejected; bare `NEXT` and `NEXT i, j` are supported.
|
|
23
|
+
- **CSV/EXPORT** report correctly when only a statevector (no counts) is present.
|
|
24
|
+
|
|
25
|
+
### Performance / scalability
|
|
26
|
+
- Stabilizer is chosen before MPS for large Clifford circuits, and the full 2^n statevector is no longer rebuilt after every measured run (a 30-qubit GHZ went from ~16s to <1s).
|
|
27
|
+
- STEP evolves the statevector incrementally; DENSITY and EXPECT avoid building large matrices; HEATMAP and STATS reuse work.
|
|
28
|
+
|
|
29
|
+
### Quantum / LOCC
|
|
30
|
+
- LOCC depolarizing noise matches the Qiskit Aer convention; amplitude- and phase-damping channels are now simulated in the LOCC engine.
|
|
31
|
+
- EXPECT/ENTROPY/DENSITY/BLOCH accept a register argument in SPLIT mode; `LOCC SHOTCAP` replaces the magic shot-cap variable; SEND prefix/suffix optimization detects all control transfers.
|
|
32
|
+
- Per-qubit memory-mapped noise applies to multi-qubit gates; complex single-qubit amplitudes are exposed in the qubit-state block; POKE-driven state preparation is applied at build.
|
|
33
|
+
|
|
34
|
+
### Language / interface
|
|
35
|
+
- User-defined TYPE record fields can be read and assigned (`p.x`); ELSEIF chains and re-parsed clauses are memoized.
|
|
36
|
+
- SAVE/LOAD round-trip noise, seed, screen mode, DEF FN, and TYPE definitions; CHAIN/MERGE/INCLUDE/IMPORT honor a `QUBASIC_PATH` search path; IMPORT no longer pollutes SAVE output.
|
|
37
|
+
- CLI gains `--version` and `--agent`; `--json` output is enriched (statevector, variables, manifest) and emits structured errors; the agent write-sandbox engages under `--json`/`--agent`.
|
|
38
|
+
- `SET_STATE` persists into the next RUN and warns when a named state cannot fit; per-command `HELP <name>`; dependency-version warnings are suppressed before the banner.
|
|
39
|
+
|
|
3
40
|
## 0.5.0 (2026-03-30)
|
|
4
41
|
|
|
5
42
|
- **Colorized histograms**: bars colored green/yellow/dim by probability in terminal
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,11 +73,12 @@ Requires Python >= 3.10, Qiskit >= 1.0, qiskit-aer >= 0.13.
|
|
|
73
73
|
## Usage
|
|
74
74
|
|
|
75
75
|
```
|
|
76
|
-
|
|
77
|
-
python
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
qubasic Interactive REPL (installed console script)
|
|
77
|
+
python -m qubasic_core Same, without installing
|
|
78
|
+
qubasic script.qb Run a script file
|
|
79
|
+
qubasic --quiet script Suppress banner, output results only
|
|
80
|
+
qubasic --json script Machine-readable JSON output
|
|
81
|
+
qubasic --help Show CLI help
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
Headless (no REPL):
|
|
@@ -468,15 +469,9 @@ BLOCH A 0 Bloch sphere for register A, qubit 0
|
|
|
468
469
|
LOCCINFO Protocol metrics
|
|
469
470
|
```
|
|
470
471
|
|
|
471
|
-
SPLIT: max capacity (
|
|
472
|
+
SPLIT: max capacity (up to 33 qubits per register), no cross-register entanglement. Use SEND/IF for classical coordination.
|
|
472
473
|
|
|
473
|
-
JOINT: shared entanglement via SHARE. Limited to
|
|
474
|
-
|
|
475
|
-
### Network LOCC
|
|
476
|
-
```
|
|
477
|
-
CONNECT "host:port" AS C Attach remote register (local simulation stand-in)
|
|
478
|
-
DISCONNECT C Detach
|
|
479
|
-
```
|
|
474
|
+
JOINT: shared entanglement via SHARE. Limited to 33 total qubits.
|
|
480
475
|
|
|
481
476
|
### LOCC optimization
|
|
482
477
|
Programs with SEND execute the deterministic prefix (before first SEND) once, snapshot the statevector, then re-execute only the suffix per shot. Complexity: O(prefix + shots * suffix) instead of O(shots * total).
|
|
@@ -691,7 +686,7 @@ Programs with SEND use prefix/suffix splitting: the deterministic prefix (before
|
|
|
691
686
|
## JSON output
|
|
692
687
|
|
|
693
688
|
```
|
|
694
|
-
|
|
689
|
+
qubasic --json examples/bell.qb
|
|
695
690
|
```
|
|
696
691
|
|
|
697
692
|
```json
|
|
@@ -705,8 +700,9 @@ python qubasic.py --json examples/bell.qb
|
|
|
705
700
|
## Architecture
|
|
706
701
|
|
|
707
702
|
```
|
|
708
|
-
qubasic.py CLI entry point
|
|
709
703
|
qubasic_core/
|
|
704
|
+
cli.py CLI entry point (qubasic / python -m qubasic_core)
|
|
705
|
+
__main__.py Module entry point
|
|
710
706
|
engine_state.py Engine: standalone state container
|
|
711
707
|
terminal.py QBasicTerminal: REPL + command dispatch
|
|
712
708
|
engine.py Constants, gate tables, numpy simulation, LOCCEngine
|
|
@@ -735,9 +731,11 @@ qubasic_core/
|
|
|
735
731
|
demos.py Built-in demo circuits
|
|
736
732
|
protocol.py TerminalProtocol (mixin contract)
|
|
737
733
|
mock_backend.py MockAerSimulator for fast testing
|
|
734
|
+
tests/ Test suites (test_qubasic.py, test_features.py)
|
|
735
|
+
examples/ Sample .qb programs
|
|
738
736
|
```
|
|
739
737
|
|
|
740
|
-
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` +
|
|
738
|
+
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` + 20 mixins. Execution methods live on `QBasicTerminal`, so headless/agent use should instantiate `QBasicTerminal` (the `Engine` base is a state container only).
|
|
741
739
|
|
|
742
740
|
## License
|
|
743
741
|
|
|
@@ -40,11 +40,12 @@ Requires Python >= 3.10, Qiskit >= 1.0, qiskit-aer >= 0.13.
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
42
|
```
|
|
43
|
-
|
|
44
|
-
python
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
qubasic Interactive REPL (installed console script)
|
|
44
|
+
python -m qubasic_core Same, without installing
|
|
45
|
+
qubasic script.qb Run a script file
|
|
46
|
+
qubasic --quiet script Suppress banner, output results only
|
|
47
|
+
qubasic --json script Machine-readable JSON output
|
|
48
|
+
qubasic --help Show CLI help
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
Headless (no REPL):
|
|
@@ -435,15 +436,9 @@ BLOCH A 0 Bloch sphere for register A, qubit 0
|
|
|
435
436
|
LOCCINFO Protocol metrics
|
|
436
437
|
```
|
|
437
438
|
|
|
438
|
-
SPLIT: max capacity (
|
|
439
|
+
SPLIT: max capacity (up to 33 qubits per register), no cross-register entanglement. Use SEND/IF for classical coordination.
|
|
439
440
|
|
|
440
|
-
JOINT: shared entanglement via SHARE. Limited to
|
|
441
|
-
|
|
442
|
-
### Network LOCC
|
|
443
|
-
```
|
|
444
|
-
CONNECT "host:port" AS C Attach remote register (local simulation stand-in)
|
|
445
|
-
DISCONNECT C Detach
|
|
446
|
-
```
|
|
441
|
+
JOINT: shared entanglement via SHARE. Limited to 33 total qubits.
|
|
447
442
|
|
|
448
443
|
### LOCC optimization
|
|
449
444
|
Programs with SEND execute the deterministic prefix (before first SEND) once, snapshot the statevector, then re-execute only the suffix per shot. Complexity: O(prefix + shots * suffix) instead of O(shots * total).
|
|
@@ -658,7 +653,7 @@ Programs with SEND use prefix/suffix splitting: the deterministic prefix (before
|
|
|
658
653
|
## JSON output
|
|
659
654
|
|
|
660
655
|
```
|
|
661
|
-
|
|
656
|
+
qubasic --json examples/bell.qb
|
|
662
657
|
```
|
|
663
658
|
|
|
664
659
|
```json
|
|
@@ -672,8 +667,9 @@ python qubasic.py --json examples/bell.qb
|
|
|
672
667
|
## Architecture
|
|
673
668
|
|
|
674
669
|
```
|
|
675
|
-
qubasic.py CLI entry point
|
|
676
670
|
qubasic_core/
|
|
671
|
+
cli.py CLI entry point (qubasic / python -m qubasic_core)
|
|
672
|
+
__main__.py Module entry point
|
|
677
673
|
engine_state.py Engine: standalone state container
|
|
678
674
|
terminal.py QBasicTerminal: REPL + command dispatch
|
|
679
675
|
engine.py Constants, gate tables, numpy simulation, LOCCEngine
|
|
@@ -702,9 +698,11 @@ qubasic_core/
|
|
|
702
698
|
demos.py Built-in demo circuits
|
|
703
699
|
protocol.py TerminalProtocol (mixin contract)
|
|
704
700
|
mock_backend.py MockAerSimulator for fast testing
|
|
701
|
+
tests/ Test suites (test_qubasic.py, test_features.py)
|
|
702
|
+
examples/ Sample .qb programs
|
|
705
703
|
```
|
|
706
704
|
|
|
707
|
-
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` +
|
|
705
|
+
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` + 20 mixins. Execution methods live on `QBasicTerminal`, so headless/agent use should instantiate `QBasicTerminal` (the `Engine` base is a state container only).
|
|
708
706
|
|
|
709
707
|
## License
|
|
710
708
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "qubasic"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.1"
|
|
8
8
|
description = "Quantum BASIC Interactive Terminal"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -42,10 +42,9 @@ charts = ["plotille>=5.0"]
|
|
|
42
42
|
readline = ["pyreadline3>=3.4; sys_platform == 'win32'"]
|
|
43
43
|
|
|
44
44
|
[project.scripts]
|
|
45
|
-
qubasic = "
|
|
45
|
+
qubasic = "qubasic_core.cli:main"
|
|
46
46
|
|
|
47
47
|
[tool.setuptools]
|
|
48
|
-
py-modules = ["qubasic"]
|
|
49
48
|
packages = ["qubasic_core"]
|
|
50
49
|
|
|
51
50
|
[tool.setuptools.package-data]
|
|
@@ -66,9 +65,19 @@ skip_empty = true
|
|
|
66
65
|
|
|
67
66
|
[tool.mypy]
|
|
68
67
|
python_version = "3.10"
|
|
69
|
-
|
|
68
|
+
ignore_missing_imports = true
|
|
70
69
|
warn_unused_configs = true
|
|
71
|
-
disallow_untyped_defs = true
|
|
72
|
-
check_untyped_defs = true
|
|
73
70
|
warn_redundant_casts = true
|
|
74
|
-
|
|
71
|
+
# QBasicTerminal composes 20 mixins; each mixin accesses attributes the host
|
|
72
|
+
# class provides, which mypy cannot resolve in isolation. The codebase is
|
|
73
|
+
# intentionally gradually typed, so the strict per-method settings and the
|
|
74
|
+
# cross-mixin attribute check are disabled while keeping mypy useful for real
|
|
75
|
+
# type mismatches, undefined names, and signature checks.
|
|
76
|
+
disallow_untyped_defs = false
|
|
77
|
+
check_untyped_defs = false
|
|
78
|
+
warn_return_any = false
|
|
79
|
+
warn_unused_ignores = false
|
|
80
|
+
# attr-defined: cross-mixin attributes the host class provides.
|
|
81
|
+
# has-type: types behind the lazy __getattr__ re-exports cannot be inferred.
|
|
82
|
+
# misc/union-attr: mixin base-class attribute overlap and optional access.
|
|
83
|
+
disable_error_code = ["attr-defined", "has-type", "misc", "union-attr"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,11 +73,12 @@ Requires Python >= 3.10, Qiskit >= 1.0, qiskit-aer >= 0.13.
|
|
|
73
73
|
## Usage
|
|
74
74
|
|
|
75
75
|
```
|
|
76
|
-
|
|
77
|
-
python
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
qubasic Interactive REPL (installed console script)
|
|
77
|
+
python -m qubasic_core Same, without installing
|
|
78
|
+
qubasic script.qb Run a script file
|
|
79
|
+
qubasic --quiet script Suppress banner, output results only
|
|
80
|
+
qubasic --json script Machine-readable JSON output
|
|
81
|
+
qubasic --help Show CLI help
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
Headless (no REPL):
|
|
@@ -468,15 +469,9 @@ BLOCH A 0 Bloch sphere for register A, qubit 0
|
|
|
468
469
|
LOCCINFO Protocol metrics
|
|
469
470
|
```
|
|
470
471
|
|
|
471
|
-
SPLIT: max capacity (
|
|
472
|
+
SPLIT: max capacity (up to 33 qubits per register), no cross-register entanglement. Use SEND/IF for classical coordination.
|
|
472
473
|
|
|
473
|
-
JOINT: shared entanglement via SHARE. Limited to
|
|
474
|
-
|
|
475
|
-
### Network LOCC
|
|
476
|
-
```
|
|
477
|
-
CONNECT "host:port" AS C Attach remote register (local simulation stand-in)
|
|
478
|
-
DISCONNECT C Detach
|
|
479
|
-
```
|
|
474
|
+
JOINT: shared entanglement via SHARE. Limited to 33 total qubits.
|
|
480
475
|
|
|
481
476
|
### LOCC optimization
|
|
482
477
|
Programs with SEND execute the deterministic prefix (before first SEND) once, snapshot the statevector, then re-execute only the suffix per shot. Complexity: O(prefix + shots * suffix) instead of O(shots * total).
|
|
@@ -691,7 +686,7 @@ Programs with SEND use prefix/suffix splitting: the deterministic prefix (before
|
|
|
691
686
|
## JSON output
|
|
692
687
|
|
|
693
688
|
```
|
|
694
|
-
|
|
689
|
+
qubasic --json examples/bell.qb
|
|
695
690
|
```
|
|
696
691
|
|
|
697
692
|
```json
|
|
@@ -705,8 +700,9 @@ python qubasic.py --json examples/bell.qb
|
|
|
705
700
|
## Architecture
|
|
706
701
|
|
|
707
702
|
```
|
|
708
|
-
qubasic.py CLI entry point
|
|
709
703
|
qubasic_core/
|
|
704
|
+
cli.py CLI entry point (qubasic / python -m qubasic_core)
|
|
705
|
+
__main__.py Module entry point
|
|
710
706
|
engine_state.py Engine: standalone state container
|
|
711
707
|
terminal.py QBasicTerminal: REPL + command dispatch
|
|
712
708
|
engine.py Constants, gate tables, numpy simulation, LOCCEngine
|
|
@@ -735,9 +731,11 @@ qubasic_core/
|
|
|
735
731
|
demos.py Built-in demo circuits
|
|
736
732
|
protocol.py TerminalProtocol (mixin contract)
|
|
737
733
|
mock_backend.py MockAerSimulator for fast testing
|
|
734
|
+
tests/ Test suites (test_qubasic.py, test_features.py)
|
|
735
|
+
examples/ Sample .qb programs
|
|
738
736
|
```
|
|
739
737
|
|
|
740
|
-
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` +
|
|
738
|
+
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` + 20 mixins. Execution methods live on `QBasicTerminal`, so headless/agent use should instantiate `QBasicTerminal` (the `Engine` base is a state container only).
|
|
741
739
|
|
|
742
740
|
## License
|
|
743
741
|
|
|
@@ -3,7 +3,6 @@ LICENSE
|
|
|
3
3
|
MANIFEST.in
|
|
4
4
|
README.md
|
|
5
5
|
pyproject.toml
|
|
6
|
-
qubasic.py
|
|
7
6
|
examples/bell.qb
|
|
8
7
|
examples/grover3.qb
|
|
9
8
|
examples/locc_teleport.qb
|
|
@@ -19,6 +18,7 @@ qubasic_core/__main__.py
|
|
|
19
18
|
qubasic_core/analysis.py
|
|
20
19
|
qubasic_core/backend.py
|
|
21
20
|
qubasic_core/classic.py
|
|
21
|
+
qubasic_core/cli.py
|
|
22
22
|
qubasic_core/control_flow.py
|
|
23
23
|
qubasic_core/debug.py
|
|
24
24
|
qubasic_core/demos.py
|
|
@@ -54,4 +54,6 @@ qubasic_core/statements.py
|
|
|
54
54
|
qubasic_core/strings.py
|
|
55
55
|
qubasic_core/subs.py
|
|
56
56
|
qubasic_core/sweep.py
|
|
57
|
-
qubasic_core/terminal.py
|
|
57
|
+
qubasic_core/terminal.py
|
|
58
|
+
tests/test_features.py
|
|
59
|
+
tests/test_qubasic.py
|
|
@@ -22,24 +22,48 @@ class AnalysisMixin:
|
|
|
22
22
|
self.locc, self.locc_mode.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
+
def _resolve_analysis_target(self, rest: str):
|
|
26
|
+
"""Return (sv, n_qubits, remaining_rest) for a state-analysis command.
|
|
27
|
+
|
|
28
|
+
In LOCC SPLIT mode a leading register letter selects that register
|
|
29
|
+
(e.g. ``EXPECT A Z 0``); otherwise the active statevector is used.
|
|
30
|
+
"""
|
|
31
|
+
parts = rest.split()
|
|
32
|
+
if self.locc_mode and self.locc and not self.locc.joint and parts:
|
|
33
|
+
cand = parts[0].upper()
|
|
34
|
+
if cand in self.locc.names:
|
|
35
|
+
sv, n = self._active_sv_for_reg(cand)
|
|
36
|
+
return sv, n, ' '.join(parts[1:])
|
|
37
|
+
return self._active_sv, self._active_nqubits, rest
|
|
38
|
+
|
|
25
39
|
def cmd_expect(self, rest: str) -> None:
|
|
26
|
-
"""EXPECT <pauli> [qubits] — compute expectation value.
|
|
27
|
-
Examples: EXPECT Z 0, EXPECT ZZ 0 1, EXPECT
|
|
28
|
-
sv = self.
|
|
40
|
+
"""EXPECT [reg] <pauli> [qubits] — compute expectation value.
|
|
41
|
+
Examples: EXPECT Z 0, EXPECT ZZ 0 1, EXPECT A Z 0 (SPLIT register A)"""
|
|
42
|
+
sv, n, rest = self._resolve_analysis_target(rest)
|
|
29
43
|
if sv is None:
|
|
30
44
|
if self.locc_mode and self.locc and not self.locc.joint:
|
|
31
|
-
self.io.writeln("?SPLIT mode:
|
|
45
|
+
self.io.writeln("?SPLIT mode: prefix a register, e.g. EXPECT A Z 0")
|
|
32
46
|
else:
|
|
33
47
|
self.io.writeln("?NO STATE — RUN first")
|
|
34
48
|
return
|
|
35
49
|
parts = rest.split()
|
|
36
50
|
if not parts:
|
|
37
|
-
self.io.writeln("?USAGE: EXPECT <Z|X|Y|ZZ|...> [qubits]")
|
|
51
|
+
self.io.writeln("?USAGE: EXPECT [reg] <Z|X|Y|ZZ|...> [qubits]")
|
|
38
52
|
return
|
|
39
53
|
pauli_str = parts[0].upper()
|
|
40
54
|
qubits = [int(q) for q in parts[1:]] if len(parts) > 1 else list(range(len(pauli_str)))
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
# Fast numpy path for diagonal (Z/I-only) observables — no qiskit copy.
|
|
56
|
+
if set(pauli_str) <= {'Z', 'I'}:
|
|
57
|
+
svf = np.ascontiguousarray(sv).ravel()
|
|
58
|
+
probs = np.abs(svf) ** 2
|
|
59
|
+
idx = np.arange(svf.size)
|
|
60
|
+
sign = np.ones(svf.size)
|
|
61
|
+
for i, p in enumerate(pauli_str):
|
|
62
|
+
if p == 'Z' and i < len(qubits):
|
|
63
|
+
sign = sign * np.where(((idx >> qubits[i]) & 1) == 1, -1.0, 1.0)
|
|
64
|
+
val = float(np.sum(probs * sign))
|
|
65
|
+
self.io.writeln(f" <{pauli_str}> on qubits {qubits} = {val:.6f}")
|
|
66
|
+
return
|
|
43
67
|
try:
|
|
44
68
|
from qiskit.quantum_info import Statevector, SparsePauliOp
|
|
45
69
|
sv_q = Statevector(np.ascontiguousarray(sv).ravel())
|
|
@@ -54,17 +78,19 @@ class AnalysisMixin:
|
|
|
54
78
|
self.io.writeln(f"?EXPECT ERROR: {e}")
|
|
55
79
|
|
|
56
80
|
def cmd_entropy(self, rest: str = '') -> None:
|
|
57
|
-
"""ENTROPY [qubits] — entanglement entropy of
|
|
58
|
-
Examples: ENTROPY 0
|
|
59
|
-
sv = self.
|
|
81
|
+
"""ENTROPY [reg] [qubits] — entanglement entropy of qubits vs rest.
|
|
82
|
+
Examples: ENTROPY 0 | ENTROPY 0 1 | ENTROPY A 0 (SPLIT register A)"""
|
|
83
|
+
sv, n, rest = self._resolve_analysis_target(rest)
|
|
60
84
|
if sv is None:
|
|
61
|
-
self.
|
|
85
|
+
if self.locc_mode and self.locc and not self.locc.joint:
|
|
86
|
+
self.io.writeln("?SPLIT mode: prefix a register, e.g. ENTROPY A 0")
|
|
87
|
+
else:
|
|
88
|
+
self.io.writeln("?NO STATE — RUN first")
|
|
62
89
|
return
|
|
63
90
|
if rest.strip():
|
|
64
91
|
partition_a = [int(q) for q in rest.replace(',', ' ').split() if q.strip()]
|
|
65
92
|
else:
|
|
66
93
|
partition_a = [0]
|
|
67
|
-
n = self._active_nqubits
|
|
68
94
|
try:
|
|
69
95
|
from qiskit.quantum_info import Statevector, entropy, partial_trace
|
|
70
96
|
sv_obj = Statevector(np.ascontiguousarray(sv).ravel())
|
|
@@ -81,25 +107,27 @@ class AnalysisMixin:
|
|
|
81
107
|
except Exception as e:
|
|
82
108
|
self.io.writeln(f"?ENTROPY ERROR: {e}")
|
|
83
109
|
|
|
84
|
-
def cmd_density(self) -> None:
|
|
85
|
-
"""Show density matrix (
|
|
86
|
-
sv = self.
|
|
110
|
+
def cmd_density(self, rest: str = '') -> None:
|
|
111
|
+
"""Show density matrix (DENSITY [reg]); summarizes for large systems."""
|
|
112
|
+
sv, n, rest = self._resolve_analysis_target(rest)
|
|
87
113
|
if sv is None:
|
|
88
|
-
self.
|
|
114
|
+
if self.locc_mode and self.locc and not self.locc.joint:
|
|
115
|
+
self.io.writeln("?SPLIT mode: prefix a register, e.g. DENSITY A")
|
|
116
|
+
else:
|
|
117
|
+
self.io.writeln("?NO STATE — RUN first")
|
|
89
118
|
return
|
|
90
119
|
sv = np.ascontiguousarray(sv).ravel()
|
|
91
|
-
rho = np.outer(sv, sv.conj())
|
|
92
|
-
n = self._active_nqubits
|
|
93
120
|
dim = 2**n
|
|
94
121
|
if dim > 16:
|
|
122
|
+
# |psi><psi| is rank-1, so purity is ||psi||^4 and the von Neumann
|
|
123
|
+
# entropy is 0; report directly instead of building a 2^n x 2^n matrix.
|
|
124
|
+
norm2 = float(np.vdot(sv, sv).real)
|
|
95
125
|
self.io.writeln(f" Density matrix: {dim}x{dim} (too large to display)")
|
|
96
|
-
self.io.writeln(f"
|
|
97
|
-
self.io.
|
|
98
|
-
|
|
99
|
-
eigvals = eigvals[eigvals > 1e-15]
|
|
100
|
-
ent = -np.sum(eigvals * np.log2(eigvals))
|
|
101
|
-
self.io.writeln(f"{ent:.6f} bits")
|
|
126
|
+
self.io.writeln(f" Pure state |psi><psi|: purity {norm2 ** 2:.6f}")
|
|
127
|
+
self.io.writeln(f" Von Neumann entropy: 0.000000 bits")
|
|
128
|
+
self.io.writeln(f" (use ENTROPY <qubits> for reduced-state entanglement)")
|
|
102
129
|
return
|
|
130
|
+
rho = np.outer(sv, sv.conj())
|
|
103
131
|
self.io.writeln(f"\n Density matrix ({dim}x{dim}):\n")
|
|
104
132
|
for i in range(dim):
|
|
105
133
|
row = []
|
|
@@ -154,7 +182,7 @@ class AnalysisMixin:
|
|
|
154
182
|
return
|
|
155
183
|
n = self._active_nqubits
|
|
156
184
|
sv = np.ascontiguousarray(sv).ravel()
|
|
157
|
-
checks = []
|
|
185
|
+
checks: list = []
|
|
158
186
|
|
|
159
187
|
# 1. SV normalization
|
|
160
188
|
norm = float(np.sum(np.abs(sv)**2))
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
QUBASIC —
|
|
3
|
+
QUBASIC — command-line entry point.
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
|
-
|
|
7
|
-
python
|
|
6
|
+
qubasic Interactive REPL (installed console script)
|
|
7
|
+
python -m qubasic_core Same, run as a module
|
|
8
|
+
qubasic script.qb Run a script file
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
import sys
|
|
@@ -50,14 +51,15 @@ def main():
|
|
|
50
51
|
import json as _json
|
|
51
52
|
os.environ.setdefault('PYTHONIOENCODING', 'utf-8')
|
|
52
53
|
|
|
54
|
+
from qubasic_core import __version__
|
|
55
|
+
|
|
53
56
|
args = sys.argv[1:]
|
|
54
57
|
quiet = '--quiet' in args or '-q' in args
|
|
55
58
|
json_mode = '--json' in args
|
|
59
|
+
agent_mode = '--agent' in args
|
|
56
60
|
seed_val = None
|
|
57
|
-
|
|
58
|
-
args = [a for a in args if a
|
|
59
|
-
if json_mode:
|
|
60
|
-
args = [a for a in args if a != '--json']
|
|
61
|
+
for flag in ('--quiet', '-q', '--json', '--agent'):
|
|
62
|
+
args = [a for a in args if a != flag]
|
|
61
63
|
# Parse --seed N
|
|
62
64
|
filtered = []
|
|
63
65
|
i = 0
|
|
@@ -70,8 +72,11 @@ def main():
|
|
|
70
72
|
i += 1
|
|
71
73
|
args = filtered
|
|
72
74
|
|
|
75
|
+
if any(a in ('-v', '--version') for a in args):
|
|
76
|
+
print(f"QUBASIC {__version__}")
|
|
77
|
+
sys.exit(0)
|
|
78
|
+
|
|
73
79
|
if any(a in ('-h', '--help') for a in args):
|
|
74
|
-
from qubasic_core import __version__
|
|
75
80
|
print(f"QUBASIC {__version__} — Quantum BASIC Interactive Terminal")
|
|
76
81
|
print()
|
|
77
82
|
print("Usage:")
|
|
@@ -79,13 +84,17 @@ def main():
|
|
|
79
84
|
print(" qubasic script.qb Run a script file")
|
|
80
85
|
print(" qubasic --quiet script Suppress banner and progress")
|
|
81
86
|
print(" qubasic --json script Output results as JSON")
|
|
87
|
+
print(" qubasic --agent script Confine file writes to the working dir")
|
|
82
88
|
print(" qubasic --seed N script Set random seed for reproducibility")
|
|
89
|
+
print(" qubasic --version Show version")
|
|
83
90
|
print(" qubasic --help Show this help")
|
|
84
91
|
print()
|
|
85
92
|
print("Type HELP inside the REPL for full command reference.")
|
|
86
93
|
sys.exit(0)
|
|
87
94
|
|
|
88
95
|
term = QBasicTerminal()
|
|
96
|
+
# JSON mode implies agent use, so confine file writes to the working dir.
|
|
97
|
+
term.agent_mode = agent_mode or json_mode
|
|
89
98
|
if seed_val is not None:
|
|
90
99
|
import numpy as _np
|
|
91
100
|
term._seed = seed_val
|
|
@@ -93,35 +102,41 @@ def main():
|
|
|
93
102
|
|
|
94
103
|
if args:
|
|
95
104
|
path = args[0]
|
|
96
|
-
if os.path.isfile(path):
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
buf = io.StringIO()
|
|
100
|
-
old = sys.stdout
|
|
101
|
-
sys.stdout = buf
|
|
102
|
-
try:
|
|
103
|
-
run_script(path, term)
|
|
104
|
-
finally:
|
|
105
|
-
sys.stdout = old
|
|
106
|
-
if json_mode:
|
|
107
|
-
result = {
|
|
108
|
-
'counts': term.last_counts or {},
|
|
109
|
-
'num_qubits': term.num_qubits,
|
|
110
|
-
'shots': term.shots,
|
|
111
|
-
}
|
|
112
|
-
print(_json.dumps(result, indent=2))
|
|
113
|
-
elif not quiet:
|
|
114
|
-
print(buf.getvalue())
|
|
105
|
+
if not os.path.isfile(path):
|
|
106
|
+
if json_mode:
|
|
107
|
+
print(_json.dumps({'error': f'FILE NOT FOUND: {path}'}, indent=2))
|
|
115
108
|
else:
|
|
116
|
-
|
|
109
|
+
print(f"?FILE NOT FOUND: {path}")
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
if quiet or json_mode:
|
|
112
|
+
import io
|
|
113
|
+
buf = io.StringIO()
|
|
114
|
+
old = sys.stdout
|
|
115
|
+
sys.stdout = buf
|
|
116
|
+
err = None
|
|
117
|
+
try:
|
|
117
118
|
run_script(path, term)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
except Exception as e: # surface as structured error in JSON mode
|
|
120
|
+
err = str(e)
|
|
121
|
+
finally:
|
|
122
|
+
sys.stdout = old
|
|
123
|
+
if json_mode:
|
|
124
|
+
if err is not None:
|
|
125
|
+
print(_json.dumps({'error': err}, indent=2))
|
|
126
|
+
sys.exit(1)
|
|
127
|
+
print(_json.dumps(term.result(), indent=2))
|
|
128
|
+
elif not quiet:
|
|
129
|
+
print(buf.getvalue())
|
|
130
|
+
if err is not None:
|
|
131
|
+
print(f"?ERROR: {err}")
|
|
132
|
+
sys.exit(1)
|
|
122
133
|
else:
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
term.print_banner()
|
|
135
|
+
run_script(path, term)
|
|
136
|
+
# Exit 0 on success; 1 if a measured program produced no counts.
|
|
137
|
+
sys.exit(0 if term.last_counts is not None or not any(
|
|
138
|
+
term.program.get(ln, '').strip().upper() == 'MEASURE'
|
|
139
|
+
for ln in term.program) else 1)
|
|
125
140
|
else:
|
|
126
141
|
term.repl()
|
|
127
142
|
|