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
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""QBASIC LOCC execution mixin — program execution in LOCC mode."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from qbasic_core.engine import (
|
|
9
|
+
ExecResult,
|
|
10
|
+
GATE_TABLE, GATE_ALIASES,
|
|
11
|
+
_np_gate_matrix, _sample_one_np,
|
|
12
|
+
LOCC_SEND_SHOT_CAP, LOCC_SEND_QUBIT_THRESHOLD,
|
|
13
|
+
RE_CTRL, RE_INV,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LOCCExecutionMixin:
|
|
18
|
+
"""LOCC execution methods for QBasicTerminal.
|
|
19
|
+
|
|
20
|
+
Requires: TerminalProtocol — uses self.locc, self.program, self.shots,
|
|
21
|
+
self.variables, self._max_iterations, self._custom_gates,
|
|
22
|
+
self.eval_expr(), self._eval_with_vars(), self._expand_statement(),
|
|
23
|
+
self._tokenize_gate(), self._gate_info(), self._resolve_qubit(),
|
|
24
|
+
self._parse_syndrome(), self._split_colon_stmts(), self._exec_control_flow(),
|
|
25
|
+
self._get_parsed(), self._gosub_stack, self._locc_display_results(),
|
|
26
|
+
self.last_counts, self.io.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def _locc_run(self):
|
|
30
|
+
"""Execute program in LOCC mode (N registers)."""
|
|
31
|
+
sorted_lines = sorted(self.program.keys())
|
|
32
|
+
if not sorted_lines:
|
|
33
|
+
self.io.writeln("NOTHING TO RUN")
|
|
34
|
+
return
|
|
35
|
+
has_measure = any(self.program[l].strip().upper() == 'MEASURE'
|
|
36
|
+
for l in sorted_lines)
|
|
37
|
+
has_send = any(re.search(r'\bSEND\b', self.program[l], re.IGNORECASE)
|
|
38
|
+
for l in sorted_lines)
|
|
39
|
+
if has_send:
|
|
40
|
+
self._locc_run_with_send(sorted_lines, has_measure)
|
|
41
|
+
else:
|
|
42
|
+
self._locc_run_vectorized(sorted_lines, has_measure)
|
|
43
|
+
|
|
44
|
+
def _locc_run_with_send(self, sorted_lines, has_measure):
|
|
45
|
+
"""LOCC execution with SEND — prefix/suffix split optimization.
|
|
46
|
+
|
|
47
|
+
Executes the deterministic prefix (before first SEND) once,
|
|
48
|
+
snapshots the quantum state, then re-executes only the suffix
|
|
49
|
+
(from first SEND onward) per shot. Falls back to full re-execution
|
|
50
|
+
if the prefix contains jumps that could skip past the SEND.
|
|
51
|
+
"""
|
|
52
|
+
from qbasic_core.scope import Scope
|
|
53
|
+
from qbasic_core.statements import SendStmt, GotoStmt, GosubStmt
|
|
54
|
+
|
|
55
|
+
# Check if prefix contains jumps — if so, fall back to full re-exec
|
|
56
|
+
first_send_ip = None
|
|
57
|
+
for i, ln in enumerate(sorted_lines):
|
|
58
|
+
p = self._get_parsed(ln)
|
|
59
|
+
if isinstance(p, SendStmt):
|
|
60
|
+
first_send_ip = i
|
|
61
|
+
break
|
|
62
|
+
has_prefix_jumps = False
|
|
63
|
+
if first_send_ip is not None and first_send_ip > 0:
|
|
64
|
+
for i in range(first_send_ip):
|
|
65
|
+
p = self._get_parsed(sorted_lines[i])
|
|
66
|
+
if isinstance(p, (GotoStmt, GosubStmt)):
|
|
67
|
+
has_prefix_jumps = True
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
sizes_str = '+'.join(str(s) for s in self.locc.sizes)
|
|
71
|
+
mode = "JOINT" if self.locc.joint else "SPLIT"
|
|
72
|
+
shots = self.shots
|
|
73
|
+
max_q = max(self.locc.sizes)
|
|
74
|
+
if max_q > LOCC_SEND_QUBIT_THRESHOLD and shots > LOCC_SEND_SHOT_CAP:
|
|
75
|
+
self.io.writeln(f" WARNING: capping at {LOCC_SEND_SHOT_CAP} shots for "
|
|
76
|
+
f"{max_q}-qubit LOCC w/ SEND (per-shot re-execution)")
|
|
77
|
+
shots = LOCC_SEND_SHOT_CAP
|
|
78
|
+
|
|
79
|
+
# Execute deterministic prefix once (skip if jumps make it unsafe)
|
|
80
|
+
self.locc.reset()
|
|
81
|
+
if has_prefix_jumps or first_send_ip == 0 or first_send_ip is None:
|
|
82
|
+
send_ip = 0 # no safe prefix — re-exec from start
|
|
83
|
+
else:
|
|
84
|
+
prefix_vars = dict(self.variables)
|
|
85
|
+
try:
|
|
86
|
+
send_ip = self._locc_execute_program(sorted_lines, stop_before_send=True,
|
|
87
|
+
run_vars=Scope(prefix_vars))
|
|
88
|
+
except (RuntimeError, ValueError) as e:
|
|
89
|
+
self.io.writeln(f"?RUNTIME ERROR: {e}")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Snapshot state after prefix
|
|
93
|
+
snap = self.locc.snapshot()
|
|
94
|
+
snap_vars = dict(self.variables)
|
|
95
|
+
|
|
96
|
+
per_reg = {name: {} for name in self.locc.names}
|
|
97
|
+
counts_joint = {}
|
|
98
|
+
t0 = time.time()
|
|
99
|
+
progress_interval = max(1, shots // 10)
|
|
100
|
+
|
|
101
|
+
for shot in range(shots):
|
|
102
|
+
# Restore to post-prefix state
|
|
103
|
+
self.locc.restore(snap)
|
|
104
|
+
self.variables.clear()
|
|
105
|
+
self.variables.update(snap_vars)
|
|
106
|
+
try:
|
|
107
|
+
self._locc_execute_program(sorted_lines, start_ip=send_ip,
|
|
108
|
+
run_vars=Scope(self.variables))
|
|
109
|
+
except (RuntimeError, ValueError) as e:
|
|
110
|
+
self.io.writeln(f"?RUNTIME ERROR: {e}")
|
|
111
|
+
return
|
|
112
|
+
if has_measure:
|
|
113
|
+
if self.locc.joint:
|
|
114
|
+
out = _sample_one_np(self.locc.sv, self.locc.n_total)
|
|
115
|
+
parts = []
|
|
116
|
+
pos = len(out)
|
|
117
|
+
for i in range(self.locc.n_regs):
|
|
118
|
+
size = self.locc.sizes[i]
|
|
119
|
+
parts.append(out[pos - size:pos])
|
|
120
|
+
pos -= size
|
|
121
|
+
else:
|
|
122
|
+
parts = [_sample_one_np(self.locc.svs[name],
|
|
123
|
+
self.locc.get_size(name))
|
|
124
|
+
for name in self.locc.names]
|
|
125
|
+
for name, bits in zip(self.locc.names, parts):
|
|
126
|
+
per_reg[name][bits] = per_reg[name].get(bits, 0) + 1
|
|
127
|
+
jkey = '|'.join(parts)
|
|
128
|
+
counts_joint[jkey] = counts_joint.get(jkey, 0) + 1
|
|
129
|
+
if shots > 50 and (shot + 1) % progress_interval == 0:
|
|
130
|
+
pct = 100 * (shot + 1) // shots
|
|
131
|
+
self.io.write(f" {pct}% ({shot+1}/{shots} shots)...\r")
|
|
132
|
+
|
|
133
|
+
if shots > 50:
|
|
134
|
+
self.io.write(" " * 40 + '\r')
|
|
135
|
+
dt = time.time() - t0
|
|
136
|
+
self.io.writeln(f"\nRAN {len(self.program)} lines, LOCC {mode} "
|
|
137
|
+
f"{sizes_str}q, {shots} shots in {dt:.2f}s")
|
|
138
|
+
if has_measure:
|
|
139
|
+
self._locc_display_results(per_reg, counts_joint)
|
|
140
|
+
self.last_counts = counts_joint
|
|
141
|
+
|
|
142
|
+
def _locc_run_vectorized(self, sorted_lines, has_measure):
|
|
143
|
+
"""LOCC execution without SEND — single execution, vectorized sampling."""
|
|
144
|
+
sizes_str = '+'.join(str(s) for s in self.locc.sizes)
|
|
145
|
+
mode = "JOINT" if self.locc.joint else "SPLIT"
|
|
146
|
+
t0 = time.time()
|
|
147
|
+
|
|
148
|
+
self.locc.reset()
|
|
149
|
+
try:
|
|
150
|
+
self._locc_execute_program(sorted_lines)
|
|
151
|
+
except (RuntimeError, ValueError) as e:
|
|
152
|
+
self.io.writeln(f"?RUNTIME ERROR: {e}")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
if has_measure:
|
|
156
|
+
per_reg, cj = self.locc.sample_joint(self.shots)
|
|
157
|
+
dt = time.time() - t0
|
|
158
|
+
self.io.writeln(f"\nRAN {len(self.program)} lines, LOCC {mode} "
|
|
159
|
+
f"{sizes_str}q, {self.shots} shots in {dt:.2f}s")
|
|
160
|
+
self._locc_display_results(per_reg, cj)
|
|
161
|
+
if not cj:
|
|
162
|
+
self.io.writeln(f"\n (SPLIT mode, no SEND — registers are independent)")
|
|
163
|
+
self.last_counts = cj if cj else per_reg.get('A', {})
|
|
164
|
+
else:
|
|
165
|
+
dt = time.time() - t0
|
|
166
|
+
self.io.writeln(f"\nRAN {len(self.program)} lines, LOCC in {dt:.2f}s")
|
|
167
|
+
regs = ', '.join(self.locc.names)
|
|
168
|
+
self.io.writeln(f"(no MEASURE — use STATE {regs} to inspect)")
|
|
169
|
+
|
|
170
|
+
def _locc_execute_program(self, sorted_lines, *, start_ip: int = 0,
|
|
171
|
+
run_vars=None, stop_before_send: bool = False) -> int:
|
|
172
|
+
"""Execute LOCC program lines.
|
|
173
|
+
|
|
174
|
+
Returns the ip where execution stopped (end of program or at first SEND
|
|
175
|
+
if stop_before_send is True).
|
|
176
|
+
"""
|
|
177
|
+
from qbasic_core.scope import Scope
|
|
178
|
+
from qbasic_core.exec_context import ExecContext
|
|
179
|
+
from qbasic_core.statements import MeasureStmt, SendStmt
|
|
180
|
+
if run_vars is None:
|
|
181
|
+
run_vars = Scope(self.variables)
|
|
182
|
+
ctx = ExecContext(
|
|
183
|
+
sorted_lines=sorted_lines, ip=start_ip, run_vars=run_vars,
|
|
184
|
+
max_iterations=self._max_iterations,
|
|
185
|
+
)
|
|
186
|
+
_parsed = [self._get_parsed(ln) for ln in sorted_lines]
|
|
187
|
+
_stmts = [self.program[ln].strip() for ln in sorted_lines]
|
|
188
|
+
n_lines = len(sorted_lines)
|
|
189
|
+
while ctx.ip < n_lines:
|
|
190
|
+
ctx.iteration_count += 1
|
|
191
|
+
if ctx.iteration_count > ctx.max_iterations:
|
|
192
|
+
raise RuntimeError(f"LOOP LIMIT ({ctx.max_iterations}) — possible infinite loop")
|
|
193
|
+
if isinstance(_parsed[ctx.ip], MeasureStmt):
|
|
194
|
+
ctx.ip += 1
|
|
195
|
+
continue
|
|
196
|
+
if stop_before_send and isinstance(_parsed[ctx.ip], SendStmt):
|
|
197
|
+
return ctx.ip
|
|
198
|
+
try:
|
|
199
|
+
result = self._locc_exec_line(_stmts[ctx.ip], ctx.loop_stack, sorted_lines, ctx.ip, run_vars, parsed=_parsed[ctx.ip])
|
|
200
|
+
except Exception as e:
|
|
201
|
+
raise RuntimeError(f"LINE {sorted_lines[ctx.ip]}: {e}") from None
|
|
202
|
+
if result is ExecResult.END:
|
|
203
|
+
break
|
|
204
|
+
elif isinstance(result, int):
|
|
205
|
+
ctx.ip = result
|
|
206
|
+
else:
|
|
207
|
+
ctx.ip += 1
|
|
208
|
+
return ctx.ip
|
|
209
|
+
|
|
210
|
+
def _locc_exec_line(self, stmt, loop_stack, sorted_lines, ip, run_vars, *, parsed=None):
|
|
211
|
+
"""Execute one line in LOCC mode."""
|
|
212
|
+
from qbasic_core.parser import parse_stmt
|
|
213
|
+
from qbasic_core.statements import (
|
|
214
|
+
SendStmt, ShareStmt, AtRegStmt, CompoundStmt,
|
|
215
|
+
RemStmt, MeasureStmt, EndStmt, BarrierStmt, ReturnStmt,
|
|
216
|
+
)
|
|
217
|
+
if parsed is None:
|
|
218
|
+
parsed = parse_stmt(stmt)
|
|
219
|
+
|
|
220
|
+
# Fast-path for terminals
|
|
221
|
+
if isinstance(parsed, (RemStmt, MeasureStmt, BarrierStmt)):
|
|
222
|
+
return ExecResult.ADVANCE
|
|
223
|
+
if isinstance(parsed, EndStmt):
|
|
224
|
+
return ExecResult.END
|
|
225
|
+
if isinstance(parsed, ReturnStmt):
|
|
226
|
+
if not self._gosub_stack:
|
|
227
|
+
raise RuntimeError("RETURN WITHOUT GOSUB")
|
|
228
|
+
return self._gosub_stack.pop()
|
|
229
|
+
if isinstance(parsed, CompoundStmt):
|
|
230
|
+
for sub in parsed.parts:
|
|
231
|
+
self._locc_exec_line(sub, loop_stack, sorted_lines, ip, run_vars)
|
|
232
|
+
return ExecResult.ADVANCE
|
|
233
|
+
|
|
234
|
+
# Fast-path for LOCC-specific statements
|
|
235
|
+
if isinstance(parsed, SendStmt):
|
|
236
|
+
qubit = int(self.eval_expr(parsed.qubit_expr))
|
|
237
|
+
outcome = self.locc.send(parsed.reg, qubit)
|
|
238
|
+
run_vars[parsed.var] = outcome
|
|
239
|
+
self.variables[parsed.var] = outcome
|
|
240
|
+
self.locc.classical[parsed.var] = outcome
|
|
241
|
+
return ExecResult.ADVANCE
|
|
242
|
+
if isinstance(parsed, ShareStmt):
|
|
243
|
+
self.locc.share(parsed.reg1, parsed.q1, parsed.reg2, parsed.q2)
|
|
244
|
+
return ExecResult.ADVANCE
|
|
245
|
+
if isinstance(parsed, AtRegStmt):
|
|
246
|
+
if self._locc_try_special(parsed.reg, parsed.inner, run_vars):
|
|
247
|
+
return ExecResult.ADVANCE
|
|
248
|
+
self._locc_apply_gate(parsed.reg, parsed.inner)
|
|
249
|
+
return ExecResult.ADVANCE
|
|
250
|
+
|
|
251
|
+
# Control flow
|
|
252
|
+
handled, result = self._exec_control_flow(
|
|
253
|
+
stmt, loop_stack, sorted_lines, ip, run_vars,
|
|
254
|
+
lambda s, ls, sl, i, rv: self._locc_exec_line(s, ls, sl, i, rv),
|
|
255
|
+
parsed=parsed)
|
|
256
|
+
if handled:
|
|
257
|
+
return result
|
|
258
|
+
|
|
259
|
+
if ':' in stmt:
|
|
260
|
+
for sub in self._split_colon_stmts(stmt):
|
|
261
|
+
self._locc_exec_line(sub, loop_stack, sorted_lines, ip, run_vars)
|
|
262
|
+
return ExecResult.ADVANCE
|
|
263
|
+
|
|
264
|
+
raise ValueError(f"LOCC mode requires @A/@B prefix, SEND, SHARE, or IF: {stmt}")
|
|
265
|
+
|
|
266
|
+
def _locc_try_special(self, reg, stmt, run_vars):
|
|
267
|
+
"""Handle MEAS/RESET/MEASURE_X/Y/Z/SYNDROME in LOCC mode."""
|
|
268
|
+
from qbasic_core.parser import parse_stmt
|
|
269
|
+
from qbasic_core.statements import MeasStmt, ResetStmt, MeasureBasisStmt, SyndromeStmt
|
|
270
|
+
p = parse_stmt(stmt)
|
|
271
|
+
|
|
272
|
+
if isinstance(p, MeasStmt):
|
|
273
|
+
qubit = int(self._eval_with_vars(p.qubit_expr, run_vars))
|
|
274
|
+
outcome = self.locc.send(reg, qubit)
|
|
275
|
+
run_vars[p.var] = outcome
|
|
276
|
+
self.variables[p.var] = outcome
|
|
277
|
+
self.locc.classical[p.var] = outcome
|
|
278
|
+
return True
|
|
279
|
+
|
|
280
|
+
if isinstance(p, ResetStmt):
|
|
281
|
+
qubit = int(self._eval_with_vars(p.qubit_expr, run_vars))
|
|
282
|
+
outcome = self.locc.send(reg, qubit)
|
|
283
|
+
if outcome == 1:
|
|
284
|
+
self.locc.apply(reg, 'X', (), [qubit])
|
|
285
|
+
return True
|
|
286
|
+
|
|
287
|
+
if isinstance(p, MeasureBasisStmt):
|
|
288
|
+
qubit = int(self._eval_with_vars(p.qubit_expr, run_vars))
|
|
289
|
+
if p.basis == 'X':
|
|
290
|
+
self.locc.apply(reg, 'H', (), [qubit])
|
|
291
|
+
elif p.basis == 'Y':
|
|
292
|
+
self.locc.apply(reg, 'SDG', (), [qubit])
|
|
293
|
+
self.locc.apply(reg, 'H', (), [qubit])
|
|
294
|
+
outcome = self.locc.send(reg, qubit)
|
|
295
|
+
var = f"m{p.basis.lower()}_{qubit}"
|
|
296
|
+
run_vars[var] = outcome
|
|
297
|
+
self.variables[var] = outcome
|
|
298
|
+
self.locc.classical[var] = outcome
|
|
299
|
+
return True
|
|
300
|
+
|
|
301
|
+
parsed = self._parse_syndrome(stmt, run_vars)
|
|
302
|
+
if parsed is not None:
|
|
303
|
+
pauli_str, qubits, var = parsed
|
|
304
|
+
n_reg = self.locc.get_size(reg)
|
|
305
|
+
anc = n_reg - 1
|
|
306
|
+
if anc in qubits:
|
|
307
|
+
raise ValueError(
|
|
308
|
+
f"Qubit {anc} needed as ancilla but appears in stabilizer. "
|
|
309
|
+
f"Increase register size by 1.")
|
|
310
|
+
self.locc.apply(reg, 'H', (), [anc])
|
|
311
|
+
for p, q in zip(pauli_str, qubits):
|
|
312
|
+
if p != 'I':
|
|
313
|
+
self.locc.apply(reg, self._PAULI_TO_CONTROLLED[p], (), [anc, q])
|
|
314
|
+
self.locc.apply(reg, 'H', (), [anc])
|
|
315
|
+
outcome = self.locc.send(reg, anc)
|
|
316
|
+
if outcome == 1:
|
|
317
|
+
self.locc.apply(reg, 'X', (), [anc])
|
|
318
|
+
run_vars[var] = outcome
|
|
319
|
+
self.variables[var] = outcome
|
|
320
|
+
self.locc.classical[var] = outcome
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
return False
|
|
324
|
+
|
|
325
|
+
def _locc_apply_gate(self, reg, gate_stmt):
|
|
326
|
+
"""Parse and apply a gate to a LOCC register via numpy.
|
|
327
|
+
|
|
328
|
+
Uses LOCCRegBackend for standard gates. CTRL and INV modifiers
|
|
329
|
+
use apply_matrix directly since they need matrix construction.
|
|
330
|
+
"""
|
|
331
|
+
from qbasic_core.backend import LOCCRegBackend
|
|
332
|
+
backend = LOCCRegBackend(self.locc, reg)
|
|
333
|
+
n_reg = self.locc.get_size(reg)
|
|
334
|
+
expanded = self._expand_statement(gate_stmt)
|
|
335
|
+
for stmt in expanded:
|
|
336
|
+
tokens = self._tokenize_gate(stmt)
|
|
337
|
+
if not tokens:
|
|
338
|
+
continue
|
|
339
|
+
gate_name = tokens[0].upper()
|
|
340
|
+
gate_name = GATE_ALIASES.get(gate_name, gate_name)
|
|
341
|
+
if gate_name == 'BARRIER':
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
# CTRL modifier
|
|
345
|
+
m_ctrl = RE_CTRL.match(stmt)
|
|
346
|
+
if m_ctrl:
|
|
347
|
+
inner_name = GATE_ALIASES.get(m_ctrl.group(1).upper(), m_ctrl.group(1).upper())
|
|
348
|
+
args = [a.strip() for a in m_ctrl.group(2).replace(',', ' ').split()]
|
|
349
|
+
ctrl_qubit = self._resolve_qubit(args[0], n_qubits=n_reg)
|
|
350
|
+
target_qubits = [self._resolve_qubit(a, n_qubits=n_reg) for a in args[1:]]
|
|
351
|
+
inner_matrix = _np_gate_matrix(inner_name, ())
|
|
352
|
+
dim = inner_matrix.shape[0]
|
|
353
|
+
n_inner_qubits = int(np.log2(dim))
|
|
354
|
+
if len(target_qubits) != n_inner_qubits:
|
|
355
|
+
raise ValueError(
|
|
356
|
+
f"CTRL {inner_name} needs 1 control + {n_inner_qubits} "
|
|
357
|
+
f"target qubit(s), got {len(target_qubits)} target(s)")
|
|
358
|
+
controlled = np.eye(2 * dim, dtype=complex)
|
|
359
|
+
controlled[dim:, dim:] = inner_matrix
|
|
360
|
+
self.locc.apply_matrix(reg, controlled, [ctrl_qubit] + target_qubits)
|
|
361
|
+
continue
|
|
362
|
+
|
|
363
|
+
# INV modifier
|
|
364
|
+
m_inv = RE_INV.match(stmt)
|
|
365
|
+
if m_inv:
|
|
366
|
+
inner_name = GATE_ALIASES.get(m_inv.group(1).upper(), m_inv.group(1).upper())
|
|
367
|
+
inv_args = [a.strip() for a in m_inv.group(2).replace(',', ' ').split()]
|
|
368
|
+
info = self._gate_info(inner_name)
|
|
369
|
+
if info is None:
|
|
370
|
+
raise ValueError(f"UNKNOWN GATE: {inner_name}")
|
|
371
|
+
n_params, n_qubits_needed = info
|
|
372
|
+
params = [self.eval_expr(a) for a in inv_args[:n_params]]
|
|
373
|
+
qubits = [self._resolve_qubit(a, n_qubits=n_reg) for a in inv_args[n_params:n_params+n_qubits_needed]]
|
|
374
|
+
matrix = _np_gate_matrix(inner_name, tuple(params)).conj().T
|
|
375
|
+
self.locc.apply_matrix(reg, matrix, qubits)
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
# Standard gate — through backend
|
|
379
|
+
info = self._gate_info(gate_name)
|
|
380
|
+
if info is None:
|
|
381
|
+
raise ValueError(f"UNKNOWN GATE: {gate_name}")
|
|
382
|
+
n_params, n_qubits_needed = info
|
|
383
|
+
args = tokens[1:]
|
|
384
|
+
if len(args) < n_params + n_qubits_needed:
|
|
385
|
+
raise ValueError(f"{gate_name} needs {n_params} params + "
|
|
386
|
+
f"{n_qubits_needed} qubits")
|
|
387
|
+
params = tuple(self.eval_expr(a) for a in args[:n_params])
|
|
388
|
+
qubits = [self._resolve_qubit(a, n_qubits=n_reg) for a in args[n_params:n_params+n_qubits_needed]]
|
|
389
|
+
backend.apply_gate(gate_name, params, qubits)
|