qubasic 0.4.0__tar.gz → 0.5.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.4.0 → qubasic-0.5.0}/CHANGELOG.md +44 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/LICENSE +1 -1
- {qubasic-0.4.0/qubasic.egg-info → qubasic-0.5.0}/PKG-INFO +1 -1
- {qubasic-0.4.0 → qubasic-0.5.0}/pyproject.toml +1 -1
- {qubasic-0.4.0 → qubasic-0.5.0/qubasic.egg-info}/PKG-INFO +1 -1
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic.egg-info/SOURCES.txt +1 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic.py +17 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/__init__.py +1 -1
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/classic.py +3 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/control_flow.py +5 -3
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/demos.py +1 -1
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/display.py +10 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/expression.py +9 -25
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/locc_commands.py +9 -5
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/locc_display.py +3 -3
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/locc_execution.py +8 -6
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/parser.py +2 -2
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/profiler.py +9 -1
- qubasic-0.5.0/qubasic_core/qol.py +671 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/scope.py +0 -1
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/subs.py +25 -1
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/terminal.py +71 -24
- {qubasic-0.4.0 → qubasic-0.5.0}/MANIFEST.in +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/README.md +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/examples/bell.qb +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/examples/grover3.qb +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/examples/locc_teleport.qb +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/examples/sweep_rx.qb +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic.egg-info/entry_points.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic.egg-info/top_level.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/__main__.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/analysis.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/backend.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/debug.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/engine.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/engine_state.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/errors.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/exec_context.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/executor.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/file_io.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/gates.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/help_text.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/locc.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/locc_engine.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/memory.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/patterns.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/program_mgmt.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/protocol.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/screen.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/state_display.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/statements.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/strings.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/qubasic_core/sweep.py +0 -0
- {qubasic-0.4.0 → qubasic-0.5.0}/setup.cfg +0 -0
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 (2026-03-30)
|
|
4
|
+
|
|
5
|
+
- **Colorized histograms**: bars colored green/yellow/dim by probability in terminal
|
|
6
|
+
- **Animated STEP mode**: press A during STEP for auto-advance with configurable delay
|
|
7
|
+
- **"Did you mean?"**: typo suggestions for misspelled commands (BLASCH -> BLOCH)
|
|
8
|
+
- **Status bar prompt**: REPL prompt shows qubit count, LOCC, noise status
|
|
9
|
+
- **DRAW command**: braille-character Bloch sphere rendering
|
|
10
|
+
- **Color-coded LIST**: gates in cyan, flow in yellow, comments dim (THEME controls)
|
|
11
|
+
- **Gate throughput**: RUN summary shows gates/s and circuit complexity (T-count, CNOT count)
|
|
12
|
+
- **COMPARE command**: run circuit with two methods and diff output distributions
|
|
13
|
+
- **HEATMAP command**: qubit-qubit entanglement entropy grid
|
|
14
|
+
- **Startup tip of the day**: random tip shown at REPL launch
|
|
15
|
+
- **ANIMATE command**: animated parameter sweep with in-place terminal updates
|
|
16
|
+
- **QUIZ mode**: interactive quantum computing quiz with multiple choice
|
|
17
|
+
- **Tab completion for file paths**: SAVE/LOAD/INCLUDE complete .qb filenames
|
|
18
|
+
- **DIFF command**: diff current program against another BANK slot
|
|
19
|
+
- **PLOT command**: ASCII scatter plot of P(top state) vs swept variable
|
|
20
|
+
- **UNDO preview**: shows what lines will change before applying
|
|
21
|
+
- **THEME command**: switch color schemes (default, retro, none)
|
|
22
|
+
- **F1/F2/F3 demo shortcuts**: load Bell, GHZ, Grover demos via function keys
|
|
23
|
+
- **Quantum spinner**: |0>, |+>, |1>, |-> cycling during STATS and LOCC progress
|
|
24
|
+
- **EXPLAIN command**: describe each program line in plain English
|
|
25
|
+
- **LOCC progress spinner**: quantum-themed progress during SEND-based LOCC runs
|
|
26
|
+
- **CLIP command**: copy last results to system clipboard
|
|
27
|
+
- **Sound on completion**: terminal bell for runs exceeding 2 seconds
|
|
28
|
+
- New QoLMixin with 24 quality-of-life features
|
|
29
|
+
|
|
30
|
+
## 0.4.1 (2026-03-30)
|
|
31
|
+
|
|
32
|
+
- **Fix SEED dispatch**: moved from no-arg to with-arg dispatch table so `SEED 42` works from the REPL
|
|
33
|
+
- **Fix GPU probe**: `cmd_method` GPU probe used undefined `_pqc` variable; now uses `_pqc_m`
|
|
34
|
+
- **Fix LOCC non-numeric args**: `LOCC 4 banana` no longer crashes with unguarded ValueError
|
|
35
|
+
- **Coverage threshold**: raised CI coverage floor from 60% to 75%
|
|
36
|
+
- **Property-based tests**: 4 new hypothesis tests (arithmetic identity, parser fuzzing, process fuzzing, FOR loop count)
|
|
37
|
+
- **CLI integration tests**: 8 new tests covering dispatch, SEED, LOCC error handling, METHOD probe, --seed flag, --help
|
|
38
|
+
- **Parser imports**: import regexes from patterns.py directly instead of double-indirection through engine.py
|
|
39
|
+
- **Expression simplification**: `_replace_dollar_outside_strings` reduced from two passes to one
|
|
40
|
+
- **Jump table**: pre-compute WHILE/WEND and DO/LOOP ip mappings in `_scan_subs`; `_find_matching_wend` and `_find_matching_loop` use O(1) lookup when available
|
|
41
|
+
- **Scope cleanup**: removed dual-write hack in `Scope.__setitem__`; writes go to `_runtime` only, `_persistent` is read-through fallback
|
|
42
|
+
- **STATS output**: redirect stdout during stats runs to suppress rich console output in non-TTY mode
|
|
43
|
+
- **Copyright year**: LICENSE updated from 2025 to 2026
|
|
44
|
+
- **CLI --seed flag**: `qubasic --seed N script.qb` sets deterministic seed before execution
|
|
45
|
+
- **Type annotations**: added return type annotations to 14 unannotated mixin methods across locc_commands, locc_display, locc_execution, demos
|
|
46
|
+
|
|
3
47
|
## 0.4.0 (2026-03-29)
|
|
4
48
|
|
|
5
49
|
- **Noise correctness**: transpile with optimization_level=0 when noisy so gates survive for noise attachment
|
|
@@ -53,10 +53,22 @@ def main():
|
|
|
53
53
|
args = sys.argv[1:]
|
|
54
54
|
quiet = '--quiet' in args or '-q' in args
|
|
55
55
|
json_mode = '--json' in args
|
|
56
|
+
seed_val = None
|
|
56
57
|
if quiet:
|
|
57
58
|
args = [a for a in args if a not in ('--quiet', '-q')]
|
|
58
59
|
if json_mode:
|
|
59
60
|
args = [a for a in args if a != '--json']
|
|
61
|
+
# Parse --seed N
|
|
62
|
+
filtered = []
|
|
63
|
+
i = 0
|
|
64
|
+
while i < len(args):
|
|
65
|
+
if args[i] == '--seed' and i + 1 < len(args):
|
|
66
|
+
seed_val = int(args[i + 1])
|
|
67
|
+
i += 2
|
|
68
|
+
else:
|
|
69
|
+
filtered.append(args[i])
|
|
70
|
+
i += 1
|
|
71
|
+
args = filtered
|
|
60
72
|
|
|
61
73
|
if any(a in ('-h', '--help') for a in args):
|
|
62
74
|
from qubasic_core import __version__
|
|
@@ -67,12 +79,17 @@ def main():
|
|
|
67
79
|
print(" qubasic script.qb Run a script file")
|
|
68
80
|
print(" qubasic --quiet script Suppress banner and progress")
|
|
69
81
|
print(" qubasic --json script Output results as JSON")
|
|
82
|
+
print(" qubasic --seed N script Set random seed for reproducibility")
|
|
70
83
|
print(" qubasic --help Show this help")
|
|
71
84
|
print()
|
|
72
85
|
print("Type HELP inside the REPL for full command reference.")
|
|
73
86
|
sys.exit(0)
|
|
74
87
|
|
|
75
88
|
term = QBasicTerminal()
|
|
89
|
+
if seed_val is not None:
|
|
90
|
+
import numpy as _np
|
|
91
|
+
term._seed = seed_val
|
|
92
|
+
_np.random.seed(seed_val)
|
|
76
93
|
|
|
77
94
|
if args:
|
|
78
95
|
path = args[0]
|
|
@@ -217,6 +217,9 @@ class ClassicMixin:
|
|
|
217
217
|
# ── DO / LOOP ─────────────────────────────────────────────────────
|
|
218
218
|
|
|
219
219
|
def _find_matching_loop(self, sorted_lines: list[int], ip: int) -> int:
|
|
220
|
+
jt = getattr(self, '_jump_table', None)
|
|
221
|
+
if jt and ip in jt:
|
|
222
|
+
return jt[ip]
|
|
220
223
|
depth = 1
|
|
221
224
|
scan = ip + 1
|
|
222
225
|
while scan < len(sorted_lines):
|
|
@@ -166,10 +166,12 @@ class ControlFlowMixin:
|
|
|
166
166
|
def _find_matching_wend(self, sorted_lines: list[int], ip: int) -> int:
|
|
167
167
|
"""Find the ip after the WEND matching the WHILE at ip.
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
for clear diagnostics.
|
|
169
|
+
Uses pre-computed jump table when available (O(1)), falls back
|
|
170
|
+
to linear scan with nesting depth tracking.
|
|
172
171
|
"""
|
|
172
|
+
jt = getattr(self, '_jump_table', None)
|
|
173
|
+
if jt and ip in jt:
|
|
174
|
+
return jt[ip] + 1
|
|
173
175
|
depth = 1
|
|
174
176
|
scan = ip + 1
|
|
175
177
|
while scan < len(sorted_lines):
|
|
@@ -88,11 +88,21 @@ class DisplayMixin:
|
|
|
88
88
|
max_count = max(c for _, c in display) if display else 1
|
|
89
89
|
max_label = max(len(k) for k, _ in display) if display else 1
|
|
90
90
|
|
|
91
|
+
_theme = getattr(self, '_theme', {})
|
|
91
92
|
for state, count in display:
|
|
92
93
|
pct = 100 * count / total
|
|
93
94
|
bar_len = int(HISTOGRAM_BAR_WIDTH * count / max_count)
|
|
94
95
|
bar = '\u2588' * bar_len
|
|
95
96
|
ket = f"|{state}\u27E9"
|
|
97
|
+
# Colorize bar by probability
|
|
98
|
+
if _theme and sys.stdout.isatty():
|
|
99
|
+
rst = _theme.get('reset', '')
|
|
100
|
+
if pct > 40:
|
|
101
|
+
bar = f"{_theme.get('bar_hi', '')}{bar}{rst}"
|
|
102
|
+
elif pct > 10:
|
|
103
|
+
bar = f"{_theme.get('bar_mid', '')}{bar}{rst}"
|
|
104
|
+
else:
|
|
105
|
+
bar = f"{_theme.get('bar_lo', '')}{bar}{rst}"
|
|
96
106
|
self.io.writeln(f" {ket:>{max_label+3}} {count:>6} ({pct:5.1f}%) {bar}")
|
|
97
107
|
|
|
98
108
|
if len(sorted_counts) > MAX_HISTOGRAM_STATES:
|
|
@@ -12,44 +12,28 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def _replace_dollar_outside_strings(expr_str: str) -> str:
|
|
15
|
-
"""Apply $->_S_ replacement only outside quoted string literals.
|
|
16
|
-
|
|
15
|
+
"""Apply $->_S_ replacement only outside quoted string literals.
|
|
16
|
+
|
|
17
|
+
Single pass: splits on quote boundaries, substitutes only in
|
|
18
|
+
unquoted segments.
|
|
19
|
+
"""
|
|
20
|
+
parts: list[str] = []
|
|
17
21
|
i = 0
|
|
18
22
|
n = len(expr_str)
|
|
19
23
|
while i < n:
|
|
20
24
|
ch = expr_str[i]
|
|
21
25
|
if ch in ('"', "'"):
|
|
22
|
-
# Copy the entire quoted region verbatim
|
|
23
26
|
quote = ch
|
|
24
27
|
j = i + 1
|
|
25
28
|
while j < n and expr_str[j] != quote:
|
|
26
29
|
j += 1
|
|
27
|
-
|
|
28
|
-
result.append(expr_str[i:j + 1])
|
|
29
|
-
i = j + 1
|
|
30
|
-
else:
|
|
31
|
-
result.append(ch)
|
|
32
|
-
i += 1
|
|
33
|
-
text = ''.join(result)
|
|
34
|
-
# Now apply the substitution only to non-quoted segments
|
|
35
|
-
parts: list[str] = []
|
|
36
|
-
i = 0
|
|
37
|
-
n = len(text)
|
|
38
|
-
while i < n:
|
|
39
|
-
ch = text[i]
|
|
40
|
-
if ch in ('"', "'"):
|
|
41
|
-
quote = ch
|
|
42
|
-
j = i + 1
|
|
43
|
-
while j < n and text[j] != quote:
|
|
44
|
-
j += 1
|
|
45
|
-
parts.append(text[i:j + 1])
|
|
30
|
+
parts.append(expr_str[i:j + 1])
|
|
46
31
|
i = j + 1
|
|
47
32
|
else:
|
|
48
|
-
# Accumulate non-quoted text
|
|
49
33
|
j = i
|
|
50
|
-
while j < n and
|
|
34
|
+
while j < n and expr_str[j] not in ('"', "'"):
|
|
51
35
|
j += 1
|
|
52
|
-
segment =
|
|
36
|
+
segment = expr_str[i:j]
|
|
53
37
|
parts.append(re.sub(r'(\w+)\$', r'\1_S_', segment))
|
|
54
38
|
i = j
|
|
55
39
|
return ''.join(parts)
|
|
@@ -24,7 +24,7 @@ class LOCCCommandsMixin:
|
|
|
24
24
|
"""
|
|
25
25
|
return getattr(self, '_noise_depol_p', 0.0)
|
|
26
26
|
|
|
27
|
-
def cmd_locc(self, rest):
|
|
27
|
+
def cmd_locc(self, rest: str) -> None:
|
|
28
28
|
args = rest.upper().split()
|
|
29
29
|
if not args:
|
|
30
30
|
if self.locc_mode:
|
|
@@ -86,7 +86,11 @@ class LOCCCommandsMixin:
|
|
|
86
86
|
self.io.writeln(f"?MAX {LOCC_MAX_REGISTERS} registers (A-Z)")
|
|
87
87
|
return
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
try:
|
|
90
|
+
sizes = [int(n) for n in nums]
|
|
91
|
+
except ValueError:
|
|
92
|
+
self.io.writeln("?LOCC register sizes must be integers")
|
|
93
|
+
return
|
|
90
94
|
total = sum(sizes)
|
|
91
95
|
if joint and total > LOCC_MAX_JOINT_QUBITS:
|
|
92
96
|
self.io.writeln(f"?JOINT mode limited to {LOCC_MAX_JOINT_QUBITS} total qubits (requested {total})")
|
|
@@ -130,7 +134,7 @@ class LOCCCommandsMixin:
|
|
|
130
134
|
self.io.writeln(f" WARNING: non-depolarizing noise model active but not supported "
|
|
131
135
|
f"in LOCC numpy path. Only NOISE depolarizing propagates to LOCC.")
|
|
132
136
|
|
|
133
|
-
def cmd_send(self, rest):
|
|
137
|
+
def cmd_send(self, rest: str) -> None:
|
|
134
138
|
if not self.locc_mode:
|
|
135
139
|
self.io.writeln("?SEND requires LOCC mode")
|
|
136
140
|
return
|
|
@@ -153,7 +157,7 @@ class LOCCCommandsMixin:
|
|
|
153
157
|
self.locc.classical[var] = outcome
|
|
154
158
|
self.io.writeln(f" {reg}[{qubit}] -> {var} = {outcome}")
|
|
155
159
|
|
|
156
|
-
def cmd_share(self, rest):
|
|
160
|
+
def cmd_share(self, rest: str) -> None:
|
|
157
161
|
if not self.locc_mode:
|
|
158
162
|
self.io.writeln("?SHARE requires LOCC mode")
|
|
159
163
|
return
|
|
@@ -173,7 +177,7 @@ class LOCCCommandsMixin:
|
|
|
173
177
|
except RuntimeError as e:
|
|
174
178
|
self.io.writeln(f"?{e}")
|
|
175
179
|
|
|
176
|
-
def cmd_loccinfo(self):
|
|
180
|
+
def cmd_loccinfo(self) -> None:
|
|
177
181
|
"""Show LOCC protocol metrics after a run."""
|
|
178
182
|
if not self.locc_mode:
|
|
179
183
|
self.io.writeln("?NOT IN LOCC MODE")
|
|
@@ -10,7 +10,7 @@ class LOCCDisplayMixin:
|
|
|
10
10
|
self._print_bloch_single(), self.print_histogram(), self.io.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
def _locc_state(self, rest=''):
|
|
13
|
+
def _locc_state(self, rest: str = '') -> None:
|
|
14
14
|
reg = rest.strip().upper() if rest else ''
|
|
15
15
|
if self.locc.joint:
|
|
16
16
|
if not reg or reg in self.locc.names:
|
|
@@ -24,7 +24,7 @@ class LOCCDisplayMixin:
|
|
|
24
24
|
self.io.writeln(f"\n Register {name} ({size} qubits):")
|
|
25
25
|
self._print_statevector(self.locc.svs[name], size)
|
|
26
26
|
|
|
27
|
-
def _locc_bloch(self, rest):
|
|
27
|
+
def _locc_bloch(self, rest: str) -> None:
|
|
28
28
|
m = re.match(r'([A-Z])\s*(\d*)', rest.strip(), re.IGNORECASE) if rest.strip() else None
|
|
29
29
|
if m and m.group(1):
|
|
30
30
|
reg = m.group(1).upper()
|
|
@@ -49,7 +49,7 @@ class LOCCDisplayMixin:
|
|
|
49
49
|
else:
|
|
50
50
|
self.io.writeln(f"?USAGE: BLOCH <reg> [qubit] (registers: {', '.join(self.locc.names)})")
|
|
51
51
|
|
|
52
|
-
def _locc_display_results(self, per_reg, counts_joint):
|
|
52
|
+
def _locc_display_results(self, per_reg: dict, counts_joint: dict) -> None:
|
|
53
53
|
"""Display per-register and joint histograms."""
|
|
54
54
|
for name in self.locc.names:
|
|
55
55
|
size = self.locc.get_size(name)
|
|
@@ -27,7 +27,7 @@ class LOCCExecutionMixin:
|
|
|
27
27
|
self.last_counts, self.io.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
def _locc_run(self):
|
|
30
|
+
def _locc_run(self) -> None:
|
|
31
31
|
"""Execute program in LOCC mode (N registers)."""
|
|
32
32
|
sorted_lines = sorted(self.program.keys())
|
|
33
33
|
if not sorted_lines:
|
|
@@ -44,7 +44,7 @@ class LOCCExecutionMixin:
|
|
|
44
44
|
# Sync last_sv from LOCC engine so EXPECT/DENSITY/BLOCH work
|
|
45
45
|
self.last_sv = self._active_sv
|
|
46
46
|
|
|
47
|
-
def _locc_run_with_send(self, sorted_lines, has_measure):
|
|
47
|
+
def _locc_run_with_send(self, sorted_lines: list[int], has_measure: bool) -> None:
|
|
48
48
|
"""LOCC execution with SEND — prefix/suffix split optimization.
|
|
49
49
|
|
|
50
50
|
Executes the deterministic prefix (before first SEND) once,
|
|
@@ -134,7 +134,9 @@ class LOCCExecutionMixin:
|
|
|
134
134
|
counts_joint[jkey] = counts_joint.get(jkey, 0) + 1
|
|
135
135
|
if shots > 50 and (shot + 1) % progress_interval == 0:
|
|
136
136
|
pct = 100 * (shot + 1) // shots
|
|
137
|
-
|
|
137
|
+
from qubasic_core.qol import quantum_spin
|
|
138
|
+
spin = quantum_spin(shot)
|
|
139
|
+
_prog = f" {spin} {pct}% ({shot+1}/{shots} shots)..."
|
|
138
140
|
if hasattr(self.io, 'write') and sys.stdout.isatty():
|
|
139
141
|
self.io.write(_prog + '\r')
|
|
140
142
|
# Non-terminal: skip progress to avoid \r noise
|
|
@@ -148,7 +150,7 @@ class LOCCExecutionMixin:
|
|
|
148
150
|
self._locc_display_results(per_reg, counts_joint)
|
|
149
151
|
self.last_counts = counts_joint
|
|
150
152
|
|
|
151
|
-
def _locc_run_vectorized(self, sorted_lines, has_measure):
|
|
153
|
+
def _locc_run_vectorized(self, sorted_lines: list[int], has_measure: bool) -> None:
|
|
152
154
|
"""LOCC execution without SEND — single execution, vectorized sampling.
|
|
153
155
|
|
|
154
156
|
When noise is active, re-executes per shot so that Monte Carlo noise
|
|
@@ -314,7 +316,7 @@ class LOCCExecutionMixin:
|
|
|
314
316
|
|
|
315
317
|
raise ValueError(f"LOCC mode requires @A/@B prefix, SEND, SHARE, or IF: {stmt}")
|
|
316
318
|
|
|
317
|
-
def _locc_try_special(self, reg, stmt, run_vars):
|
|
319
|
+
def _locc_try_special(self, reg: str, stmt: str, run_vars: dict) -> bool:
|
|
318
320
|
"""Handle MEAS/RESET/MEASURE_X/Y/Z/SYNDROME in LOCC mode."""
|
|
319
321
|
from qubasic_core.parser import parse_stmt
|
|
320
322
|
from qubasic_core.statements import MeasStmt, ResetStmt, MeasureBasisStmt, SyndromeStmt
|
|
@@ -373,7 +375,7 @@ class LOCCExecutionMixin:
|
|
|
373
375
|
|
|
374
376
|
return False
|
|
375
377
|
|
|
376
|
-
def _locc_apply_gate(self, reg, gate_stmt):
|
|
378
|
+
def _locc_apply_gate(self, reg: str, gate_stmt: str) -> None:
|
|
377
379
|
"""Parse and apply a gate to a LOCC register via numpy.
|
|
378
380
|
|
|
379
381
|
Uses LOCCRegBackend for standard gates. CTRL and INV modifiers
|
|
@@ -4,8 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
|
-
from qubasic_core.
|
|
8
|
-
|
|
7
|
+
from qubasic_core.gates import GATE_TABLE, GATE_ALIASES
|
|
8
|
+
from qubasic_core.patterns import (
|
|
9
9
|
RE_LET_ARRAY, RE_LET_VAR, RE_PRINT,
|
|
10
10
|
RE_GOTO, RE_GOSUB, RE_FOR, RE_NEXT, RE_WHILE, RE_IF_THEN,
|
|
11
11
|
RE_MEAS, RE_RESET, RE_SEND, RE_SHARE, RE_AT_REG_LINE, RE_AT_REG,
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
6
8
|
import time
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
@@ -111,10 +113,14 @@ class ProfilerMixin:
|
|
|
111
113
|
self.io.writeln(f"\nRunning {n} trials...")
|
|
112
114
|
for trial in range(n):
|
|
113
115
|
old_io = self.io
|
|
116
|
+
old_stdout = sys.stdout
|
|
114
117
|
self.io = _NullIOPort()
|
|
118
|
+
sys.stdout = open(os.devnull, 'w')
|
|
115
119
|
try:
|
|
116
120
|
self.cmd_run()
|
|
117
121
|
finally:
|
|
122
|
+
sys.stdout.close()
|
|
123
|
+
sys.stdout = old_stdout
|
|
118
124
|
self.io = old_io
|
|
119
125
|
if self.last_counts:
|
|
120
126
|
if len(self._stats_runs) >= MAX_STATS_RUNS:
|
|
@@ -122,7 +128,9 @@ class ProfilerMixin:
|
|
|
122
128
|
break
|
|
123
129
|
self._stats_runs.append(dict(self.last_counts))
|
|
124
130
|
if n > 10 and (trial + 1) % (n // 10) == 0:
|
|
125
|
-
|
|
131
|
+
from qubasic_core.qol import quantum_spin
|
|
132
|
+
spin = quantum_spin(trial)
|
|
133
|
+
self.io.write(f" {spin} {100 * (trial + 1) // n}%..." + '\r')
|
|
126
134
|
if n > 10:
|
|
127
135
|
self.io.write(" " * 30 + '\r')
|
|
128
136
|
self.io.writeln(f"Collected {len(self._stats_runs)} runs ({n} trials)")
|