diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 96ca9dc38e..2e215cb5f1 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -8,6 +8,10 @@ Features: * Add `RemovePhaseOps` pass. +Fixes: + +* Fix issue with custom gates in qasm to circuit conversion. + 1.38.0 (January 2025) --------------------- diff --git a/pytket/pytket/qasm/qasm.py b/pytket/pytket/qasm/qasm.py index 605689dd1c..a0f20e41eb 100644 --- a/pytket/pytket/qasm/qasm.py +++ b/pytket/pytket/qasm/qasm.py @@ -183,6 +183,16 @@ class QASMUnsupportedError(Exception): "fsim": OpType.FSim, } +N_PARAMS_EXTRA_COMMANDS = { + OpType.TK2: 3, + OpType.ISWAP: 1, + OpType.PhasedISWAP: 2, + OpType.YYPhase: 1, + OpType.XXPhase3: 1, + OpType.ESWAP: 1, + OpType.FSim: 2, +} + _tk_to_qasm_noparams = dict((item[1], item[0]) for item in NOPARAM_COMMANDS.items()) _tk_to_qasm_noparams[OpType.CX] = "cx" # prefer "cx" to "CX" _tk_to_qasm_params = dict((item[1], item[0]) for item in PARAM_COMMANDS.items()) @@ -859,9 +869,7 @@ def transform(self, tree: Tree) -> dict[str, Any]: def gdef(self, tree: list) -> None: child_iter = iter(tree) - gate = next(child_iter).value - next_tree = next(child_iter) symbols, args = [], [] if isinstance(next_tree, ParsMap): @@ -882,6 +890,9 @@ def gdef(self, tree: list) -> None: # check to see whether gate definition was generated by pytket converter # if true, add op as pytket Op existing_op: bool = False + # NOPARAM_EXTRA_COMMANDS and PARAM_EXTRA_COMMANDS are + # gates that aren't in the standard qasm spec but in the standard TKET + # optypes if gate in NOPARAM_EXTRA_COMMANDS: qubit_args = [ Qubit(gate + "q" + str(index), 0) for index in list(range(len(args))) @@ -894,25 +905,33 @@ def gdef(self, tree: list) -> None: ) == circuit_to_qasm_str(gate_circ, maxwidth=self.maxwidth): existing_op = True elif gate in PARAM_EXTRA_COMMANDS: - qubit_args = [ - Qubit(gate + "q" + str(index), 0) for index in list(range(len(args))) - ] - comparison_circ = _get_gate_circuit( - PARAM_EXTRA_COMMANDS[gate], - qubit_args, - [Symbol("param" + str(index) + "/pi") for index in range(len(symbols))], - ) - # checks that each command has same string - existing_op = all( - str(g) == str(c) - for g, c in zip( - gate_circ.get_commands(), comparison_circ.get_commands() + optype = PARAM_EXTRA_COMMANDS[gate] + # we check this here, as _get_gate_circuit will find issue if it isn't true + # the later existing_op=all check will make sure it's the same circuit later + if len(symbols) != N_PARAMS_EXTRA_COMMANDS[optype]: + existing_op = False + else: + qubit_args = [ + Qubit(gate + "q" + str(index), 0) for index in range(len(args)) + ] + comparison_circ = _get_gate_circuit( + optype, + qubit_args, + [ + Symbol("param" + str(index) + "/pi") + for index in range(len(symbols)) + ], + ) + # checks that each command has same string + existing_op = all( + str(g) == str(c) + for g, c in zip( + gate_circ.get_commands(), comparison_circ.get_commands() + ) ) - ) if not existing_op: gate_circ.symbol_substitution(symbol_map) gate_circ.rename_units(cast(dict[UnitID, UnitID], rename_map)) - self.gate_dict[gate] = { "definition": gate_circ.to_dict(), "args": symbols, diff --git a/pytket/tests/qasm_test.py b/pytket/tests/qasm_test.py index a3824c03fe..88f70c6d20 100644 --- a/pytket/tests/qasm_test.py +++ b/pytket/tests/qasm_test.py @@ -1209,6 +1209,23 @@ def test_multibitop() -> None: ) +def test_existing_name_conversion() -> None: + # https://github.com/CQCL/tket/issues/1605 + assert ( + circuit_from_qasm_str( + """OPENQASM 2.0; +include "qelib1.inc"; +gate iswap q0,q1 { s q0; s q1; h q0; cx q0,q1; cx q1,q0; h q1; } +qreg qr[3]; +creg cr[3]; +iswap qr[1],qr[2];""" + ) + .get_commands()[0] + .op.type + == OpType.CustomGate + ) + + if __name__ == "__main__": test_qasm_correct() test_qasm_qubit() @@ -1247,3 +1264,4 @@ def test_multibitop() -> None: test_classical_expbox_arg_order(True) test_classical_expbox_arg_order(False) test_register_name_check() + test_existing_name_conversion()