qubasic 0.6.3__tar.gz → 0.6.4__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.6.3 → qubasic-0.6.4}/CHANGELOG.md +9 -0
- {qubasic-0.6.3/qubasic.egg-info → qubasic-0.6.4}/PKG-INFO +2 -2
- {qubasic-0.6.3 → qubasic-0.6.4}/README.md +1 -1
- {qubasic-0.6.3 → qubasic-0.6.4}/pyproject.toml +1 -1
- {qubasic-0.6.3 → qubasic-0.6.4/qubasic.egg-info}/PKG-INFO +2 -2
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/__init__.py +1 -1
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/cli.py +10 -11
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/control_flow.py +49 -1
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/executor.py +44 -1
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/file_io.py +0 -2
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/locc_engine.py +19 -4
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/locc_execution.py +1 -2
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/terminal.py +12 -1
- {qubasic-0.6.3 → qubasic-0.6.4}/LICENSE +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/MANIFEST.in +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/examples/bell.qb +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/examples/grover3.qb +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/examples/locc_teleport.qb +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/examples/sweep_rx.qb +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic.egg-info/SOURCES.txt +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic.egg-info/entry_points.txt +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic.egg-info/top_level.txt +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/__main__.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/analysis.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/backend.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/classic.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/debug.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/demos.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/display.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/engine.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/engine_state.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/errors.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/exec_context.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/expression.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/gates.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/help_text.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/locc.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/locc_commands.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/locc_display.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/memory.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/parser.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/patterns.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/profiler.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/program_mgmt.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/protocol.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/qol.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/scope.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/screen.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/state_display.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/statements.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/strings.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/subs.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/qubasic_core/sweep.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/setup.cfg +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/tests/test_features.py +0 -0
- {qubasic-0.6.3 → qubasic-0.6.4}/tests/test_qubasic.py +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.4 (2026-06-19)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Immediate-mode `PRINT` at the REPL no longer prints a spurious statevector after its output. `PRINT` is not a dispatch-table command, so it fell through to the immediate gate path, which always dumped `|psi>`. That path now skips the dump when the typed line added no circuit operations, so gate and subroutine entries still show state while `PRINT`, `MEASURE`, and other classical statements do not.
|
|
7
|
+
- `--quiet` now prints results with the banner suppressed, as documented. The results branch was unreachable (guarded by `elif not quiet` inside an `if quiet or json_mode` block), so `--quiet` previously produced no output at all.
|
|
8
|
+
- A `MEASURE` reachable only inside a `DEF`/`SUB` body, an `IF ... THEN/ELSE` clause, or a colon compound is now detected, so the program takes the shots path (and `qubasic script.qb` auto-runs) instead of the no-measure statevector path.
|
|
9
|
+
- `LET m(i, j) = x` writes a multi-dimensional array element using the same flat-stride convention the expression-side accessor reads, so multi-dimensional reads and writes agree.
|
|
10
|
+
- Immediate-mode `LET a(i) = <expr>` at the REPL assigns the array element, matching the in-program `LET` (previously only the in-program form handled array targets).
|
|
11
|
+
|
|
3
12
|
## 0.6.3 (2026-06-18)
|
|
4
13
|
|
|
5
14
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -661,7 +661,7 @@ QBASIC detects available RAM, estimates per-instance memory, and reports maximum
|
|
|
661
661
|
### Simulation method selection
|
|
662
662
|
- **automatic**: stabilizer for Clifford-only circuits, MPS for >28 qubits, statevector otherwise
|
|
663
663
|
- **stabilizer**: polynomial-time for Clifford circuits (H, S, CX, SWAP, etc.)
|
|
664
|
-
- **matrix_product_state**:
|
|
664
|
+
- **matrix_product_state**: memory-efficient for low-entanglement circuits; handles the full 32-qubit range that would exhaust statevector
|
|
665
665
|
- **extended_stabilizer**: approximate simulation for near-Clifford circuits
|
|
666
666
|
- **statevector**: exact, limited by RAM (~28 qubits on 16GB)
|
|
667
667
|
- **density_matrix**: includes mixed states, ~14 qubits on 16GB
|
|
@@ -628,7 +628,7 @@ QBASIC detects available RAM, estimates per-instance memory, and reports maximum
|
|
|
628
628
|
### Simulation method selection
|
|
629
629
|
- **automatic**: stabilizer for Clifford-only circuits, MPS for >28 qubits, statevector otherwise
|
|
630
630
|
- **stabilizer**: polynomial-time for Clifford circuits (H, S, CX, SWAP, etc.)
|
|
631
|
-
- **matrix_product_state**:
|
|
631
|
+
- **matrix_product_state**: memory-efficient for low-entanglement circuits; handles the full 32-qubit range that would exhaust statevector
|
|
632
632
|
- **extended_stabilizer**: approximate simulation for near-Clifford circuits
|
|
633
633
|
- **statevector**: exact, limited by RAM (~28 qubits on 16GB)
|
|
634
634
|
- **density_matrix**: includes mixed states, ~14 qubits on 16GB
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -661,7 +661,7 @@ QBASIC detects available RAM, estimates per-instance memory, and reports maximum
|
|
|
661
661
|
### Simulation method selection
|
|
662
662
|
- **automatic**: stabilizer for Clifford-only circuits, MPS for >28 qubits, statevector otherwise
|
|
663
663
|
- **stabilizer**: polynomial-time for Clifford circuits (H, S, CX, SWAP, etc.)
|
|
664
|
-
- **matrix_product_state**:
|
|
664
|
+
- **matrix_product_state**: memory-efficient for low-entanglement circuits; handles the full 32-qubit range that would exhaust statevector
|
|
665
665
|
- **extended_stabilizer**: approximate simulation for near-Clifford circuits
|
|
666
666
|
- **statevector**: exact, limited by RAM (~28 qubits on 16GB)
|
|
667
667
|
- **density_matrix**: includes mixed states, ~14 qubits on 16GB
|
|
@@ -38,12 +38,8 @@ def run_script(path: str, terminal: 'QBasicTerminal') -> None:
|
|
|
38
38
|
ProgramMgmtMixin._load_lines_with_defs(
|
|
39
39
|
lines, lambda line: terminal.process(line, track_undo=False))
|
|
40
40
|
|
|
41
|
-
# Auto-run if the program has a MEASURE
|
|
42
|
-
|
|
43
|
-
terminal.program.get(ln, '').strip().upper() == 'MEASURE'
|
|
44
|
-
for ln in terminal.program
|
|
45
|
-
)
|
|
46
|
-
if terminal.program and has_measure:
|
|
41
|
+
# Auto-run if the program has a reachable MEASURE (incl. in subs / IF clauses)
|
|
42
|
+
if terminal.program and terminal._program_has_measure(sorted(terminal.program)):
|
|
47
43
|
terminal.cmd_run()
|
|
48
44
|
|
|
49
45
|
|
|
@@ -125,8 +121,12 @@ def main():
|
|
|
125
121
|
print(_json.dumps({'error': err}, indent=2))
|
|
126
122
|
sys.exit(1)
|
|
127
123
|
print(_json.dumps(term.result(), indent=2))
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
else:
|
|
125
|
+
# --quiet (no --json): the banner was never emitted into the
|
|
126
|
+
# buffer (only the non-captured path calls print_banner), so the
|
|
127
|
+
# captured text is exactly the results. Print them, matching the
|
|
128
|
+
# documented "suppress banner, output results only" behavior.
|
|
129
|
+
print(buf.getvalue(), end='')
|
|
130
130
|
if err is not None:
|
|
131
131
|
print(f"?ERROR: {err}")
|
|
132
132
|
sys.exit(1)
|
|
@@ -134,9 +134,8 @@ def main():
|
|
|
134
134
|
term.print_banner()
|
|
135
135
|
run_script(path, term)
|
|
136
136
|
# Exit 0 on success; 1 if a measured program produced no counts.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
for ln in term.program) else 1)
|
|
137
|
+
expects_measure = bool(term.program) and term._program_has_measure(sorted(term.program))
|
|
138
|
+
sys.exit(0 if term.last_counts is not None or not expects_measure else 1)
|
|
140
139
|
else:
|
|
141
140
|
term.repl()
|
|
142
141
|
|
|
@@ -40,12 +40,60 @@ class ControlFlowMixin:
|
|
|
40
40
|
# signature for compatibility with _exec_control_flow's argument
|
|
41
41
|
# passing but is not used by the methods themselves.
|
|
42
42
|
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _split_arg_list(s: str) -> list[str]:
|
|
45
|
+
"""Split a comma list at top level (outside quotes and brackets)."""
|
|
46
|
+
parts: list[str] = []
|
|
47
|
+
buf = ''
|
|
48
|
+
depth = 0
|
|
49
|
+
quote: str | None = None
|
|
50
|
+
for ch in s:
|
|
51
|
+
if quote:
|
|
52
|
+
buf += ch
|
|
53
|
+
if ch == quote:
|
|
54
|
+
quote = None
|
|
55
|
+
elif ch in ('"', "'"):
|
|
56
|
+
quote = ch
|
|
57
|
+
buf += ch
|
|
58
|
+
elif ch in '([':
|
|
59
|
+
depth += 1
|
|
60
|
+
buf += ch
|
|
61
|
+
elif ch in ')]':
|
|
62
|
+
depth = max(0, depth - 1)
|
|
63
|
+
buf += ch
|
|
64
|
+
elif ch == ',' and depth == 0:
|
|
65
|
+
parts.append(buf)
|
|
66
|
+
buf = ''
|
|
67
|
+
else:
|
|
68
|
+
buf += ch
|
|
69
|
+
if buf.strip():
|
|
70
|
+
parts.append(buf)
|
|
71
|
+
return [p.strip() for p in parts if p.strip()]
|
|
72
|
+
|
|
43
73
|
def _cf_let_array(self, stmt: str, run_vars: dict[str, Any],
|
|
44
74
|
parsed: LetArrayStmt) -> tuple[bool, ExecOutcome]:
|
|
45
75
|
name, idx_expr, val_expr = parsed.name, parsed.index_expr, parsed.value_expr
|
|
46
76
|
base = getattr(self, '_option_base', 0)
|
|
47
|
-
idx = int(self._eval_with_vars(idx_expr, run_vars)) - base
|
|
48
77
|
val = self._eval_with_vars(val_expr, run_vars)
|
|
78
|
+
parts = self._split_arg_list(idx_expr)
|
|
79
|
+
if len(parts) > 1:
|
|
80
|
+
# Multi-dimensional write: flatten with the same stride convention
|
|
81
|
+
# the expression-side accessor uses, so reads and writes agree.
|
|
82
|
+
dims = getattr(self, '_array_dims', {}).get(name)
|
|
83
|
+
indices = [int(self._eval_with_vars(p, run_vars)) - base for p in parts]
|
|
84
|
+
if any(i < 0 for i in indices):
|
|
85
|
+
raise RuntimeError(f"ARRAY INDEX OUT OF RANGE: {name}({idx_expr})")
|
|
86
|
+
flat, stride = 0, 1
|
|
87
|
+
for k in range(len(indices) - 1, -1, -1):
|
|
88
|
+
flat += indices[k] * stride
|
|
89
|
+
stride *= dims[k] if dims and k < len(dims) else 1
|
|
90
|
+
if name not in self.arrays:
|
|
91
|
+
raise RuntimeError(f"ARRAY NOT DIMENSIONED: {name} (use DIM first)")
|
|
92
|
+
if flat < 0 or flat >= len(self.arrays[name]):
|
|
93
|
+
raise RuntimeError(f"ARRAY INDEX OUT OF RANGE: {name}({idx_expr})")
|
|
94
|
+
self.arrays[name][flat] = val
|
|
95
|
+
return True, ExecResult.ADVANCE
|
|
96
|
+
idx = int(self._eval_with_vars(idx_expr, run_vars)) - base
|
|
49
97
|
if idx < 0:
|
|
50
98
|
raise RuntimeError(f"ARRAY INDEX OUT OF RANGE: {name}({idx + base})")
|
|
51
99
|
if name not in self.arrays:
|
|
@@ -66,6 +66,41 @@ class ExecutorMixin:
|
|
|
66
66
|
|
|
67
67
|
# ── Circuit Building ──────────────────────────────────────────────
|
|
68
68
|
|
|
69
|
+
def _program_has_measure(self, sorted_lines) -> bool:
|
|
70
|
+
"""True if any reachable statement measures.
|
|
71
|
+
|
|
72
|
+
The plain per-line scan misses a MEASURE that only appears inside a DEF
|
|
73
|
+
subroutine body or an IF/THEN-ELSE clause, which left the run on the
|
|
74
|
+
no-measure path with no counts. This recursion-guarded walk follows
|
|
75
|
+
colon compounds, IF clauses, and subroutine bodies so the shots path
|
|
76
|
+
fires whenever a measurement is actually reachable.
|
|
77
|
+
"""
|
|
78
|
+
from qubasic_core.statements import MeasureStmt, CompoundStmt, IfThenStmt
|
|
79
|
+
from qubasic_core.parser import parse_stmt
|
|
80
|
+
|
|
81
|
+
def scan(text: str, seen: frozenset) -> bool:
|
|
82
|
+
p = parse_stmt(text)
|
|
83
|
+
if isinstance(p, MeasureStmt):
|
|
84
|
+
return True
|
|
85
|
+
if isinstance(p, CompoundStmt):
|
|
86
|
+
return any(scan(part, seen) for part in p.parts)
|
|
87
|
+
if isinstance(p, IfThenStmt):
|
|
88
|
+
if p.then_clause and scan(p.then_clause, seen):
|
|
89
|
+
return True
|
|
90
|
+
if p.else_clause and scan(p.else_clause, seen):
|
|
91
|
+
return True
|
|
92
|
+
return False
|
|
93
|
+
words = text.strip().split()
|
|
94
|
+
if words:
|
|
95
|
+
word = words[0].upper().split('(')[0]
|
|
96
|
+
if word in self.subroutines and word not in seen:
|
|
97
|
+
sub = self.subroutines[word]
|
|
98
|
+
body = sub['body'] if isinstance(sub, dict) else sub
|
|
99
|
+
return any(scan(b, seen | {word}) for b in body)
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
return any(scan(self.program[ln], frozenset()) for ln in sorted_lines)
|
|
103
|
+
|
|
69
104
|
def build_circuit(self) -> tuple['QuantumCircuit', bool]:
|
|
70
105
|
"""Compile program lines into a QuantumCircuit. Returns (circuit, has_measure)."""
|
|
71
106
|
from qubasic_core.exec_context import ExecContext
|
|
@@ -92,7 +127,9 @@ class ExecutorMixin:
|
|
|
92
127
|
qc=qc,
|
|
93
128
|
backend=backend,
|
|
94
129
|
)
|
|
95
|
-
|
|
130
|
+
# Reachability scan catches MEASURE inside subroutines and IF clauses,
|
|
131
|
+
# not just literal top-level MEASURE lines.
|
|
132
|
+
has_measure = self._program_has_measure(ctx.sorted_lines)
|
|
96
133
|
self._on_measure_fired = False
|
|
97
134
|
|
|
98
135
|
while ctx.ip < len(ctx.sorted_lines):
|
|
@@ -685,6 +722,12 @@ class ExecutorMixin:
|
|
|
685
722
|
imm_ctx = ExecContext(sorted_lines=[0], ip=0,
|
|
686
723
|
run_vars=dict(self.variables), qc=qc)
|
|
687
724
|
self._exec_line(line, ctx=imm_ctx)
|
|
725
|
+
# A classical statement typed at the prompt (PRINT, MEASURE, an IF whose
|
|
726
|
+
# clause printed, ...) adds no circuit operations and has already emitted
|
|
727
|
+
# its own output via _exec_line. Simulating an empty circuit just to print
|
|
728
|
+
# an unchanged |psi> would be spurious, so skip the statevector dump.
|
|
729
|
+
if qc.size() == 0:
|
|
730
|
+
return
|
|
688
731
|
qc.save_statevector()
|
|
689
732
|
backend = self._make_backend('statevector')
|
|
690
733
|
result = backend.run(transpile(qc, backend)).result()
|
|
@@ -195,8 +195,6 @@ class FileIOMixin:
|
|
|
195
195
|
DEFs in the imported file are prefixed with the module name.
|
|
196
196
|
E.g., IMPORT "math.qb" makes DEF ROT available as MATH.ROT.
|
|
197
197
|
"""
|
|
198
|
-
from qubasic_core.engine import RE_IMPORT
|
|
199
|
-
m = RE_IMPORT.match(f"IMPORT {rest}") if not rest.startswith('IMPORT') else RE_IMPORT.match(rest)
|
|
200
198
|
path = rest.strip().strip('"').strip("'")
|
|
201
199
|
if not path:
|
|
202
200
|
self.io.writeln('?USAGE: IMPORT "filename"')
|
|
@@ -180,7 +180,13 @@ class LOCCEngine:
|
|
|
180
180
|
self._renormalize(reg)
|
|
181
181
|
|
|
182
182
|
def apply(self, reg: str, gate_name: str, params: tuple[float, ...], qubits: list[int]) -> None:
|
|
183
|
-
"""Apply a gate to a
|
|
183
|
+
"""Apply a gate to a register, then apply noise if configured.
|
|
184
|
+
|
|
185
|
+
Qubit indices are register-LOCAL (0..size-1); the register offset into
|
|
186
|
+
the joint statevector is added internally. Callers that index the raw
|
|
187
|
+
statevector (e.g. for fidelity/inspection) must convert with
|
|
188
|
+
``offsets[reg] + local`` themselves.
|
|
189
|
+
"""
|
|
184
190
|
self._check_qubits(reg, qubits)
|
|
185
191
|
matrix = _np_gate_matrix(gate_name, tuple(params))
|
|
186
192
|
if self.joint:
|
|
@@ -194,7 +200,10 @@ class LOCCEngine:
|
|
|
194
200
|
self._contiguate(reg)
|
|
195
201
|
|
|
196
202
|
def share(self, reg1: str, q1: int, reg2: str, q2: int) -> None:
|
|
197
|
-
"""Create Bell pair |Phi+> between reg1[q1] and reg2[q2]. JOINT only.
|
|
203
|
+
"""Create Bell pair |Phi+> between reg1[q1] and reg2[q2]. JOINT only.
|
|
204
|
+
|
|
205
|
+
``q1``/``q2`` are register-LOCAL indices; offsets are added internally.
|
|
206
|
+
"""
|
|
198
207
|
if not self.joint:
|
|
199
208
|
raise RuntimeError("SHARE requires LOCC JOINT mode")
|
|
200
209
|
self._check_qubits(reg1, [q1])
|
|
@@ -210,7 +219,10 @@ class LOCCEngine:
|
|
|
210
219
|
self._apply_noise(reg2, [q2])
|
|
211
220
|
|
|
212
221
|
def send(self, reg: str, qubit: int) -> int:
|
|
213
|
-
"""Measure a qubit (Born rule) and return the classical outcome.
|
|
222
|
+
"""Measure a qubit (Born rule) and return the classical outcome.
|
|
223
|
+
|
|
224
|
+
``qubit`` is register-LOCAL (0..size-1); the offset is added internally.
|
|
225
|
+
"""
|
|
214
226
|
self._check_qubits(reg, [qubit])
|
|
215
227
|
if self.joint:
|
|
216
228
|
actual = qubit + self.offsets[self._idx(reg)]
|
|
@@ -266,7 +278,10 @@ class LOCCEngine:
|
|
|
266
278
|
return per_reg, {}
|
|
267
279
|
|
|
268
280
|
def apply_matrix(self, reg: str, matrix: np.ndarray, qubits: list[int]) -> None:
|
|
269
|
-
"""Apply a raw unitary matrix to qubits in a register.
|
|
281
|
+
"""Apply a raw unitary matrix to qubits in a register.
|
|
282
|
+
|
|
283
|
+
``qubits`` are register-LOCAL indices; offsets are added internally.
|
|
284
|
+
"""
|
|
270
285
|
self._check_qubits(reg, qubits)
|
|
271
286
|
if self.joint:
|
|
272
287
|
idx = self._idx(reg)
|
|
@@ -33,8 +33,7 @@ class LOCCExecutionMixin:
|
|
|
33
33
|
if not sorted_lines:
|
|
34
34
|
self.io.writeln("NOTHING TO RUN")
|
|
35
35
|
return
|
|
36
|
-
has_measure =
|
|
37
|
-
for l in sorted_lines)
|
|
36
|
+
has_measure = self._program_has_measure(sorted_lines)
|
|
38
37
|
has_send = any(re.search(r'\bSEND\b', self.program[l], re.IGNORECASE)
|
|
39
38
|
for l in sorted_lines)
|
|
40
39
|
if has_send:
|
|
@@ -928,7 +928,18 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
928
928
|
|
|
929
929
|
def cmd_let(self, rest: str) -> None:
|
|
930
930
|
"""LET <var> = <expr> — assign a computed value to a variable.
|
|
931
|
-
Supports record fields
|
|
931
|
+
Supports record fields (LET p.x = 3.14) and array elements
|
|
932
|
+
(LET a(0) = PI, LET m(i, j) = x), matching the in-program LET."""
|
|
933
|
+
from qubasic_core.patterns import RE_LET_ARRAY
|
|
934
|
+
am = RE_LET_ARRAY.match(f"LET {rest}")
|
|
935
|
+
if am:
|
|
936
|
+
from qubasic_core.statements import LetArrayStmt
|
|
937
|
+
name, idx_expr, val_expr = am.group(1), am.group(2), am.group(3)
|
|
938
|
+
parsed = LetArrayStmt(raw=f"LET {rest}", name=name,
|
|
939
|
+
index_expr=idx_expr, value_expr=val_expr)
|
|
940
|
+
self._cf_let_array(f"LET {rest}", self.variables, parsed)
|
|
941
|
+
self.io.writeln(f"{name}({idx_expr.strip()}) = {self.eval_expr(val_expr)}")
|
|
942
|
+
return
|
|
932
943
|
m = re.match(r'(\w+(?:\.\w+)?)\s*=\s*(.*)', rest)
|
|
933
944
|
if not m:
|
|
934
945
|
self.io.writeln("?USAGE: LET <var> = <expr>")
|
|
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
|