qubasic 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qbasic.py +113 -0
- qbasic_core/__init__.py +80 -0
- qbasic_core/__main__.py +6 -0
- qbasic_core/analysis.py +179 -0
- qbasic_core/backend.py +76 -0
- qbasic_core/classic.py +419 -0
- qbasic_core/control_flow.py +576 -0
- qbasic_core/debug.py +327 -0
- qbasic_core/demos.py +356 -0
- qbasic_core/display.py +274 -0
- qbasic_core/engine.py +126 -0
- qbasic_core/engine_state.py +109 -0
- qbasic_core/errors.py +37 -0
- qbasic_core/exec_context.py +24 -0
- qbasic_core/executor.py +861 -0
- qbasic_core/expression.py +228 -0
- qbasic_core/file_io.py +457 -0
- qbasic_core/gates.py +284 -0
- qbasic_core/help_text.py +167 -0
- qbasic_core/io_protocol.py +33 -0
- qbasic_core/locc.py +10 -0
- qbasic_core/locc_commands.py +221 -0
- qbasic_core/locc_display.py +61 -0
- qbasic_core/locc_engine.py +195 -0
- qbasic_core/locc_execution.py +389 -0
- qbasic_core/memory.py +369 -0
- qbasic_core/mock_backend.py +66 -0
- qbasic_core/noise_mixin.py +96 -0
- qbasic_core/parser.py +564 -0
- qbasic_core/patterns.py +186 -0
- qbasic_core/profiler.py +156 -0
- qbasic_core/program_mgmt.py +369 -0
- qbasic_core/protocol.py +77 -0
- qbasic_core/py.typed +0 -0
- qbasic_core/scope.py +74 -0
- qbasic_core/screen.py +115 -0
- qbasic_core/state_display.py +60 -0
- qbasic_core/statements.py +387 -0
- qbasic_core/strings.py +107 -0
- qbasic_core/subs.py +261 -0
- qbasic_core/sweep.py +82 -0
- qbasic_core/terminal.py +1697 -0
- qubasic-0.1.0.dist-info/METADATA +736 -0
- qubasic-0.1.0.dist-info/RECORD +48 -0
- qubasic-0.1.0.dist-info/WHEEL +5 -0
- qubasic-0.1.0.dist-info/entry_points.txt +2 -0
- qubasic-0.1.0.dist-info/licenses/LICENSE +21 -0
- qubasic-0.1.0.dist-info/top_level.txt +2 -0
qbasic.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
QBASIC — Quantum BASIC Interactive Terminal
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python qbasic.py Interactive REPL
|
|
7
|
+
python qbasic.py script.qb Run a script file
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
# Force UTF-8 output on Windows
|
|
14
|
+
if sys.stdout and hasattr(sys.stdout, 'reconfigure'):
|
|
15
|
+
try:
|
|
16
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
17
|
+
except Exception:
|
|
18
|
+
pass
|
|
19
|
+
if sys.stderr and hasattr(sys.stderr, 'reconfigure'):
|
|
20
|
+
try:
|
|
21
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
22
|
+
except Exception:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
from qbasic_core.terminal import QBasicTerminal
|
|
26
|
+
from qbasic_core.program_mgmt import ProgramMgmtMixin
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run_script(path: str, terminal: 'QBasicTerminal') -> None:
|
|
30
|
+
"""Run a .qb script file. Supports multi-line DEF blocks.
|
|
31
|
+
|
|
32
|
+
After loading all lines, auto-runs the program if it contains
|
|
33
|
+
numbered lines with a MEASURE statement.
|
|
34
|
+
"""
|
|
35
|
+
with open(path, 'r') as f:
|
|
36
|
+
lines = [l.rstrip('\n\r') for l in f.readlines()]
|
|
37
|
+
ProgramMgmtMixin._load_lines_with_defs(
|
|
38
|
+
lines, lambda line: terminal.process(line, track_undo=False))
|
|
39
|
+
|
|
40
|
+
# Auto-run if the program has a MEASURE statement
|
|
41
|
+
has_measure = any(
|
|
42
|
+
terminal.program.get(ln, '').strip().upper() == 'MEASURE'
|
|
43
|
+
for ln in terminal.program
|
|
44
|
+
)
|
|
45
|
+
if terminal.program and has_measure:
|
|
46
|
+
terminal.cmd_run()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main():
|
|
50
|
+
import json as _json
|
|
51
|
+
os.environ.setdefault('PYTHONIOENCODING', 'utf-8')
|
|
52
|
+
|
|
53
|
+
args = sys.argv[1:]
|
|
54
|
+
quiet = '--quiet' in args or '-q' in args
|
|
55
|
+
json_mode = '--json' in args
|
|
56
|
+
if quiet:
|
|
57
|
+
args = [a for a in args if a not in ('--quiet', '-q')]
|
|
58
|
+
if json_mode:
|
|
59
|
+
args = [a for a in args if a != '--json']
|
|
60
|
+
|
|
61
|
+
if any(a in ('-h', '--help') for a in args):
|
|
62
|
+
from qbasic_core import __version__
|
|
63
|
+
print(f"QBASIC {__version__} — Quantum BASIC Interactive Terminal")
|
|
64
|
+
print()
|
|
65
|
+
print("Usage:")
|
|
66
|
+
print(" qbasic Interactive REPL")
|
|
67
|
+
print(" qbasic script.qb Run a script file")
|
|
68
|
+
print(" qbasic --quiet script Suppress banner and progress")
|
|
69
|
+
print(" qbasic --json script Output results as JSON")
|
|
70
|
+
print(" qbasic --help Show this help")
|
|
71
|
+
print()
|
|
72
|
+
print("Type HELP inside the REPL for full command reference.")
|
|
73
|
+
sys.exit(0)
|
|
74
|
+
|
|
75
|
+
term = QBasicTerminal()
|
|
76
|
+
|
|
77
|
+
if args:
|
|
78
|
+
path = args[0]
|
|
79
|
+
if os.path.isfile(path):
|
|
80
|
+
if quiet or json_mode:
|
|
81
|
+
import io
|
|
82
|
+
buf = io.StringIO()
|
|
83
|
+
old = sys.stdout
|
|
84
|
+
sys.stdout = buf
|
|
85
|
+
try:
|
|
86
|
+
run_script(path, term)
|
|
87
|
+
finally:
|
|
88
|
+
sys.stdout = old
|
|
89
|
+
if json_mode:
|
|
90
|
+
result = {
|
|
91
|
+
'counts': term.last_counts or {},
|
|
92
|
+
'num_qubits': term.num_qubits,
|
|
93
|
+
'shots': term.shots,
|
|
94
|
+
}
|
|
95
|
+
print(_json.dumps(result, indent=2))
|
|
96
|
+
elif not quiet:
|
|
97
|
+
print(buf.getvalue())
|
|
98
|
+
else:
|
|
99
|
+
term.print_banner()
|
|
100
|
+
run_script(path, term)
|
|
101
|
+
# Exit code: 0 if results exist, 1 if error
|
|
102
|
+
sys.exit(0 if term.last_counts is not None or not any(
|
|
103
|
+
term.program.get(ln, '').strip().upper() == 'MEASURE'
|
|
104
|
+
for ln in term.program) else 1)
|
|
105
|
+
else:
|
|
106
|
+
print(f"?FILE NOT FOUND: {path}")
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
else:
|
|
109
|
+
term.repl()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == '__main__':
|
|
113
|
+
main()
|
qbasic_core/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""QBASIC — Quantum BASIC Interactive Terminal (package)."""
|
|
2
|
+
|
|
3
|
+
# Lightweight imports that don't pull in Qiskit/numpy
|
|
4
|
+
from qbasic_core.statements import Stmt, GateStmt, RawStmt
|
|
5
|
+
from qbasic_core.errors import (
|
|
6
|
+
QBasicError, QBasicSyntaxError, QBasicRuntimeError,
|
|
7
|
+
QBasicBuildError, QBasicRangeError, QBasicIOError, QBasicUndefinedError,
|
|
8
|
+
)
|
|
9
|
+
from qbasic_core.io_protocol import IOPort, StdIOPort
|
|
10
|
+
from qbasic_core.exec_context import ExecContext
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'QBasicTerminal', 'LOCCEngine', 'ExecResult', 'ExecOutcome',
|
|
14
|
+
'ExpressionMixin', 'DisplayMixin', 'DemoMixin', 'ControlFlowMixin',
|
|
15
|
+
'LOCCMixin', 'LOCCCommandsMixin', 'LOCCDisplayMixin', 'LOCCExecutionMixin',
|
|
16
|
+
'NoiseMixin', 'StateDisplayMixin', 'HELP_TEXT', 'BANNER_ART',
|
|
17
|
+
'FileIOMixin', 'AnalysisMixin', 'SweepMixin',
|
|
18
|
+
'MemoryMixin', 'StringMixin', 'ScreenMixin', 'ClassicMixin',
|
|
19
|
+
'SubroutineMixin', 'DebugMixin', 'ProgramMgmtMixin', 'ProfilerMixin',
|
|
20
|
+
'TerminalProtocol',
|
|
21
|
+
'Engine', 'ExecutorMixin',
|
|
22
|
+
'QBasicError', 'QBasicSyntaxError', 'QBasicRuntimeError',
|
|
23
|
+
'QBasicBuildError', 'QBasicRangeError', 'QBasicIOError', 'QBasicUndefinedError',
|
|
24
|
+
'IOPort', 'StdIOPort',
|
|
25
|
+
'Stmt', 'GateStmt', 'RawStmt', 'parse_stmt',
|
|
26
|
+
'ExecContext', 'Scope',
|
|
27
|
+
'QuantumBackend', 'QiskitBackend', 'LOCCRegBackend',
|
|
28
|
+
'GATE_TABLE', 'GATE_ALIASES',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
__version__ = '0.1.0'
|
|
32
|
+
|
|
33
|
+
def __getattr__(name):
|
|
34
|
+
"""Lazy import heavy modules on first access."""
|
|
35
|
+
_lazy_imports = {
|
|
36
|
+
'GATE_TABLE': 'qbasic_core.engine',
|
|
37
|
+
'GATE_ALIASES': 'qbasic_core.engine',
|
|
38
|
+
'LOCCEngine': 'qbasic_core.engine',
|
|
39
|
+
'ExecResult': 'qbasic_core.engine',
|
|
40
|
+
'ExecOutcome': 'qbasic_core.engine',
|
|
41
|
+
'QBasicTerminal': 'qbasic_core.terminal',
|
|
42
|
+
'Engine': 'qbasic_core.engine_state',
|
|
43
|
+
'ExecutorMixin': 'qbasic_core.executor',
|
|
44
|
+
'ExpressionMixin': 'qbasic_core.expression',
|
|
45
|
+
'DisplayMixin': 'qbasic_core.display',
|
|
46
|
+
'LOCCMixin': 'qbasic_core.locc',
|
|
47
|
+
'LOCCCommandsMixin': 'qbasic_core.locc_commands',
|
|
48
|
+
'LOCCDisplayMixin': 'qbasic_core.locc_display',
|
|
49
|
+
'LOCCExecutionMixin': 'qbasic_core.locc_execution',
|
|
50
|
+
'ControlFlowMixin': 'qbasic_core.control_flow',
|
|
51
|
+
'DemoMixin': 'qbasic_core.demos',
|
|
52
|
+
'FileIOMixin': 'qbasic_core.file_io',
|
|
53
|
+
'AnalysisMixin': 'qbasic_core.analysis',
|
|
54
|
+
'SweepMixin': 'qbasic_core.sweep',
|
|
55
|
+
'MemoryMixin': 'qbasic_core.memory',
|
|
56
|
+
'StringMixin': 'qbasic_core.strings',
|
|
57
|
+
'ScreenMixin': 'qbasic_core.screen',
|
|
58
|
+
'ClassicMixin': 'qbasic_core.classic',
|
|
59
|
+
'SubroutineMixin': 'qbasic_core.subs',
|
|
60
|
+
'DebugMixin': 'qbasic_core.debug',
|
|
61
|
+
'ProgramMgmtMixin': 'qbasic_core.program_mgmt',
|
|
62
|
+
'ProfilerMixin': 'qbasic_core.profiler',
|
|
63
|
+
'NoiseMixin': 'qbasic_core.noise_mixin',
|
|
64
|
+
'StateDisplayMixin': 'qbasic_core.state_display',
|
|
65
|
+
'TerminalProtocol': 'qbasic_core.protocol',
|
|
66
|
+
'HELP_TEXT': 'qbasic_core.help_text',
|
|
67
|
+
'BANNER_ART': 'qbasic_core.help_text',
|
|
68
|
+
'QuantumBackend': 'qbasic_core.backend',
|
|
69
|
+
'QiskitBackend': 'qbasic_core.backend',
|
|
70
|
+
'LOCCRegBackend': 'qbasic_core.backend',
|
|
71
|
+
'Scope': 'qbasic_core.scope',
|
|
72
|
+
'parse_stmt': 'qbasic_core.parser',
|
|
73
|
+
}
|
|
74
|
+
if name in _lazy_imports:
|
|
75
|
+
import importlib
|
|
76
|
+
module = importlib.import_module(_lazy_imports[name])
|
|
77
|
+
val = getattr(module, name)
|
|
78
|
+
globals()[name] = val # cache for next access
|
|
79
|
+
return val
|
|
80
|
+
raise AttributeError(f"module 'qbasic_core' has no attribute {name}")
|
qbasic_core/__main__.py
ADDED
qbasic_core/analysis.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""QBASIC analysis mixin — EXPECT, ENTROPY, DENSITY, BENCH, RAM."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from qiskit import QuantumCircuit, transpile
|
|
8
|
+
from qiskit_aer import AerSimulator
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from qbasic_core.engine import (
|
|
12
|
+
_estimate_gb, _get_ram_gb,
|
|
13
|
+
OVERHEAD_FACTOR, RAM_BUDGET_FRACTION,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AnalysisMixin:
|
|
18
|
+
"""Analysis and introspection commands for QBasicTerminal.
|
|
19
|
+
|
|
20
|
+
Requires: TerminalProtocol — uses self.last_sv, self.last_counts,
|
|
21
|
+
self.num_qubits, self.shots, self.sim_method, self.sim_device,
|
|
22
|
+
self.locc, self.locc_mode.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def cmd_expect(self, rest: str) -> None:
|
|
26
|
+
"""EXPECT <pauli> [qubits] — compute expectation value.
|
|
27
|
+
Examples: EXPECT Z 0, EXPECT ZZ 0 1, EXPECT X 0"""
|
|
28
|
+
if self.last_sv is None:
|
|
29
|
+
self.io.writeln("?NO STATE — RUN first")
|
|
30
|
+
return
|
|
31
|
+
parts = rest.split()
|
|
32
|
+
if not parts:
|
|
33
|
+
self.io.writeln("?USAGE: EXPECT <Z|X|Y|ZZ|...> [qubits]")
|
|
34
|
+
return
|
|
35
|
+
pauli_str = parts[0].upper()
|
|
36
|
+
qubits = [int(q) for q in parts[1:]] if len(parts) > 1 else list(range(len(pauli_str)))
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from qiskit.quantum_info import Statevector, SparsePauliOp
|
|
40
|
+
sv = Statevector(np.ascontiguousarray(self.last_sv).ravel())
|
|
41
|
+
# Build Pauli string for full system
|
|
42
|
+
full_pauli = ['I'] * self.num_qubits
|
|
43
|
+
for i, p in enumerate(pauli_str):
|
|
44
|
+
if i < len(qubits):
|
|
45
|
+
full_pauli[self.num_qubits - 1 - qubits[i]] = p
|
|
46
|
+
op = SparsePauliOp(''.join(full_pauli))
|
|
47
|
+
val = sv.expectation_value(op)
|
|
48
|
+
self.io.writeln(f" <{pauli_str}> on qubits {qubits} = {val.real:.6f}")
|
|
49
|
+
except Exception as e:
|
|
50
|
+
self.io.writeln(f"?EXPECT ERROR: {e}")
|
|
51
|
+
|
|
52
|
+
def cmd_entropy(self, rest: str = '') -> None:
|
|
53
|
+
"""ENTROPY [qubits] — entanglement entropy of specified qubits vs rest.
|
|
54
|
+
Examples: ENTROPY 0 | ENTROPY 0 1 | ENTROPY (defaults to qubit 0)"""
|
|
55
|
+
if self.last_sv is None:
|
|
56
|
+
self.io.writeln("?NO STATE — RUN first")
|
|
57
|
+
return
|
|
58
|
+
if rest.strip():
|
|
59
|
+
partition_a = [int(q) for q in rest.replace(',', ' ').split() if q.strip()]
|
|
60
|
+
else:
|
|
61
|
+
partition_a = [0]
|
|
62
|
+
n = self.num_qubits
|
|
63
|
+
try:
|
|
64
|
+
from qiskit.quantum_info import Statevector, entropy, partial_trace
|
|
65
|
+
sv_obj = Statevector(np.ascontiguousarray(self.last_sv).ravel())
|
|
66
|
+
keep = partition_a
|
|
67
|
+
rho_a = partial_trace(sv_obj, [q for q in range(n) if q not in keep])
|
|
68
|
+
ent = entropy(rho_a, base=2)
|
|
69
|
+
self.io.writeln(f" Entanglement entropy S(A) = {ent:.6f} bits")
|
|
70
|
+
self.io.writeln(f" Partition A: qubits {partition_a}")
|
|
71
|
+
self.io.writeln(f" Partition B: qubits {[q for q in range(n) if q not in partition_a]}")
|
|
72
|
+
if ent < 0.01:
|
|
73
|
+
self.io.writeln(f" -> Qubits are separable (product state)")
|
|
74
|
+
elif abs(ent - len(partition_a)) < 0.01:
|
|
75
|
+
self.io.writeln(f" -> Maximally entangled")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
self.io.writeln(f"?ENTROPY ERROR: {e}")
|
|
78
|
+
|
|
79
|
+
def cmd_density(self) -> None:
|
|
80
|
+
"""Show density matrix (or partial trace for small systems)."""
|
|
81
|
+
if self.last_sv is None:
|
|
82
|
+
self.io.writeln("?NO STATE — RUN first")
|
|
83
|
+
return
|
|
84
|
+
sv = np.ascontiguousarray(self.last_sv).ravel()
|
|
85
|
+
rho = np.outer(sv, sv.conj())
|
|
86
|
+
n = self.num_qubits
|
|
87
|
+
dim = 2**n
|
|
88
|
+
if dim > 16:
|
|
89
|
+
self.io.writeln(f" Density matrix: {dim}x{dim} (too large to display)")
|
|
90
|
+
self.io.writeln(f" Purity: {np.real(np.trace(rho @ rho)):.6f}")
|
|
91
|
+
self.io.write(f" Von Neumann entropy: ")
|
|
92
|
+
eigvals = np.linalg.eigvalsh(rho)
|
|
93
|
+
eigvals = eigvals[eigvals > 1e-15]
|
|
94
|
+
ent = -np.sum(eigvals * np.log2(eigvals))
|
|
95
|
+
self.io.writeln(f"{ent:.6f} bits")
|
|
96
|
+
return
|
|
97
|
+
self.io.writeln(f"\n Density matrix ({dim}x{dim}):\n")
|
|
98
|
+
for i in range(dim):
|
|
99
|
+
row = []
|
|
100
|
+
for j in range(dim):
|
|
101
|
+
v = rho[i, j]
|
|
102
|
+
if abs(v.imag) < 1e-6:
|
|
103
|
+
row.append(f"{v.real:7.3f}")
|
|
104
|
+
else:
|
|
105
|
+
row.append(f"{v.real:+.2f}{v.imag:+.2f}j")
|
|
106
|
+
self.io.writeln(f" {' '.join(row)}")
|
|
107
|
+
self.io.writeln(f"\n Purity: {np.real(np.trace(rho @ rho)):.6f}")
|
|
108
|
+
self.io.writeln('')
|
|
109
|
+
|
|
110
|
+
def cmd_bench(self) -> None:
|
|
111
|
+
"""BENCH — benchmark simulation at various qubit counts."""
|
|
112
|
+
self.io.writeln("\n Benchmark (H + CX chain + measure):")
|
|
113
|
+
try:
|
|
114
|
+
import psutil
|
|
115
|
+
has_psutil = True
|
|
116
|
+
except ImportError:
|
|
117
|
+
has_psutil = False
|
|
118
|
+
header_mem = ' mem_gb' if has_psutil else ''
|
|
119
|
+
self.io.writeln(f" {'qubits':>8} {'method':>20} {'time':>8}{header_mem}")
|
|
120
|
+
for n in [4, 8, 12, 16, 20, 24, 28]:
|
|
121
|
+
qc = QuantumCircuit(n)
|
|
122
|
+
for i in range(n):
|
|
123
|
+
qc.h(i)
|
|
124
|
+
for i in range(n - 1):
|
|
125
|
+
qc.cx(i, i + 1)
|
|
126
|
+
qc.measure_all()
|
|
127
|
+
method = 'stabilizer' if n > 28 else 'automatic'
|
|
128
|
+
backend = AerSimulator(method=method)
|
|
129
|
+
t0 = time.time()
|
|
130
|
+
try:
|
|
131
|
+
result = backend.run(transpile(qc, backend), shots=100).result()
|
|
132
|
+
dt = time.time() - t0
|
|
133
|
+
mem_str = f" {psutil.virtual_memory().used / 1e9:>7.1f}" if has_psutil else ""
|
|
134
|
+
self.io.writeln(f" {n:>8} {method:>20} {dt:>7.2f}s{mem_str}")
|
|
135
|
+
except Exception as e:
|
|
136
|
+
self.io.writeln(f" {n:>8} {method:>20} FAILED: {e}")
|
|
137
|
+
self.io.writeln('')
|
|
138
|
+
|
|
139
|
+
def cmd_ram(self) -> None:
|
|
140
|
+
"""RAM — show memory budget, per-instance cost, and parallelism estimates."""
|
|
141
|
+
ram = _get_ram_gb()
|
|
142
|
+
if not ram:
|
|
143
|
+
self.io.writeln("?psutil not installed — RAM detection unavailable")
|
|
144
|
+
self.io.writeln(" Install: pip install psutil")
|
|
145
|
+
return
|
|
146
|
+
total, avail = ram
|
|
147
|
+
budget = total * RAM_BUDGET_FRACTION
|
|
148
|
+
|
|
149
|
+
self.io.writeln(f"\n System RAM: {total:.1f} GB total, {avail:.1f} GB available")
|
|
150
|
+
self.io.writeln(f" 80% budget: {budget:.1f} GB\n")
|
|
151
|
+
|
|
152
|
+
if self.locc_mode and self.locc:
|
|
153
|
+
mode = "JOINT" if self.locc.joint else "SPLIT"
|
|
154
|
+
sizes_str = '+'.join(str(s) for s in self.locc.sizes)
|
|
155
|
+
est, _ = self.locc.mem_gb()
|
|
156
|
+
max_par = int(budget / est) if est > 0 else 0
|
|
157
|
+
self.io.writeln(f" Current: LOCC {mode} {sizes_str}q")
|
|
158
|
+
self.io.writeln(f" Per instance: {est:.2f} GB")
|
|
159
|
+
if max_par > 0:
|
|
160
|
+
self.io.writeln(f" Parallel: ~{max_par} instances in budget")
|
|
161
|
+
else:
|
|
162
|
+
n = self.num_qubits
|
|
163
|
+
est = _estimate_gb(n)
|
|
164
|
+
max_par = int(budget / est) if est > 0 else 0
|
|
165
|
+
self.io.writeln(f" Current: {n} qubits")
|
|
166
|
+
self.io.writeln(f" Per instance: {est:.2f} GB")
|
|
167
|
+
if max_par > 0:
|
|
168
|
+
self.io.writeln(f" Parallel: ~{max_par} instances in budget")
|
|
169
|
+
|
|
170
|
+
self.io.writeln(f"\n Qubit scaling (per instance, with {OVERHEAD_FACTOR:.0f}x overhead):")
|
|
171
|
+
for nq in [16, 20, 24, 28, 30, 32]:
|
|
172
|
+
e = _estimate_gb(nq)
|
|
173
|
+
mp = int(budget / e) if e > 0 and budget >= e else 0
|
|
174
|
+
marker = " <--" if nq == self.num_qubits else ""
|
|
175
|
+
if mp > 0:
|
|
176
|
+
self.io.writeln(f" {nq:>2} qubits: {e:>8.2f} GB -> ~{mp:>6,} parallel{marker}")
|
|
177
|
+
else:
|
|
178
|
+
self.io.writeln(f" {nq:>2} qubits: {e:>8.2f} GB -> exceeds budget{marker}")
|
|
179
|
+
self.io.writeln('')
|
qbasic_core/backend.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""QBASIC backend abstraction — Qiskit and numpy behind a common interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Protocol
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QuantumBackend(Protocol):
|
|
11
|
+
"""Abstract quantum backend for gate application."""
|
|
12
|
+
|
|
13
|
+
def apply_gate(self, gate_name: str, params: tuple[float, ...],
|
|
14
|
+
qubits: list[int]) -> None: ...
|
|
15
|
+
def barrier(self) -> None: ...
|
|
16
|
+
def reset(self, qubit: int) -> None: ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class QiskitBackend:
|
|
20
|
+
"""Wraps a QuantumCircuit for the circuit-build path."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, qc: Any, apply_gate_fn: Any):
|
|
23
|
+
self._qc = qc
|
|
24
|
+
self._apply_gate = apply_gate_fn
|
|
25
|
+
|
|
26
|
+
def apply_gate(self, gate_name: str, params: tuple[float, ...],
|
|
27
|
+
qubits: list[int]) -> None:
|
|
28
|
+
self._apply_gate(self._qc, gate_name, list(params), qubits)
|
|
29
|
+
|
|
30
|
+
def barrier(self) -> None:
|
|
31
|
+
self._qc.barrier()
|
|
32
|
+
|
|
33
|
+
def reset(self, qubit: int) -> None:
|
|
34
|
+
self._qc.reset(qubit)
|
|
35
|
+
|
|
36
|
+
def measure(self, qubit: int, classical_bit: Any) -> None:
|
|
37
|
+
self._qc.measure(qubit, classical_bit)
|
|
38
|
+
|
|
39
|
+
def add_classical_register(self, name: str, size: int = 1) -> Any:
|
|
40
|
+
from qiskit.circuit import ClassicalRegister
|
|
41
|
+
cr = ClassicalRegister(size, name)
|
|
42
|
+
self._qc.add_register(cr)
|
|
43
|
+
return cr
|
|
44
|
+
|
|
45
|
+
def h(self, qubit: int) -> None:
|
|
46
|
+
self._qc.h(qubit)
|
|
47
|
+
|
|
48
|
+
def append_inverse(self, sub_qc: Any, qubits: list[int]) -> None:
|
|
49
|
+
self._qc.append(sub_qc.inverse(), qubits)
|
|
50
|
+
|
|
51
|
+
def append_controlled(self, gate: Any, qubits: list[int]) -> None:
|
|
52
|
+
self._qc.append(gate, qubits)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def qc(self) -> Any:
|
|
56
|
+
return self._qc
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LOCCRegBackend:
|
|
60
|
+
"""Wraps a LOCCEngine register for the numpy path."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, engine: Any, reg: str):
|
|
63
|
+
self._engine = engine
|
|
64
|
+
self._reg = reg
|
|
65
|
+
|
|
66
|
+
def apply_gate(self, gate_name: str, params: tuple[float, ...],
|
|
67
|
+
qubits: list[int]) -> None:
|
|
68
|
+
self._engine.apply(self._reg, gate_name, params, qubits)
|
|
69
|
+
|
|
70
|
+
def barrier(self) -> None:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def reset(self, qubit: int) -> None:
|
|
74
|
+
outcome = self._engine.send(self._reg, qubit)
|
|
75
|
+
if outcome == 1:
|
|
76
|
+
self._engine.apply(self._reg, 'X', (), [qubit])
|