Coverage for /home/runner/work/tket/tket/pytket/pytket/qasm/qasm.py: 92%
1039 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-14 11:30 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-14 11:30 +0000
1# Copyright Quantinuum
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15import itertools
16import os
17import re
18import uuid
19from collections import OrderedDict
20from collections.abc import Callable, Generator, Iterable, Iterator, Sequence
21from dataclasses import dataclass
22from decimal import Decimal
23from importlib import import_module
24from itertools import chain, groupby
25from typing import Any, NewType, TextIO, TypeVar, Union, cast
27from lark import Discard, Lark, Token, Transformer, Tree
28from sympy import Expr, Symbol, pi
30from pytket._tket.circuit import (
31 BarrierOp,
32 ClExpr,
33 ClExprOp,
34 Command,
35 Conditional,
36 CopyBitsOp,
37 MultiBitOp,
38 RangePredicateOp,
39 SetBitsOp,
40 WASMOp,
41 WiredClExpr,
42)
43from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE
44from pytket.circuit import (
45 Bit,
46 BitRegister,
47 Circuit,
48 Op,
49 OpType,
50 Qubit,
51 QubitRegister,
52 UnitID,
53)
54from pytket.circuit.clexpr import (
55 check_register_alignments,
56 has_reg_output,
57 wired_clexpr_from_logic_exp,
58)
59from pytket.circuit.decompose_classical import int_to_bools
60from pytket.circuit.logic_exp import (
61 BitLogicExp,
62 BitWiseOp,
63 LogicExp,
64 PredicateExp,
65 RegEq,
66 RegLogicExp,
67 RegNeg,
68 RegWiseOp,
69 create_logic_exp,
70 create_predicate_exp,
71)
72from pytket.passes import (
73 AutoRebase,
74 DecomposeBoxes,
75 RemoveRedundancies,
76 scratch_reg_resize_pass,
77)
78from pytket.qasm.grammar import grammar
79from pytket.wasm import WasmFileHandler
82class QASMParseError(Exception):
83 """Error while parsing QASM input."""
85 def __init__(self, msg: str, line: int | None = None, fname: str | None = None):
86 self.msg = msg
87 self.line = line
88 self.fname = fname
90 ctx = "" if fname is None else f"\nFile:{fname}: "
91 ctx += "" if line is None else f"\nLine:{line}. "
93 super().__init__(f"{msg}{ctx}")
96class QASMUnsupportedError(Exception):
97 pass
100Value = Union[int, float, str]
101T = TypeVar("T")
103_BITOPS = set(op.value for op in BitWiseOp)
104_BITOPS.update(("+", "-")) # both are parsed to XOR
105_REGOPS = set(op.value for op in RegWiseOp)
107Arg = Union[list, str]
110NOPARAM_COMMANDS = {
111 "CX": OpType.CX, # built-in gate equivalent to "cx"
112 "cx": OpType.CX,
113 "x": OpType.X,
114 "y": OpType.Y,
115 "z": OpType.Z,
116 "h": OpType.H,
117 "s": OpType.S,
118 "sdg": OpType.Sdg,
119 "t": OpType.T,
120 "tdg": OpType.Tdg,
121 "sx": OpType.SX,
122 "sxdg": OpType.SXdg,
123 "cz": OpType.CZ,
124 "cy": OpType.CY,
125 "ch": OpType.CH,
126 "csx": OpType.CSX,
127 "ccx": OpType.CCX,
128 "c3x": OpType.CnX,
129 "c4x": OpType.CnX,
130 "ZZ": OpType.ZZMax,
131 "measure": OpType.Measure,
132 "reset": OpType.Reset,
133 "id": OpType.noop,
134 "barrier": OpType.Barrier,
135 "swap": OpType.SWAP,
136 "cswap": OpType.CSWAP,
137}
138PARAM_COMMANDS = {
139 "p": OpType.U1, # alias. https://github.com/Qiskit/qiskit-terra/pull/4765
140 "u": OpType.U3, # alias. https://github.com/Qiskit/qiskit-terra/pull/4765
141 "U": OpType.U3, # built-in gate equivalent to "u3"
142 "u3": OpType.U3,
143 "u2": OpType.U2,
144 "u1": OpType.U1,
145 "rx": OpType.Rx,
146 "rxx": OpType.XXPhase,
147 "ry": OpType.Ry,
148 "rz": OpType.Rz,
149 "RZZ": OpType.ZZPhase,
150 "rzz": OpType.ZZPhase,
151 "Rz": OpType.Rz,
152 "U1q": OpType.PhasedX,
153 "crz": OpType.CRz,
154 "crx": OpType.CRx,
155 "cry": OpType.CRy,
156 "cu1": OpType.CU1,
157 "cu3": OpType.CU3,
158 "Rxxyyzz": OpType.TK2,
159}
161NOPARAM_EXTRA_COMMANDS = {
162 "v": OpType.V,
163 "vdg": OpType.Vdg,
164 "cv": OpType.CV,
165 "cvdg": OpType.CVdg,
166 "csxdg": OpType.CSXdg,
167 "bridge": OpType.BRIDGE,
168 "iswapmax": OpType.ISWAPMax,
169 "zzmax": OpType.ZZMax,
170 "ecr": OpType.ECR,
171 "cs": OpType.CS,
172 "csdg": OpType.CSdg,
173}
175PARAM_EXTRA_COMMANDS = {
176 "tk2": OpType.TK2,
177 "iswap": OpType.ISWAP,
178 "phasediswap": OpType.PhasedISWAP,
179 "yyphase": OpType.YYPhase,
180 "xxphase3": OpType.XXPhase3,
181 "eswap": OpType.ESWAP,
182 "fsim": OpType.FSim,
183}
185N_PARAMS_EXTRA_COMMANDS = {
186 OpType.TK2: 3,
187 OpType.ISWAP: 1,
188 OpType.PhasedISWAP: 2,
189 OpType.YYPhase: 1,
190 OpType.XXPhase3: 1,
191 OpType.ESWAP: 1,
192 OpType.FSim: 2,
193}
195_tk_to_qasm_noparams = dict((item[1], item[0]) for item in NOPARAM_COMMANDS.items())
196_tk_to_qasm_noparams[OpType.CX] = "cx" # prefer "cx" to "CX"
197_tk_to_qasm_params = dict((item[1], item[0]) for item in PARAM_COMMANDS.items())
198_tk_to_qasm_params[OpType.U3] = "u3" # prefer "u3" to "U"
199_tk_to_qasm_params[OpType.Rz] = "rz" # prefer "rz" to "Rz"
200_tk_to_qasm_extra_noparams = dict(
201 (item[1], item[0]) for item in NOPARAM_EXTRA_COMMANDS.items()
202)
203_tk_to_qasm_extra_params = dict(
204 (item[1], item[0]) for item in PARAM_EXTRA_COMMANDS.items()
205)
207_classical_gatestr_map = {"AND": "&", "OR": "|", "XOR": "^"}
210_all_known_gates = (
211 set(NOPARAM_COMMANDS.keys())
212 .union(PARAM_COMMANDS.keys())
213 .union(PARAM_EXTRA_COMMANDS.keys())
214 .union(NOPARAM_EXTRA_COMMANDS.keys())
215)
216_all_string_maps = {
217 key: val.name
218 for key, val in chain(
219 PARAM_COMMANDS.items(),
220 NOPARAM_COMMANDS.items(),
221 PARAM_EXTRA_COMMANDS.items(),
222 NOPARAM_EXTRA_COMMANDS.items(),
223 )
224}
226unit_regex = re.compile(r"([a-z][a-zA-Z0-9_]*)\[([\d]+)\]")
227regname_regex = re.compile(r"^[a-z][a-zA-Z0-9_]*$")
230def _extract_reg(var: Token) -> tuple[str, int]:
231 match = unit_regex.match(var.value)
232 if match is None:
233 raise QASMParseError(
234 f"Invalid register definition '{var.value}'. Register definitions "
235 "must follow the pattern '<name> [<size in integer>]'. "
236 "For example, 'q [5]'. QASM register names must begin with a "
237 "lowercase letter and may only contain lowercase and uppercase "
238 "letters, numbers, and underscores."
239 )
240 return match.group(1), int(match.group(2))
243def _load_include_module(
244 header_name: str, flter: bool, decls_only: bool
245) -> dict[str, dict]:
246 try:
247 if decls_only:
248 include_def: dict[str, dict] = import_module(
249 f"pytket.qasm.includes._{header_name}_decls"
250 )._INCLUDE_DECLS
251 else:
252 include_def = import_module(
253 f"pytket.qasm.includes._{header_name}_defs"
254 )._INCLUDE_DEFS
255 except ModuleNotFoundError as e:
256 raise QASMParseError(
257 f"Header {header_name} is not known and cannot be loaded."
258 ) from e
259 return {
260 gate: include_def[gate]
261 for gate in include_def
262 if not flter or gate not in _all_known_gates
263 }
266def _bin_par_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]:
267 def f(self: "CircuitTransformer", vals: list[str]) -> str:
268 return f"({vals[0]} {op} {vals[1]})"
270 return f
273def _un_par_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]:
274 def f(self: "CircuitTransformer", vals: list[str]) -> str:
275 return f"({op}{vals[0]})"
277 return f
280def _un_call_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]:
281 def f(self: "CircuitTransformer", vals: list[str]) -> str:
282 return f"{op}({vals[0]})"
284 return f
287def _hashable_uid(arg: list) -> tuple[str, int]:
288 return arg[0], arg[1][0]
291Reg = NewType("Reg", str)
292CommandDict = dict[str, Any]
295@dataclass
296class ParsMap:
297 pars: Iterable[str]
299 def __iter__(self) -> Iterable[str]:
300 return self.pars
303class CircuitTransformer(Transformer):
304 def __init__(
305 self,
306 return_gate_dict: bool = False,
307 maxwidth: int = 32,
308 ) -> None:
309 super().__init__()
310 self.q_registers: dict[str, int] = {}
311 self.c_registers: dict[str, int] = {}
312 self.gate_dict: dict[str, dict] = {}
313 self.wasm: WasmFileHandler | None = None
314 self.include = ""
315 self.return_gate_dict = return_gate_dict
316 self.maxwidth = maxwidth
318 def _fresh_temp_bit(self) -> list:
319 if _TEMP_BIT_NAME in self.c_registers:
320 idx = self.c_registers[_TEMP_BIT_NAME]
321 else:
322 idx = 0
323 self.c_registers[_TEMP_BIT_NAME] = idx + 1
325 return [_TEMP_BIT_NAME, [idx]]
327 def _reset_context(self, reset_wasm: bool = True) -> None:
328 self.q_registers = {}
329 self.c_registers = {}
330 self.gate_dict = {}
331 self.include = ""
332 if reset_wasm:
333 self.wasm = None
335 def _get_reg(self, name: str) -> Reg:
336 return Reg(name)
338 def _get_uid(self, iarg: Token) -> list:
339 name, idx = _extract_reg(iarg)
340 return [name, [idx]]
342 def _get_arg(self, arg: Token) -> Arg:
343 if arg.type == "IARG":
344 return self._get_uid(arg)
345 return self._get_reg(arg.value)
347 def unroll_all_args(self, args: Iterable[Arg]) -> Iterator[list[Any]]:
348 for arg in args:
349 if isinstance(arg, str):
350 size = (
351 self.q_registers[arg]
352 if arg in self.q_registers
353 else self.c_registers[arg]
354 )
355 yield [[arg, [idx]] for idx in range(size)]
356 else:
357 yield [arg]
359 def margs(self, tree: Iterable[Token]) -> Iterator[Arg]:
360 return map(self._get_arg, tree)
362 def iargs(self, tree: Iterable[Token]) -> Iterator[list]:
363 return map(self._get_uid, tree)
365 def args(self, tree: Iterable[Token]) -> Iterator[list]:
366 return ([tok.value, [0]] for tok in tree)
368 def creg(self, tree: list[Token]) -> None:
369 name, size = _extract_reg(tree[0])
370 if size > self.maxwidth:
371 raise QASMUnsupportedError(
372 f"Circuit contains classical register {name} of size {size} > "
373 f"{self.maxwidth}: try setting the `maxwidth` parameter to a larger "
374 "value."
375 )
376 self.c_registers[Reg(name)] = size
378 def qreg(self, tree: list[Token]) -> None:
379 name, size = _extract_reg(tree[0])
380 self.q_registers[Reg(name)] = size
382 def meas(self, tree: list[Token]) -> Iterable[CommandDict]:
383 for args in zip(*self.unroll_all_args(self.margs(tree))):
384 yield {"args": list(args), "op": {"type": "Measure"}}
386 def barr(self, tree: list[Arg]) -> Iterable[CommandDict]:
387 args = [q for qs in self.unroll_all_args(tree[0]) for q in qs]
388 signature: list[str] = []
389 for arg in args:
390 if arg[0] in self.c_registers:
391 signature.append("C")
392 elif arg[0] in self.q_registers: 392 ↛ 395line 392 didn't jump to line 395 because the condition on line 392 was always true
393 signature.append("Q")
394 else:
395 raise QASMParseError(
396 "UnitID " + str(arg) + " in Barrier arguments is not declared."
397 )
398 yield {
399 "args": args,
400 "op": {"signature": signature, "type": "Barrier"},
401 }
403 def reset(self, tree: list[Token]) -> Iterable[CommandDict]:
404 for qb in next(self.unroll_all_args(self.margs(tree))):
405 yield {"args": [qb], "op": {"type": "Reset"}}
407 def pars(self, vals: Iterable[str]) -> ParsMap:
408 return ParsMap(map(str, vals))
410 def mixedcall(self, tree: list) -> Iterator[CommandDict]:
411 child_iter = iter(tree)
413 optoken = next(child_iter)
414 opstr = optoken.value
415 next_tree = next(child_iter)
416 try:
417 args = next(child_iter)
418 pars = cast(ParsMap, next_tree).pars
419 except StopIteration:
420 args = next_tree
421 pars = []
423 treat_as_barrier = [
424 "sleep",
425 "order2",
426 "order3",
427 "order4",
428 "order5",
429 "order6",
430 "order7",
431 "order8",
432 "order9",
433 "order10",
434 "order11",
435 "order12",
436 "order13",
437 "order14",
438 "order15",
439 "order16",
440 "order17",
441 "order18",
442 "order19",
443 "order20",
444 "group2",
445 "group3",
446 "group4",
447 "group5",
448 "group6",
449 "group7",
450 "group8",
451 "group9",
452 "group10",
453 "group11",
454 "group12",
455 "group13",
456 "group14",
457 "group15",
458 "group16",
459 "group17",
460 "group18",
461 "group19",
462 "group20",
463 ]
464 # other opaque gates, which are not handled as barrier
465 # ["RZZ", "Rxxyyzz", "Rxxyyzz_zphase", "cu", "cp", "rccx", "rc3x", "c3sqrtx"]
467 args = list(args)
469 if opstr in treat_as_barrier:
470 params = [f"{par}" for par in pars]
471 else:
472 params = [f"({par})/pi" for par in pars]
473 if opstr in self.gate_dict:
474 op: dict[str, Any] = {}
475 if opstr in treat_as_barrier:
476 op["type"] = "Barrier"
477 param_sorted = ",".join(params)
479 op["data"] = f"{opstr}({param_sorted})"
481 op["signature"] = [arg[0] for arg in args]
482 else:
483 gdef = self.gate_dict[opstr]
484 op["type"] = "CustomGate"
485 box = {
486 "type": "CustomGate",
487 "id": str(uuid.uuid4()),
488 "gate": gdef,
489 }
490 box["params"] = params
491 op["box"] = box
492 params = [] # to stop duplication in to op
493 else:
494 try:
495 optype = _all_string_maps[opstr]
496 except KeyError as e:
497 raise QASMParseError(
498 f"Cannot parse gate of type: {opstr}", optoken.line
499 ) from e
500 op = {"type": optype}
501 if params:
502 op["params"] = params
503 # Operations needing special handling:
504 if optype.startswith("Cn"):
505 # n-controlled rotations have variable signature
506 op["n_qb"] = len(args)
507 elif optype == "Barrier":
508 op["signature"] = ["Q"] * len(args)
510 for arg in zip(*self.unroll_all_args(args)):
511 yield {"args": list(arg), "op": op}
513 def gatecall(self, tree: list) -> Iterable[CommandDict]:
514 return self.mixedcall(tree)
516 def exp_args(self, tree: Iterable[Token]) -> Iterable[Reg]:
517 for arg in tree:
518 if arg.type == "ARG": 518 ↛ 521line 518 didn't jump to line 521 because the condition on line 518 was always true
519 yield self._get_reg(arg.value)
520 else:
521 raise QASMParseError(
522 "Non register arguments not supported for extern call.", arg.line
523 )
525 def _logic_exp(self, tree: list, opstr: str) -> LogicExp:
526 args, line = self._get_logic_args(tree)
527 openum: type[BitWiseOp] | type[RegWiseOp]
528 if opstr in _BITOPS and opstr not in _REGOPS: 528 ↛ 529line 528 didn't jump to line 529 because the condition on line 528 was never true
529 openum = BitWiseOp
530 elif (
531 opstr in _REGOPS
532 and opstr not in _BITOPS
533 or all(isinstance(arg, int) for arg in args)
534 ):
535 openum = RegWiseOp
536 elif all(isinstance(arg, (Bit, BitLogicExp, int)) for arg in args):
537 if all(arg in (0, 1) for arg in args if isinstance(arg, int)): 537 ↛ 540line 537 didn't jump to line 540 because the condition on line 537 was always true
538 openum = BitWiseOp
539 else:
540 raise QASMParseError(
541 "Bits can only be operated with (0, 1) literals."
542 f" Incomaptible arguments {args}",
543 line,
544 )
545 else:
546 openum = RegWiseOp
547 if openum is BitWiseOp and opstr in ("+", "-"):
548 op: BitWiseOp | RegWiseOp = BitWiseOp.XOR
549 else:
550 op = openum(opstr)
551 return create_logic_exp(op, args)
553 def _get_logic_args(
554 self, tree: Sequence[Token | LogicExp]
555 ) -> tuple[list[LogicExp | Bit | BitRegister | int], int | None]:
556 args: list[LogicExp | Bit | BitRegister | int] = []
557 line = None
558 for tok in tree:
559 if isinstance(tok, LogicExp):
560 args.append(tok)
561 elif isinstance(tok, Token): 561 ↛ 572line 561 didn't jump to line 572 because the condition on line 561 was always true
562 line = tok.line
563 if tok.type == "INT":
564 args.append(int(tok.value))
565 elif tok.type == "IARG":
566 args.append(Bit(*_extract_reg(tok)))
567 elif tok.type == "ARG": 567 ↛ 570line 567 didn't jump to line 570 because the condition on line 567 was always true
568 args.append(BitRegister(tok.value, self.c_registers[tok.value]))
569 else:
570 raise QASMParseError(f"Could not pass argument {tok}")
571 else:
572 raise QASMParseError(f"Could not pass argument {tok}")
573 return args, line
575 par_add = _bin_par_exp("+")
576 par_sub = _bin_par_exp("-")
577 par_mul = _bin_par_exp("*")
578 par_div = _bin_par_exp("/")
579 par_pow = _bin_par_exp("**")
581 par_neg = _un_par_exp("-")
583 sqrt = _un_call_exp("sqrt")
584 sin = _un_call_exp("sin")
585 cos = _un_call_exp("cos")
586 tan = _un_call_exp("tan")
587 ln = _un_call_exp("ln")
589 b_and = lambda self, tree: self._logic_exp(tree, "&")
590 b_not = lambda self, tree: self._logic_exp(tree, "~")
591 b_or = lambda self, tree: self._logic_exp(tree, "|")
592 xor = lambda self, tree: self._logic_exp(tree, "^")
593 lshift = lambda self, tree: self._logic_exp(tree, "<<")
594 rshift = lambda self, tree: self._logic_exp(tree, ">>")
595 add = lambda self, tree: self._logic_exp(tree, "+")
596 sub = lambda self, tree: self._logic_exp(tree, "-")
597 mul = lambda self, tree: self._logic_exp(tree, "*")
598 div = lambda self, tree: self._logic_exp(tree, "/")
599 ipow = lambda self, tree: self._logic_exp(tree, "**")
601 def neg(self, tree: list[Token | LogicExp]) -> RegNeg:
602 arg = self._get_logic_args(tree)[0][0]
603 assert isinstance(arg, (RegLogicExp, BitRegister, int))
604 return RegNeg(arg)
606 def cond(self, tree: list[Token]) -> PredicateExp:
607 op: BitWiseOp | RegWiseOp
608 arg: Bit | BitRegister
609 if tree[1].type == "IARG":
610 arg = Bit(*_extract_reg(tree[1]))
611 op = BitWiseOp(str(tree[2]))
612 else:
613 arg = BitRegister(tree[1].value, self.c_registers[tree[1].value])
614 op = RegWiseOp(str(tree[2]))
616 return create_predicate_exp(op, [arg, int(tree[3].value)])
618 def ifc(self, tree: Sequence) -> Iterable[CommandDict]:
619 condition = cast(PredicateExp, tree[0])
621 var, val = condition.args
622 condition_bits = []
624 if isinstance(var, Bit):
625 assert condition.op in (BitWiseOp.EQ, BitWiseOp.NEQ)
626 assert val in (0, 1)
627 condition_bits = [var.to_list()]
629 else:
630 assert isinstance(var, BitRegister)
631 reg_bits = next(self.unroll_all_args([var.name]))
632 if isinstance(condition, RegEq):
633 # special case for base qasm
634 condition_bits = reg_bits
635 else:
636 pred_val = cast(int, val)
637 minval = 0
638 maxval = (1 << self.maxwidth) - 1
639 if condition.op == RegWiseOp.LT:
640 maxval = pred_val - 1
641 elif condition.op == RegWiseOp.GT:
642 minval = pred_val + 1
643 if condition.op in (RegWiseOp.LEQ, RegWiseOp.EQ, RegWiseOp.NEQ):
644 maxval = pred_val
645 if condition.op in (RegWiseOp.GEQ, RegWiseOp.EQ, RegWiseOp.NEQ):
646 minval = pred_val
648 condition_bit = self._fresh_temp_bit()
649 yield {
650 "args": reg_bits + [condition_bit],
651 "op": {
652 "classical": {
653 "lower": minval,
654 "n_i": len(reg_bits),
655 "upper": maxval,
656 },
657 "type": "RangePredicate",
658 },
659 }
660 condition_bits = [condition_bit]
661 val = int(condition.op != RegWiseOp.NEQ)
663 for com in filter(lambda x: x is not None and x is not Discard, tree[1]):
664 com["args"] = condition_bits + com["args"]
665 com["op"] = {
666 "conditional": {
667 "op": com["op"],
668 "value": val,
669 "width": len(condition_bits),
670 },
671 "type": "Conditional",
672 }
674 yield com
676 def cop(self, tree: Sequence[Iterable[CommandDict]]) -> Iterable[CommandDict]:
677 return tree[0]
679 def _calc_exp_io(
680 self, exp: LogicExp, out_args: list
681 ) -> tuple[list[list], dict[str, Any]]:
682 all_inps: list[tuple[str, int]] = []
683 for inp in exp.all_inputs_ordered():
684 if isinstance(inp, Bit):
685 all_inps.append((inp.reg_name, inp.index[0]))
686 else:
687 assert isinstance(inp, BitRegister)
688 for bit in inp:
689 all_inps.append((bit.reg_name, bit.index[0]))
690 outs = (_hashable_uid(arg) for arg in out_args)
691 o = []
692 io = []
693 for out in outs:
694 if out in all_inps:
695 all_inps.remove(out)
696 io.append(out)
697 else:
698 o.append(out)
700 exp_args = list(
701 map(lambda x: [x[0], [x[1]]], chain.from_iterable((all_inps, io, o)))
702 )
703 numbers_dict = {
704 "n_i": len(all_inps),
705 "n_io": len(io),
706 "n_o": len(o),
707 }
708 return exp_args, numbers_dict
710 def _clexpr_dict(self, exp: LogicExp, out_args: list[list]) -> CommandDict:
711 # Convert the LogicExp to a serialization of a command containing the
712 # corresponding ClExprOp.
713 wexpr, args = wired_clexpr_from_logic_exp(
714 exp, [Bit.from_list(arg) for arg in out_args]
715 )
716 return {
717 "op": {
718 "type": "ClExpr",
719 "expr": wexpr.to_dict(),
720 },
721 "args": [arg.to_list() for arg in args],
722 }
724 def assign(self, tree: list) -> Iterable[CommandDict]:
725 child_iter = iter(tree)
726 out_args = list(next(child_iter))
727 args_uids = list(self.unroll_all_args(out_args))
729 exp_tree = next(child_iter)
731 exp: str | list | LogicExp | int = ""
732 line = None
733 if isinstance(exp_tree, Token):
734 if exp_tree.type == "INT":
735 exp = int(exp_tree.value)
736 elif exp_tree.type in ("ARG", "IARG"): 736 ↛ 738line 736 didn't jump to line 738 because the condition on line 736 was always true
737 exp = self._get_arg(exp_tree)
738 line = exp_tree.line
739 elif isinstance(exp_tree, Generator):
740 # assume to be extern (wasm) call
741 chained_uids = list(chain.from_iterable(args_uids))
742 com = next(exp_tree)
743 com["args"].pop() # remove the wasmstate from the args
744 com["args"] += chained_uids
745 com["args"].append(["_w", [0]])
746 com["op"]["wasm"]["n"] += len(chained_uids)
747 com["op"]["wasm"]["width_o_parameter"] = [
748 self.c_registers[reg] for reg in out_args
749 ]
751 yield com
752 return
753 else:
754 exp = exp_tree
756 assert len(out_args) == 1
757 out_arg = out_args[0]
758 args = args_uids[0]
759 if isinstance(out_arg, list):
760 if isinstance(exp, LogicExp):
761 yield self._clexpr_dict(exp, args)
762 elif isinstance(exp, (int, bool)):
763 assert exp in (0, 1, True, False)
764 yield {
765 "args": args,
766 "op": {"classical": {"values": [bool(exp)]}, "type": "SetBits"},
767 }
768 elif isinstance(exp, list): 768 ↛ 774line 768 didn't jump to line 774 because the condition on line 768 was always true
769 yield {
770 "args": [exp] + args,
771 "op": {"classical": {"n_i": 1}, "type": "CopyBits"},
772 }
773 else:
774 raise QASMParseError(f"Unexpected expression in assignment {exp}", line)
775 else:
776 reg = out_arg
777 if isinstance(exp, RegLogicExp):
778 yield self._clexpr_dict(exp, args)
779 elif isinstance(exp, BitLogicExp): 779 ↛ 780line 779 didn't jump to line 780 because the condition on line 779 was never true
780 yield self._clexpr_dict(exp, args[:1])
781 elif isinstance(exp, int):
782 yield {
783 "args": args,
784 "op": {
785 "classical": {
786 "values": int_to_bools(exp, self.c_registers[reg])
787 },
788 "type": "SetBits",
789 },
790 }
792 elif isinstance(exp, str): 792 ↛ 800line 792 didn't jump to line 800 because the condition on line 792 was always true
793 width = min(self.c_registers[exp], len(args))
794 yield {
795 "args": [[exp, [i]] for i in range(width)] + args[:width],
796 "op": {"classical": {"n_i": width}, "type": "CopyBits"},
797 }
799 else:
800 raise QASMParseError(f"Unexpected expression in assignment {exp}", line)
802 def extern(self, tree: list[Any]) -> Any:
803 # TODO parse extern defs
804 return Discard
806 def ccall(self, tree: list) -> Iterable[CommandDict]:
807 return self.cce_call(tree)
809 def cce_call(self, tree: list) -> Iterable[CommandDict]:
810 nam = tree[0].value
811 params = list(tree[1])
812 if self.wasm is None: 812 ↛ 813line 812 didn't jump to line 813 because the condition on line 812 was never true
813 raise QASMParseError(
814 "Cannot include extern calls without a wasm module specified.",
815 tree[0].line,
816 )
817 n_i_vec = [self.c_registers[reg] for reg in params]
819 wasm_args = list(chain.from_iterable(self.unroll_all_args(params)))
821 wasm_args.append(["_w", [0]])
823 yield {
824 "args": wasm_args,
825 "op": {
826 "type": "WASM",
827 "wasm": {
828 "func_name": nam,
829 "ww_n": 1,
830 "n": sum(n_i_vec),
831 "width_i_parameter": n_i_vec,
832 "width_o_parameter": [], # this will be set in the assign function
833 "wasm_file_uid": str(self.wasm),
834 },
835 },
836 }
838 def transform(self, tree: Tree) -> dict[str, Any]:
839 self._reset_context()
840 return cast(dict[str, Any], super().transform(tree))
842 def gdef(self, tree: list) -> None:
843 child_iter = iter(tree)
844 gate = next(child_iter).value
845 next_tree = next(child_iter)
846 symbols, args = [], []
847 if isinstance(next_tree, ParsMap):
848 symbols = list(next_tree.pars)
849 args = list(next(child_iter))
850 else:
851 args = list(next_tree)
853 symbol_map = {sym: sym * pi for sym in map(Symbol, symbols)}
854 rename_map = {Qubit.from_list(qb): Qubit("q", i) for i, qb in enumerate(args)}
856 new = CircuitTransformer(maxwidth=self.maxwidth)
857 circ_dict = new.prog(child_iter)
859 circ_dict["qubits"] = args
860 gate_circ = Circuit.from_dict(circ_dict)
862 # check to see whether gate definition was generated by pytket converter
863 # if true, add op as pytket Op
864 existing_op: bool = False
865 # NOPARAM_EXTRA_COMMANDS and PARAM_EXTRA_COMMANDS are
866 # gates that aren't in the standard qasm spec but in the standard TKET
867 # optypes
868 if gate in NOPARAM_EXTRA_COMMANDS:
869 qubit_args = [
870 Qubit(gate + "q" + str(index), 0) for index in list(range(len(args)))
871 ]
872 comparison_circ = _get_gate_circuit(
873 NOPARAM_EXTRA_COMMANDS[gate], qubit_args
874 )
875 if circuit_to_qasm_str(
876 comparison_circ, maxwidth=self.maxwidth
877 ) == circuit_to_qasm_str(gate_circ, maxwidth=self.maxwidth):
878 existing_op = True
879 elif gate in PARAM_EXTRA_COMMANDS:
880 optype = PARAM_EXTRA_COMMANDS[gate]
881 # we check this here, as _get_gate_circuit will find issue if it isn't true
882 # the later existing_op=all check will make sure it's the same circuit later
883 if len(symbols) != N_PARAMS_EXTRA_COMMANDS[optype]:
884 existing_op = False
885 else:
886 qubit_args = [
887 Qubit(gate + "q" + str(index), 0) for index in range(len(args))
888 ]
889 comparison_circ = _get_gate_circuit(
890 optype,
891 qubit_args,
892 [
893 Symbol("param" + str(index) + "/pi")
894 for index in range(len(symbols))
895 ],
896 )
897 # checks that each command has same string
898 existing_op = all(
899 str(g) == str(c)
900 for g, c in zip(
901 gate_circ.get_commands(), comparison_circ.get_commands()
902 )
903 )
904 if not existing_op:
905 gate_circ.symbol_substitution(symbol_map)
906 gate_circ.rename_units(cast(dict[UnitID, UnitID], rename_map))
907 self.gate_dict[gate] = {
908 "definition": gate_circ.to_dict(),
909 "args": symbols,
910 "name": gate,
911 }
913 opaq = gdef
915 def oqasm(self, tree: list) -> Any:
916 return Discard
918 def incl(self, tree: list[Token]) -> None:
919 self.include = str(tree[0].value).split(".")[0]
920 self.gate_dict.update(_load_include_module(self.include, True, False))
922 def prog(self, tree: Iterable) -> dict[str, Any]:
923 outdict: dict[str, Any] = {
924 "commands": list(
925 chain.from_iterable(
926 filter(lambda x: x is not None and x is not Discard, tree)
927 )
928 )
929 }
930 if self.return_gate_dict:
931 return self.gate_dict
932 outdict["qubits"] = [
933 [reg, [i]] for reg, size in self.q_registers.items() for i in range(size)
934 ]
935 outdict["bits"] = [
936 [reg, [i]] for reg, size in self.c_registers.items() for i in range(size)
937 ]
938 outdict["implicit_permutation"] = [[q, q] for q in outdict["qubits"]]
939 outdict["phase"] = "0.0"
940 self._reset_context()
941 return outdict
944def parser(maxwidth: int) -> Lark:
945 return Lark(
946 grammar,
947 start="prog",
948 debug=False,
949 parser="lalr",
950 cache=True,
951 transformer=CircuitTransformer(maxwidth=maxwidth),
952 )
955g_parser = None
956g_maxwidth = 32
959def set_parser(maxwidth: int) -> None:
960 global g_parser, g_maxwidth
961 if (g_parser is None) or (g_maxwidth != maxwidth): # type: ignore
962 g_parser = parser(maxwidth=maxwidth)
963 g_maxwidth = maxwidth
966def circuit_from_qasm(
967 input_file: Union[str, "os.PathLike[Any]"],
968 encoding: str = "utf-8",
969 maxwidth: int = 32,
970) -> Circuit:
971 """A method to generate a tket Circuit from a qasm file.
973 :param input_file: path to qasm file; filename must have ``.qasm`` extension
974 :param encoding: file encoding (default utf-8)
975 :param maxwidth: maximum allowed width of classical registers (default 32)
976 :return: pytket circuit
977 """
978 ext = os.path.splitext(input_file)[-1]
979 if ext != ".qasm": 979 ↛ 980line 979 didn't jump to line 980 because the condition on line 979 was never true
980 raise TypeError("Can only convert .qasm files")
981 with open(input_file, encoding=encoding) as f:
982 try:
983 circ = circuit_from_qasm_io(f, maxwidth=maxwidth)
984 except QASMParseError as e:
985 raise QASMParseError(e.msg, e.line, str(input_file))
986 return circ
989def circuit_from_qasm_str(qasm_str: str, maxwidth: int = 32) -> Circuit:
990 """A method to generate a tket Circuit from a qasm string.
992 :param qasm_str: qasm string
993 :param maxwidth: maximum allowed width of classical registers (default 32)
994 :return: pytket circuit
995 """
996 global g_parser
997 set_parser(maxwidth=maxwidth)
998 assert g_parser is not None
999 cast(CircuitTransformer, g_parser.options.transformer)._reset_context(
1000 reset_wasm=False
1001 )
1003 circ = Circuit.from_dict(g_parser.parse(qasm_str)) # type: ignore[arg-type]
1004 cpass = scratch_reg_resize_pass(maxwidth)
1005 cpass.apply(circ)
1006 return circ
1009def circuit_from_qasm_io(stream_in: TextIO, maxwidth: int = 32) -> Circuit:
1010 """A method to generate a tket Circuit from a qasm text stream"""
1011 return circuit_from_qasm_str(stream_in.read(), maxwidth=maxwidth)
1014def circuit_from_qasm_wasm(
1015 input_file: Union[str, "os.PathLike[Any]"],
1016 wasm_file: Union[str, "os.PathLike[Any]"],
1017 encoding: str = "utf-8",
1018 maxwidth: int = 32,
1019) -> Circuit:
1020 """A method to generate a tket Circuit from a qasm string and external WASM module.
1022 :param input_file: path to qasm file; filename must have ``.qasm`` extension
1023 :param wasm_file: path to WASM file containing functions used in qasm
1024 :param encoding: encoding of qasm file (default utf-8)
1025 :param maxwidth: maximum allowed width of classical registers (default 32)
1026 :return: pytket circuit
1027 """
1028 global g_parser
1029 wasm_module = WasmFileHandler(str(wasm_file))
1030 set_parser(maxwidth=maxwidth)
1031 assert g_parser is not None
1032 cast(CircuitTransformer, g_parser.options.transformer).wasm = wasm_module
1033 return circuit_from_qasm(input_file, encoding=encoding, maxwidth=maxwidth)
1036def circuit_to_qasm(
1037 circ: Circuit, output_file: str, header: str = "qelib1", maxwidth: int = 32
1038) -> None:
1039 """Convert a Circuit to QASM and write it to a file.
1041 Classical bits in the pytket circuit must be singly-indexed.
1043 Note that this will not account for implicit qubit permutations in the Circuit.
1045 :param circ: pytket circuit
1046 :param output_file: path to output qasm file
1047 :param header: qasm header (default "qelib1")
1048 :param maxwidth: maximum allowed width of classical registers (default 32)
1049 """
1050 with open(output_file, "w") as out:
1051 circuit_to_qasm_io(circ, out, header=header, maxwidth=maxwidth)
1054def _filtered_qasm_str(qasm: str) -> str:
1055 # remove any c registers starting with _TEMP_BIT_NAME
1056 # that are not being used somewhere else
1057 lines = qasm.split("\n")
1058 def_matcher = re.compile(rf"creg ({_TEMP_BIT_NAME}\_*\d*)\[\d+\]")
1059 arg_matcher = re.compile(rf"({_TEMP_BIT_NAME}\_*\d*)\[\d+\]")
1060 unused_regs = dict()
1061 for i, line in enumerate(lines):
1062 if reg := def_matcher.match(line):
1063 # Mark a reg temporarily as unused
1064 unused_regs[reg.group(1)] = i
1065 elif args := arg_matcher.findall(line):
1066 # If the line contains scratch bits that are used as arguments
1067 # mark these regs as used
1068 for arg in args:
1069 if arg in unused_regs:
1070 unused_regs.pop(arg)
1071 # remove unused reg defs
1072 redundant_lines = sorted(unused_regs.values(), reverse=True)
1073 for line_index in redundant_lines:
1074 del lines[line_index]
1075 return "\n".join(lines)
1078def is_empty_customgate(op: Op) -> bool:
1079 return op.type == OpType.CustomGate and op.get_circuit().n_gates == 0 # type: ignore
1082def check_can_convert_circuit(circ: Circuit, header: str, maxwidth: int) -> None:
1083 if any(
1084 circ.n_gates_of_type(typ)
1085 for typ in (
1086 OpType.RangePredicate,
1087 OpType.MultiBit,
1088 OpType.ExplicitPredicate,
1089 OpType.ExplicitModifier,
1090 OpType.SetBits,
1091 OpType.CopyBits,
1092 )
1093 ) and (not hqs_header(header)):
1094 raise QASMUnsupportedError(
1095 "Complex classical gates not supported with qelib1: try converting with "
1096 "`header=hqslib1`"
1097 )
1098 if any(bit.index[0] >= maxwidth for bit in circ.bits):
1099 raise QASMUnsupportedError(
1100 f"Circuit contains a classical register larger than {maxwidth}: try "
1101 "setting the `maxwidth` parameter to a higher value."
1102 )
1103 set_circ_register = set([creg.name for creg in circ.c_registers])
1104 for b in circ.bits:
1105 if b.reg_name not in set_circ_register:
1106 raise QASMUnsupportedError(
1107 f"Circuit contains an invalid classical register {b.reg_name}."
1108 )
1109 # Empty CustomGates should have been removed by DecomposeBoxes().
1110 for cmd in circ:
1111 assert not is_empty_customgate(cmd.op)
1112 if isinstance(cmd.op, Conditional):
1113 assert not is_empty_customgate(cmd.op.op)
1114 if not check_register_alignments(circ): 1114 ↛ 1115line 1114 didn't jump to line 1115 because the condition on line 1114 was never true
1115 raise QASMUnsupportedError(
1116 "Circuit contains classical expressions on registers whose arguments or "
1117 "outputs are not register-aligned."
1118 )
1121def circuit_to_qasm_str(
1122 circ: Circuit,
1123 header: str = "qelib1",
1124 include_gate_defs: set[str] | None = None,
1125 maxwidth: int = 32,
1126) -> str:
1127 """Convert a Circuit to QASM and return the string.
1129 Classical bits in the pytket circuit must be singly-indexed.
1131 Note that this will not account for implicit qubit permutations in the Circuit.
1133 :param circ: pytket circuit
1134 :param header: qasm header (default "qelib1")
1135 :param output_file: path to output qasm file
1136 :param include_gate_defs: optional set of gates to include
1137 :param maxwidth: maximum allowed width of classical registers (default 32)
1138 :return: qasm string
1139 """
1141 qasm_writer = QasmWriter(
1142 circ.qubits, circ.bits, header, include_gate_defs, maxwidth
1143 )
1144 circ1 = circ.copy()
1145 DecomposeBoxes().apply(circ1)
1146 check_can_convert_circuit(circ1, header, maxwidth)
1147 for command in circ1:
1148 assert isinstance(command, Command)
1149 qasm_writer.add_op(command.op, command.args)
1150 return qasm_writer.finalize()
1153TypeReg = TypeVar("TypeReg", BitRegister, QubitRegister)
1156def _retrieve_registers(
1157 units: list[UnitID], reg_type: type[TypeReg]
1158) -> dict[str, TypeReg]:
1159 if any(len(unit.index) != 1 for unit in units):
1160 raise NotImplementedError("OPENQASM registers must use a single index")
1161 maxunits = map(lambda x: max(x[1]), groupby(units, key=lambda un: un.reg_name))
1162 return {
1163 maxunit.reg_name: reg_type(maxunit.reg_name, maxunit.index[0] + 1)
1164 for maxunit in maxunits
1165 }
1168def _parse_range(minval: int, maxval: int, maxwidth: int) -> tuple[str, int]:
1169 if maxwidth > 64: 1169 ↛ 1170line 1169 didn't jump to line 1170 because the condition on line 1169 was never true
1170 raise NotImplementedError("Register width exceeds maximum of 64.")
1172 REGMAX = (1 << maxwidth) - 1
1174 if minval > REGMAX: 1174 ↛ 1175line 1174 didn't jump to line 1175 because the condition on line 1174 was never true
1175 raise NotImplementedError("Range's lower bound exceeds register capacity.")
1176 if minval > maxval: 1176 ↛ 1177line 1176 didn't jump to line 1177 because the condition on line 1176 was never true
1177 raise NotImplementedError("Range's lower bound exceeds upper bound.")
1178 maxval = min(maxval, REGMAX)
1180 if minval == maxval:
1181 return ("==", minval)
1182 if minval == 0:
1183 return ("<=", maxval)
1184 if maxval == REGMAX: 1184 ↛ 1186line 1184 didn't jump to line 1186 because the condition on line 1184 was always true
1185 return (">=", minval)
1186 raise NotImplementedError("Range can only be bounded on one side.")
1189def _negate_comparator(comparator: str) -> str:
1190 if comparator == "==":
1191 return "!="
1192 if comparator == "!=": 1192 ↛ 1193line 1192 didn't jump to line 1193 because the condition on line 1192 was never true
1193 return "=="
1194 if comparator == "<=":
1195 return ">"
1196 if comparator == ">": 1196 ↛ 1197line 1196 didn't jump to line 1197 because the condition on line 1196 was never true
1197 return "<="
1198 if comparator == ">=": 1198 ↛ 1200line 1198 didn't jump to line 1200 because the condition on line 1198 was always true
1199 return "<"
1200 assert comparator == "<"
1201 return ">="
1204def _get_optype_and_params(op: Op) -> tuple[OpType, list[float | Expr] | None]:
1205 optype = op.type
1206 params = (
1207 op.params
1208 if (optype in _tk_to_qasm_params) or (optype in _tk_to_qasm_extra_params)
1209 else None
1210 )
1211 if optype == OpType.TK1:
1212 # convert to U3
1213 optype = OpType.U3
1214 params = [op.params[1], op.params[0] - 0.5, op.params[2] + 0.5]
1215 elif optype == OpType.CustomGate: 1215 ↛ 1216line 1215 didn't jump to line 1216 because the condition on line 1215 was never true
1216 params = op.params
1217 return optype, params
1220def _get_gate_circuit(
1221 optype: OpType, qubits: list[Qubit], symbols: list[Symbol] | None = None
1222) -> Circuit:
1223 # create Circuit for constructing qasm from
1224 unitids = cast(list[UnitID], qubits)
1225 gate_circ = Circuit()
1226 for q in qubits:
1227 gate_circ.add_qubit(q)
1228 if symbols:
1229 exprs = [symbol.as_expr() for symbol in symbols]
1230 gate_circ.add_gate(optype, exprs, unitids)
1231 else:
1232 gate_circ.add_gate(optype, unitids)
1233 AutoRebase({OpType.CX, OpType.U3}).apply(gate_circ)
1234 RemoveRedundancies().apply(gate_circ)
1236 return gate_circ
1239def hqs_header(header: str) -> bool:
1240 return header in ["hqslib1", "hqslib1_dev"]
1243@dataclass
1244class ConditionString:
1245 variable: str # variable, e.g. "c[1]"
1246 comparator: str # comparator, e.g. "=="
1247 value: int # value, e.g. "1"
1250class LabelledStringList:
1251 """
1252 Wrapper class for an ordered sequence of strings, where each string has a unique
1253 label, returned when the string is added, and a string may be removed from the
1254 sequence given its label. There is a method to retrieve the concatenation of all
1255 strings in order. The conditions (e.g. "if(c[0]==1)") for some strings are stored
1256 separately in `conditions`. These conditions will be converted to text when
1257 retrieving the full string.
1258 """
1260 def __init__(self) -> None:
1261 self.strings: OrderedDict[int, str] = OrderedDict()
1262 self.conditions: dict[int, ConditionString] = dict()
1263 self.label = 0
1265 def add_string(self, string: str) -> int:
1266 label = self.label
1267 self.strings[label] = string
1268 self.label += 1
1269 return label
1271 def get_string(self, label: int) -> str | None:
1272 return self.strings.get(label, None)
1274 def del_string(self, label: int) -> None:
1275 self.strings.pop(label, None)
1277 def get_full_string(self) -> str:
1278 strings = []
1279 for l, s in self.strings.items():
1280 condition = self.conditions.get(l)
1281 if condition is not None:
1282 strings.append(
1283 f"if({condition.variable}{condition.comparator}{condition.value}) "
1284 + s
1285 )
1286 else:
1287 strings.append(s)
1288 return "".join(strings)
1291def make_params_str(params: list[float | Expr] | None) -> str:
1292 s = ""
1293 if params is not None: 1293 ↛ 1312line 1293 didn't jump to line 1312 because the condition on line 1293 was always true
1294 n_params = len(params)
1295 s += "("
1296 for i in range(n_params):
1297 reduced = True
1298 try:
1299 p: float | Expr = float(params[i])
1300 except TypeError:
1301 reduced = False
1302 p = params[i]
1303 if i < n_params - 1:
1304 if reduced:
1305 s += f"{p}*pi,"
1306 else:
1307 s += f"({p})*pi,"
1308 elif reduced:
1309 s += f"{p}*pi)"
1310 else:
1311 s += f"({p})*pi)"
1312 s += " "
1313 return s
1316def make_args_str(args: Sequence[UnitID]) -> str:
1317 s = ""
1318 for i in range(len(args)):
1319 s += f"{args[i]}"
1320 if i < len(args) - 1:
1321 s += ","
1322 else:
1323 s += ";\n"
1324 return s
1327@dataclass
1328class ScratchPredicate:
1329 variable: str # variable, e.g. "c[1]"
1330 comparator: str # comparator, e.g. "=="
1331 value: int # value, e.g. "1"
1332 dest: str # destination bit, e.g. "tk_SCRATCH_BIT[0]"
1335def _vars_overlap(v: str, w: str) -> bool:
1336 """check if two variables have overlapping bits"""
1337 v_split = v.split("[")
1338 w_split = w.split("[")
1339 if v_split[0] != w_split[0]:
1340 # different registers
1341 return False
1342 # e.g. (a[1], a), (a, a[1]), (a[1], a[1]), (a, a)
1343 return len(v_split) != len(w_split) or v == w
1346def _var_appears(v: str, s: str) -> bool:
1347 """check if variable v appears in string s"""
1348 v_split = v.split("[")
1349 if len(v_split) == 1: 1349 ↛ 1352line 1349 didn't jump to line 1352 because the condition on line 1349 was never true
1350 # check if v appears in s and is not surrounded by word characters
1351 # e.g. a = a & b or a = a[1] & b[1]
1352 return bool(re.search(r"(?<!\w)" + re.escape(v) + r"(?![\w])", s))
1353 if re.search(r"(?<!\w)" + re.escape(v), s): 1353 ↛ 1356line 1353 didn't jump to line 1356 because the condition on line 1353 was never true
1354 # check if v appears in s and is not proceeded by word characters
1355 # e.g. a[1] = a[1]
1356 return True
1357 # check the register of v appears in s
1358 # e.g. a[1] = a & b
1359 return bool(re.search(r"(?<!\w)" + re.escape(v_split[0]) + r"(?![\[\w])", s))
1362class QasmWriter:
1363 """
1364 Helper class for converting a sequence of TKET Commands to QASM, and retrieving the
1365 final QASM string afterwards.
1366 """
1368 def __init__(
1369 self,
1370 qubits: list[Qubit],
1371 bits: list[Bit],
1372 header: str = "qelib1",
1373 include_gate_defs: set[str] | None = None,
1374 maxwidth: int = 32,
1375 ):
1376 self.header = header
1377 self.maxwidth = maxwidth
1378 self.added_gate_definitions: set[str] = set()
1379 self.include_module_gates = {"measure", "reset", "barrier"}
1380 self.include_module_gates.update(
1381 _load_include_module(header, False, True).keys()
1382 )
1383 self.prefix = ""
1384 self.gatedefs = ""
1385 self.strings = LabelledStringList()
1387 # Record of `RangePredicate` operations that set a "scratch" bit to 0 or 1
1388 # depending on the value of the predicate. This map is consulted when we
1389 # encounter a `Conditional` operation to see if the condition bit is one of
1390 # these scratch bits, which we can then replace with the original.
1391 self.range_preds: dict[int, ScratchPredicate] = dict()
1393 if include_gate_defs is None:
1394 self.include_gate_defs = self.include_module_gates
1395 self.include_gate_defs.update(NOPARAM_EXTRA_COMMANDS.keys())
1396 self.include_gate_defs.update(PARAM_EXTRA_COMMANDS.keys())
1397 self.prefix = f'OPENQASM 2.0;\ninclude "{header}.inc";\n\n'
1398 self.qregs = _retrieve_registers(cast(list[UnitID], qubits), QubitRegister)
1399 self.cregs = _retrieve_registers(cast(list[UnitID], bits), BitRegister)
1400 for reg in self.qregs.values():
1401 if regname_regex.match(reg.name) is None:
1402 raise QASMUnsupportedError(
1403 f"Invalid register name '{reg.name}'. QASM register names must "
1404 "begin with a lowercase letter and may only contain lowercase "
1405 "and uppercase letters, numbers, and underscores. "
1406 "Try renaming the register with `rename_units` first."
1407 )
1408 for bit_reg in self.cregs.values():
1409 if regname_regex.match(bit_reg.name) is None: 1409 ↛ 1410line 1409 didn't jump to line 1410 because the condition on line 1409 was never true
1410 raise QASMUnsupportedError(
1411 f"Invalid register name '{bit_reg.name}'. QASM register names "
1412 "must begin with a lowercase letter and may only contain "
1413 "lowercase and uppercase letters, numbers, and underscores. "
1414 "Try renaming the register with `rename_units` first."
1415 )
1416 else:
1417 # gate definition, no header necessary for file
1418 self.include_gate_defs = include_gate_defs
1419 self.cregs = {}
1420 self.qregs = {}
1422 self.cregs_as_bitseqs = set(tuple(creg) for creg in self.cregs.values())
1424 # for holding condition values when writing Conditional blocks
1425 # the size changes when adding and removing scratch bits
1426 self.scratch_reg = BitRegister(
1427 next(
1428 f"{_TEMP_BIT_REG_BASE}_{i}"
1429 for i in itertools.count()
1430 if f"{_TEMP_BIT_REG_BASE}_{i}" not in self.qregs
1431 ),
1432 0,
1433 )
1434 # if a string writes to some classical variables, the string label and
1435 # the affected variables will be recorded.
1436 self.variable_writes: dict[int, list[str]] = dict()
1438 def fresh_scratch_bit(self) -> Bit:
1439 self.scratch_reg = BitRegister(self.scratch_reg.name, self.scratch_reg.size + 1)
1440 return Bit(self.scratch_reg.name, self.scratch_reg.size - 1)
1442 def remove_last_scratch_bit(self) -> None:
1443 assert self.scratch_reg.size > 0
1444 self.scratch_reg = BitRegister(self.scratch_reg.name, self.scratch_reg.size - 1)
1446 def write_params(self, params: list[float | Expr] | None) -> None:
1447 params_str = make_params_str(params)
1448 self.strings.add_string(params_str)
1450 def write_args(self, args: Sequence[UnitID]) -> None:
1451 args_str = make_args_str(args)
1452 self.strings.add_string(args_str)
1454 def make_gate_definition(
1455 self,
1456 n_qubits: int,
1457 opstr: str,
1458 optype: OpType,
1459 n_params: int | None = None,
1460 ) -> str:
1461 s = "gate " + opstr + " "
1462 symbols: list[Symbol] | None = None
1463 if n_params is not None:
1464 # need to add parameters to gate definition
1465 s += "("
1466 symbols = [
1467 Symbol("param" + str(index) + "/pi") for index in range(n_params)
1468 ]
1469 symbols_header = [Symbol("param" + str(index)) for index in range(n_params)]
1470 for symbol in symbols_header[:-1]:
1471 s += symbol.name + ", "
1472 s += symbols_header[-1].name + ") "
1474 # add qubits to gate definition
1475 qubit_args = [
1476 Qubit(opstr + "q" + str(index)) for index in list(range(n_qubits))
1477 ]
1478 for qb in qubit_args[:-1]:
1479 s += str(qb) + ","
1480 s += str(qubit_args[-1]) + " {\n"
1481 # get rebased circuit for constructing qasm
1482 gate_circ = _get_gate_circuit(optype, qubit_args, symbols)
1483 # write circuit to qasm
1484 s += circuit_to_qasm_str(
1485 gate_circ, self.header, self.include_gate_defs, self.maxwidth
1486 )
1487 s += "}\n"
1488 return s
1490 def mark_as_written(self, label: int, written_variable: str) -> None:
1491 if label in self.variable_writes: 1491 ↛ 1492line 1491 didn't jump to line 1492 because the condition on line 1491 was never true
1492 self.variable_writes[label].append(written_variable)
1493 else:
1494 self.variable_writes[label] = [written_variable]
1496 def check_range_predicate(self, op: RangePredicateOp, args: list[Bit]) -> None:
1497 if (not hqs_header(self.header)) and op.lower != op.upper: 1497 ↛ 1498line 1497 didn't jump to line 1498 because the condition on line 1497 was never true
1498 raise QASMUnsupportedError(
1499 "OpenQASM conditions must be on a register's fixed value."
1500 )
1501 variable = args[0].reg_name
1502 assert isinstance(variable, str)
1503 if op.n_inputs != self.cregs[variable].size:
1504 raise QASMUnsupportedError(
1505 "RangePredicate conditions must be an entire classical register"
1506 )
1507 if args[:-1] != self.cregs[variable].to_list(): 1507 ↛ 1508line 1507 didn't jump to line 1508 because the condition on line 1507 was never true
1508 raise QASMUnsupportedError(
1509 "RangePredicate conditions must be a single classical register"
1510 )
1512 def add_range_predicate(self, op: RangePredicateOp, args: list[Bit]) -> None:
1513 self.check_range_predicate(op, args)
1514 comparator, value = _parse_range(op.lower, op.upper, self.maxwidth)
1515 variable = args[0].reg_name
1516 dest_bit = str(args[-1])
1517 label = self.strings.add_string(
1518 "".join(
1519 [
1520 f"if({variable}{comparator}{value}) " + f"{dest_bit} = 1;\n",
1521 f"if({variable}{_negate_comparator(comparator)}{value}) "
1522 f"{dest_bit} = 0;\n",
1523 ]
1524 )
1525 )
1526 # Record this operation.
1527 # Later if we find a conditional based on dest_bit, we can replace dest_bit with
1528 # (variable, comparator, value), provided that variable hasn't been written to
1529 # in the mean time. (So we must watch for that, and remove the record from the
1530 # list if it is.)
1531 # Note that we only perform such rewrites for internal scratch bits.
1532 if dest_bit.startswith(_TEMP_BIT_NAME):
1533 self.range_preds[label] = ScratchPredicate(
1534 variable, comparator, value, dest_bit
1535 )
1537 def replace_condition(self, pred_label: int) -> bool:
1538 """Given the label of a predicate p=(var, comp, value, dest, label)
1539 we scan the lines after p:
1540 1.if dest is the condition of a conditional line we replace dest with
1541 the predicate and do 2 for the inner command.
1542 2.if either the variable or the dest gets written, we stop.
1543 returns true if a replacement is made.
1544 """
1545 assert pred_label in self.range_preds
1546 success = False
1547 pred = self.range_preds[pred_label]
1548 line_labels = []
1549 for label in range(pred_label + 1, self.strings.label):
1550 string = self.strings.get_string(label)
1551 if string is None:
1552 continue
1553 line_labels.append(label)
1554 if "\n" not in string:
1555 continue
1556 written_variables: list[str] = []
1557 # (label, condition)
1558 conditions: list[tuple[int, ConditionString]] = []
1559 for l in line_labels:
1560 written_variables.extend(self.variable_writes.get(l, []))
1561 cond = self.strings.conditions.get(l)
1562 if cond:
1563 conditions.append((l, cond))
1564 if len(conditions) == 1 and pred.dest == conditions[0][1].variable:
1565 # if the condition is dest, replace the condition with pred
1566 success = True
1567 if conditions[0][1].value == 1:
1568 self.strings.conditions[conditions[0][0]] = ConditionString(
1569 pred.variable, pred.comparator, pred.value
1570 )
1571 else:
1572 assert conditions[0][1].value == 0
1573 self.strings.conditions[conditions[0][0]] = ConditionString(
1574 pred.variable,
1575 _negate_comparator(pred.comparator),
1576 pred.value,
1577 )
1578 if any(_vars_overlap(pred.dest, v) for v in written_variables) or any(
1579 _vars_overlap(pred.variable, v) for v in written_variables
1580 ):
1581 return success
1582 line_labels.clear()
1583 conditions.clear()
1584 written_variables.clear()
1585 return success
1587 def remove_unused_predicate(self, pred_label: int) -> bool:
1588 """Given the label of a predicate p=(var, comp, value, dest, label),
1589 we remove p if dest never appears after p."""
1590 assert pred_label in self.range_preds
1591 pred = self.range_preds[pred_label]
1592 for label in range(pred_label + 1, self.strings.label):
1593 string = self.strings.get_string(label)
1594 if string is None:
1595 continue
1596 if (
1597 _var_appears(pred.dest, string)
1598 or label in self.strings.conditions
1599 and _vars_overlap(pred.dest, self.strings.conditions[label].variable)
1600 ):
1601 return False
1602 self.range_preds.pop(pred_label)
1603 self.strings.del_string(pred_label)
1604 return True
1606 def add_conditional(self, op: Conditional, args: Sequence[UnitID]) -> None:
1607 control_bits = args[: op.width]
1608 if op.width == 1 and hqs_header(self.header):
1609 variable = str(control_bits[0])
1610 else:
1611 variable = control_bits[0].reg_name
1612 if (
1613 hqs_header(self.header)
1614 and control_bits != self.cregs[variable].to_list()
1615 ):
1616 raise QASMUnsupportedError(
1617 "hqslib1 QASM conditions must be an entire classical "
1618 "register or a single bit"
1619 )
1620 if not hqs_header(self.header):
1621 if op.width != self.cregs[variable].size:
1622 raise QASMUnsupportedError(
1623 "OpenQASM conditions must be an entire classical register"
1624 )
1625 if control_bits != self.cregs[variable].to_list():
1626 raise QASMUnsupportedError(
1627 "OpenQASM conditions must be a single classical register"
1628 )
1629 if op.op.type == OpType.Phase:
1630 # Conditional phase is ignored.
1631 return
1632 if op.op.type == OpType.RangePredicate:
1633 # Special handling for nested ifs
1634 # if condition
1635 # if pred dest = 1
1636 # if not pred dest = 0
1637 # can be written as
1638 # if condition s0 = 1
1639 # if pred s1 = 1
1640 # s2 = s0 & s1
1641 # s3 = s0 & ~s1
1642 # if s2 dest = 1
1643 # if s3 dest = 0
1644 # where s0, s1, s2, and s3 are scratch bits
1645 s0 = self.fresh_scratch_bit()
1646 l = self.strings.add_string(f"{s0} = 1;\n")
1647 # we store the condition in self.strings.conditions
1648 # as it can be later replaced by `replace_condition`
1649 # if possible
1650 self.strings.conditions[l] = ConditionString(variable, "==", op.value)
1651 # output the RangePredicate to s1
1652 s1 = self.fresh_scratch_bit()
1653 assert isinstance(op.op, RangePredicateOp)
1654 self.check_range_predicate(op.op, cast(list[Bit], args[op.width :]))
1655 pred_comparator, pred_value = _parse_range(
1656 op.op.lower, op.op.upper, self.maxwidth
1657 )
1658 pred_variable = args[op.width :][0].reg_name
1659 self.strings.add_string(
1660 f"if({pred_variable}{pred_comparator}{pred_value}) {s1} = 1;\n"
1661 )
1662 s2 = self.fresh_scratch_bit()
1663 self.strings.add_string(f"{s2} = {s0} & {s1};\n")
1664 s3 = self.fresh_scratch_bit()
1665 self.strings.add_string(f"{s3} = {s0} & (~ {s1});\n")
1666 self.strings.add_string(f"if({s2}==1) {args[-1]} = 1;\n")
1667 self.strings.add_string(f"if({s3}==1) {args[-1]} = 0;\n")
1668 return
1669 # we assign the condition to a scratch bit, which we will later remove
1670 # if the condition variable is unchanged.
1671 scratch_bit = self.fresh_scratch_bit()
1672 pred_label = self.strings.add_string(
1673 f"if({variable}=={op.value}) " + f"{scratch_bit} = 1;\n"
1674 )
1675 self.range_preds[pred_label] = ScratchPredicate(
1676 variable, "==", op.value, str(scratch_bit)
1677 )
1678 # we will later add condition to all lines starting from next_label
1679 next_label = self.strings.label
1680 self.add_op(op.op, args[op.width :])
1681 # add conditions to the lines after the predicate
1682 is_new_line = True
1683 for label in range(next_label, self.strings.label):
1684 string = self.strings.get_string(label)
1685 assert string is not None
1686 if is_new_line and string != "\n":
1687 self.strings.conditions[label] = ConditionString(
1688 str(scratch_bit), "==", 1
1689 )
1690 is_new_line = "\n" in string
1691 if self.replace_condition(pred_label) and self.remove_unused_predicate(
1692 pred_label
1693 ):
1694 # remove the unused scratch bit
1695 self.remove_last_scratch_bit()
1697 def add_set_bits(self, op: SetBitsOp, args: list[Bit]) -> None:
1698 creg_name = args[0].reg_name
1699 bits, vals = zip(*sorted(zip(args, op.values)))
1700 # check if whole register can be set at once
1701 if bits == tuple(self.cregs[creg_name].to_list()):
1702 value = int("".join(map(str, map(int, vals[::-1]))), 2)
1703 label = self.strings.add_string(f"{creg_name} = {value};\n")
1704 self.mark_as_written(label, f"{creg_name}")
1705 else:
1706 for bit, value in zip(bits, vals):
1707 label = self.strings.add_string(f"{bit} = {int(value)};\n")
1708 self.mark_as_written(label, f"{bit}")
1710 def add_copy_bits(self, op: CopyBitsOp, args: list[Bit]) -> None:
1711 l_args = args[op.n_inputs :]
1712 r_args = args[: op.n_inputs]
1713 l_name = l_args[0].reg_name
1714 r_name = r_args[0].reg_name
1715 # check if whole register can be set at once
1716 if (
1717 l_args == self.cregs[l_name].to_list()
1718 and r_args == self.cregs[r_name].to_list()
1719 ):
1720 label = self.strings.add_string(f"{l_name} = {r_name};\n")
1721 self.mark_as_written(label, f"{l_name}")
1722 else:
1723 for bit_l, bit_r in zip(l_args, r_args):
1724 label = self.strings.add_string(f"{bit_l} = {bit_r};\n")
1725 self.mark_as_written(label, f"{bit_l}")
1727 def add_multi_bit(self, op: MultiBitOp, args: list[Bit]) -> None:
1728 basic_op = op.basic_op
1729 basic_n = basic_op.n_inputs + basic_op.n_outputs + basic_op.n_input_outputs
1730 n_args = len(args)
1731 assert n_args % basic_n == 0
1732 arity = n_args // basic_n
1734 # If the operation is register-aligned we can write it more succinctly.
1735 poss_regs = [
1736 tuple(args[basic_n * i + j] for i in range(arity)) for j in range(basic_n)
1737 ]
1738 if all(poss_reg in self.cregs_as_bitseqs for poss_reg in poss_regs):
1739 # The operation is register-aligned.
1740 self.add_op(basic_op, [poss_regs[j][0].reg_name for j in range(basic_n)]) # type: ignore
1741 else:
1742 # The operation is not register-aligned.
1743 for i in range(arity):
1744 basic_args = args[basic_n * i : basic_n * (i + 1)]
1745 self.add_op(basic_op, basic_args)
1747 def add_explicit_op(self, op: Op, args: list[Bit]) -> None:
1748 # &, ^ and | gates
1749 opstr = str(op)
1750 if opstr not in _classical_gatestr_map: 1750 ↛ 1751line 1750 didn't jump to line 1751 because the condition on line 1750 was never true
1751 raise QASMUnsupportedError(f"Classical gate {opstr} not supported.")
1752 label = self.strings.add_string(
1753 f"{args[-1]} = {args[0]} {_classical_gatestr_map[opstr]} {args[1]};\n"
1754 )
1755 self.mark_as_written(label, f"{args[-1]}")
1757 def add_wired_clexpr(self, op: ClExprOp, args: list[Bit]) -> None:
1758 wexpr: WiredClExpr = op.expr
1759 # 1. Determine the mappings from bit variables to bits and from register
1760 # variables to registers.
1761 expr: ClExpr = wexpr.expr
1762 bit_posn: dict[int, int] = wexpr.bit_posn
1763 reg_posn: dict[int, list[int]] = wexpr.reg_posn
1764 output_posn: list[int] = wexpr.output_posn
1765 input_bits: dict[int, Bit] = {i: args[j] for i, j in bit_posn.items()}
1766 input_regs: dict[int, BitRegister] = {}
1767 all_cregs = set(self.cregs.values())
1768 for i, posns in reg_posn.items():
1769 reg_args = [args[j] for j in posns]
1770 for creg in all_cregs: 1770 ↛ 1775line 1770 didn't jump to line 1775 because the loop on line 1770 didn't complete
1771 if creg.to_list() == reg_args:
1772 input_regs[i] = creg
1773 break
1774 else:
1775 assert (
1776 not f"ClExprOp ({wexpr}) contains a register variable (r{i}) that "
1777 "is not wired to any BitRegister in the circuit."
1778 )
1779 # 2. Write the left-hand side of the assignment.
1780 output_repr: str | None = None
1781 output_args: list[Bit] = [args[j] for j in output_posn]
1782 n_output_args = len(output_args)
1783 expect_reg_output = has_reg_output(expr.op)
1784 if n_output_args == 0: 1784 ↛ 1785line 1784 didn't jump to line 1785 because the condition on line 1784 was never true
1785 raise QASMUnsupportedError("Expression has no output.")
1786 if n_output_args == 1:
1787 output_arg = output_args[0]
1788 output_repr = output_arg.reg_name if expect_reg_output else str(output_arg)
1789 else:
1790 if not expect_reg_output: 1790 ↛ 1791line 1790 didn't jump to line 1791 because the condition on line 1790 was never true
1791 raise QASMUnsupportedError("Unexpected output for operation.")
1792 for creg in all_cregs: 1792 ↛ 1796line 1792 didn't jump to line 1796 because the loop on line 1792 didn't complete
1793 if creg.to_list() == output_args:
1794 output_repr = creg.name
1795 break
1796 assert output_repr is not None
1797 self.strings.add_string(f"{output_repr} = ")
1798 # 3. Write the right-hand side of the assignment.
1799 self.strings.add_string(
1800 expr.as_qasm(input_bits=input_bits, input_regs=input_regs)
1801 )
1802 self.strings.add_string(";\n")
1804 def add_wasm(self, op: WASMOp, args: list[Bit]) -> None:
1805 inputs: list[str] = []
1806 outputs: list[str] = []
1807 for reglist, sizes in [(inputs, op.input_widths), (outputs, op.output_widths)]:
1808 for in_width in sizes:
1809 bits = args[:in_width]
1810 args = args[in_width:]
1811 regname = bits[0].reg_name
1812 if bits != list(self.cregs[regname]): 1812 ↛ 1813line 1812 didn't jump to line 1813 because the condition on line 1812 was never true
1813 QASMUnsupportedError("WASM ops must act on entire registers.")
1814 reglist.append(regname)
1815 if outputs:
1816 label = self.strings.add_string(f"{', '.join(outputs)} = ")
1817 self.strings.add_string(f"{op.func_name}({', '.join(inputs)});\n")
1818 for variable in outputs:
1819 self.mark_as_written(label, variable)
1821 def add_measure(self, args: Sequence[UnitID]) -> None:
1822 label = self.strings.add_string(f"measure {args[0]} -> {args[1]};\n")
1823 self.mark_as_written(label, f"{args[1]}")
1825 def add_zzphase(self, param: float | Expr, args: Sequence[UnitID]) -> None:
1826 # as op.params returns reduced parameters, we can assume
1827 # that 0 <= param < 4
1828 if param > 1:
1829 # first get in to 0 <= param < 2 range
1830 param = Decimal(str(param)) % Decimal("2")
1831 # then flip 1 <= param < 2 range into
1832 # -1 <= param < 0
1833 if param > 1:
1834 param = -2 + param
1835 self.strings.add_string("RZZ")
1836 self.write_params([param])
1837 self.write_args(args)
1839 def add_cnx(self, args: Sequence[UnitID]) -> None:
1840 n_ctrls = len(args) - 1
1841 assert n_ctrls >= 0
1842 match n_ctrls:
1843 case 0: 1843 ↛ 1844line 1843 didn't jump to line 1844 because the pattern on line 1843 never matched
1844 self.strings.add_string("x")
1845 case 1: 1845 ↛ 1846line 1845 didn't jump to line 1846 because the pattern on line 1845 never matched
1846 self.strings.add_string("cx")
1847 case 2: 1847 ↛ 1848line 1847 didn't jump to line 1848 because the pattern on line 1847 never matched
1848 self.strings.add_string("ccx")
1849 case 3:
1850 self.strings.add_string("c3x")
1851 case 4: 1851 ↛ 1853line 1851 didn't jump to line 1853 because the pattern on line 1851 always matched
1852 self.strings.add_string("c4x")
1853 case _:
1854 raise QASMUnsupportedError("CnX with n > 4 not supported in QASM")
1855 self.strings.add_string(" ")
1856 self.write_args(args)
1858 def add_data(self, op: BarrierOp, args: Sequence[UnitID]) -> None:
1859 if op.data == "": 1859 ↛ 1860line 1859 didn't jump to line 1860 because the condition on line 1859 was never true
1860 opstr = _tk_to_qasm_noparams[OpType.Barrier]
1861 else:
1862 opstr = op.data
1863 self.strings.add_string(opstr)
1864 self.strings.add_string(" ")
1865 self.write_args(args)
1867 def add_gate_noparams(self, op: Op, args: Sequence[UnitID]) -> None:
1868 self.strings.add_string(_tk_to_qasm_noparams[op.type])
1869 self.strings.add_string(" ")
1870 self.write_args(args)
1872 def add_gate_params(self, op: Op, args: Sequence[UnitID]) -> None:
1873 optype, params = _get_optype_and_params(op)
1874 self.strings.add_string(_tk_to_qasm_params[optype])
1875 self.write_params(params)
1876 self.write_args(args)
1878 def add_extra_noparams(self, op: Op, args: Sequence[UnitID]) -> tuple[str, str]:
1879 optype = op.type
1880 opstr = _tk_to_qasm_extra_noparams[optype]
1881 gatedefstr = ""
1882 if opstr not in self.added_gate_definitions:
1883 self.added_gate_definitions.add(opstr)
1884 gatedefstr = self.make_gate_definition(op.n_qubits, opstr, optype)
1885 mainstr = opstr + " " + make_args_str(args)
1886 return gatedefstr, mainstr
1888 def add_extra_params(self, op: Op, args: Sequence[UnitID]) -> tuple[str, str]:
1889 optype, params = _get_optype_and_params(op)
1890 assert params is not None
1891 opstr = _tk_to_qasm_extra_params[optype]
1892 gatedefstr = ""
1893 if opstr not in self.added_gate_definitions:
1894 self.added_gate_definitions.add(opstr)
1895 gatedefstr = self.make_gate_definition(
1896 op.n_qubits, opstr, optype, len(params)
1897 )
1898 mainstr = opstr + make_params_str(params) + make_args_str(args)
1899 return gatedefstr, mainstr
1901 def add_op(self, op: Op, args: Sequence[UnitID]) -> None:
1902 optype, _params = _get_optype_and_params(op)
1903 if optype == OpType.RangePredicate:
1904 assert isinstance(op, RangePredicateOp)
1905 self.add_range_predicate(op, cast(list[Bit], args))
1906 elif optype == OpType.Conditional:
1907 assert isinstance(op, Conditional)
1908 self.add_conditional(op, args)
1909 elif optype == OpType.Phase:
1910 # global phase is ignored in QASM
1911 pass
1912 elif optype == OpType.SetBits:
1913 assert isinstance(op, SetBitsOp)
1914 self.add_set_bits(op, cast(list[Bit], args))
1915 elif optype == OpType.CopyBits:
1916 assert isinstance(op, CopyBitsOp)
1917 self.add_copy_bits(op, cast(list[Bit], args))
1918 elif optype == OpType.MultiBit:
1919 assert isinstance(op, MultiBitOp)
1920 self.add_multi_bit(op, cast(list[Bit], args))
1921 elif optype in (OpType.ExplicitPredicate, OpType.ExplicitModifier):
1922 self.add_explicit_op(op, cast(list[Bit], args))
1923 elif optype == OpType.ClExpr:
1924 assert isinstance(op, ClExprOp)
1925 self.add_wired_clexpr(op, cast(list[Bit], args))
1926 elif optype == OpType.WASM:
1927 assert isinstance(op, WASMOp)
1928 self.add_wasm(op, cast(list[Bit], args))
1929 elif optype == OpType.Measure:
1930 self.add_measure(args)
1931 elif hqs_header(self.header) and optype == OpType.ZZPhase:
1932 # special handling for zzphase
1933 assert len(op.params) == 1
1934 self.add_zzphase(op.params[0], args)
1935 elif optype == OpType.CnX:
1936 self.add_cnx(args)
1937 elif optype == OpType.Barrier and self.header == "hqslib1_dev":
1938 assert isinstance(op, BarrierOp)
1939 self.add_data(op, args)
1940 elif (
1941 optype in _tk_to_qasm_noparams
1942 and _tk_to_qasm_noparams[optype] in self.include_module_gates
1943 ):
1944 self.add_gate_noparams(op, args)
1945 elif (
1946 optype in _tk_to_qasm_params
1947 and _tk_to_qasm_params[optype] in self.include_module_gates
1948 ):
1949 self.add_gate_params(op, args)
1950 elif optype in _tk_to_qasm_extra_noparams:
1951 gatedefstr, mainstr = self.add_extra_noparams(op, args)
1952 self.gatedefs += gatedefstr
1953 self.strings.add_string(mainstr)
1954 elif optype in _tk_to_qasm_extra_params: 1954 ↛ 1959line 1954 didn't jump to line 1959 because the condition on line 1954 was always true
1955 gatedefstr, mainstr = self.add_extra_params(op, args)
1956 self.gatedefs += gatedefstr
1957 self.strings.add_string(mainstr)
1958 else:
1959 raise QASMUnsupportedError(f"Cannot print command of type: {op.get_name()}")
1961 def finalize(self) -> str:
1962 # try removing unused predicates
1963 pred_labels = list(self.range_preds.keys())
1964 for label in pred_labels:
1965 # try replacing conditions with a predicate
1966 self.replace_condition(label)
1967 # try removing the predicate
1968 self.remove_unused_predicate(label)
1969 reg_strings = LabelledStringList()
1970 for reg in self.qregs.values():
1971 reg_strings.add_string(f"qreg {reg.name}[{reg.size}];\n")
1972 for bit_reg in self.cregs.values():
1973 reg_strings.add_string(f"creg {bit_reg.name}[{bit_reg.size}];\n")
1974 if self.scratch_reg.size > 0:
1975 reg_strings.add_string(
1976 f"creg {self.scratch_reg.name}[{self.scratch_reg.size}];\n"
1977 )
1978 return (
1979 self.prefix
1980 + self.gatedefs
1981 + _filtered_qasm_str(
1982 reg_strings.get_full_string() + self.strings.get_full_string()
1983 )
1984 )
1987def circuit_to_qasm_io(
1988 circ: Circuit,
1989 stream_out: TextIO,
1990 header: str = "qelib1",
1991 include_gate_defs: set[str] | None = None,
1992 maxwidth: int = 32,
1993) -> None:
1994 """Convert a Circuit to QASM and write to a text stream.
1996 Classical bits in the pytket circuit must be singly-indexed.
1998 Note that this will not account for implicit qubit permutations in the Circuit.
2000 :param circ: pytket circuit
2001 :param stream_out: text stream to be written to
2002 :param header: qasm header (default "qelib1")
2003 :param include_gate_defs: optional set of gates to include
2004 :param maxwidth: maximum allowed width of classical registers (default 32)
2005 """
2006 stream_out.write(
2007 circuit_to_qasm_str(
2008 circ, header=header, include_gate_defs=include_gate_defs, maxwidth=maxwidth
2009 )
2010 )