From 95dccabafd465924ae47365408aca572205d0b6e Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:43:56 +0100 Subject: [PATCH 01/23] pull out StatePreparation handling into separate function --- pytket/extensions/qiskit/qiskit_convert.py | 80 ++++++++++++---------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 1e88fca1..50d4fadb 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -290,6 +290,42 @@ def _string_to_circuit( return circ +def _add_statepreparation_circuit( + tkc: Circuit, qubits: List[Qubit], instr: Instruction +) -> None: + # Check how Initialize or StatePrep is constructed + if isinstance(instr.params[0], str): + # Parse string to get the right single qubit gates + circuit_string = "".join(instr.params) + circuit = _string_to_circuit( + circuit_string, instr.num_qubits, qiskit_instruction=instr + ) + tkc.add_circuit(circuit, qubits) + + elif len(instr.params) != 1: + amplitude_list = instr.params + if isinstance(instr, Initialize): + pytket_state_prep_box = StatePreparationBox( + amplitude_list, with_initial_reset=True # type: ignore + ) + else: + pytket_state_prep_box = StatePreparationBox( + amplitude_list, with_initial_reset=False # type: ignore + ) + # Need to reverse qubits here (endian-ness) + reversed_qubits = list(reversed(qubits)) + tkc.add_gate(pytket_state_prep_box, reversed_qubits) + + elif isinstance(instr.params[0], complex) and len(instr.params) == 1: + # convert int to a binary string and apply X for |1> + integer_parameter = int(instr.params[0].real) + bit_string = bin(integer_parameter)[2:] + circuit = _string_to_circuit( + bit_string, instr.num_qubits, qiskit_instruction=instr + ) + tkc.add_circuit(circuit, qubits) + + class CircuitBuilder: def __init__( self, @@ -399,7 +435,7 @@ def add_qiskit_data( f"qiskit ControlledGate with base gate {instr.base_gate}" + "not implemented." ) - elif type(instr) in [PauliEvolutionGate, UnitaryGate]: + elif type(instr) in (PauliEvolutionGate, UnitaryGate): pass # Special handling below else: try: @@ -420,7 +456,8 @@ def add_qiskit_data( sub_circ = Circuit(n_base_qubits) # use base gate name for the CircBox (shows in renderer) sub_circ.name = instr.base_gate.name.capitalize() - if type(instr.base_gate) == UnitaryGate: + + if type(instr.base_gate) is UnitaryGate: assert len(cargs) == 0 add_qiskit_unitary_to_tkc( sub_circ, instr.base_gate, sub_circ.qubits, condition_kwargs @@ -433,46 +470,19 @@ def add_qiskit_data( c_box = CircBox(sub_circ) q_ctrl_box = QControlBox(c_box, instr.num_ctrl_qubits) self.tkc.add_qcontrolbox(q_ctrl_box, qubits) + elif isinstance(instr, (Initialize, StatePreparation)): # Check how Initialize or StatePrep is constructed - if isinstance(instr.params[0], str): - # Parse string to get the right single qubit gates - circuit_string = "".join(instr.params) - circuit = _string_to_circuit( - circuit_string, instr.num_qubits, qiskit_instruction=instr - ) - self.tkc.add_circuit(circuit, qubits) + _add_statepreparation_circuit(self.tkc, qubits, instr) - elif isinstance(instr.params, list) and len(instr.params) != 1: - amplitude_list = instr.params - if isinstance(instr, Initialize): - pytket_state_prep_box = StatePreparationBox( - amplitude_list, with_initial_reset=True # type: ignore - ) - else: - pytket_state_prep_box = StatePreparationBox( - amplitude_list, with_initial_reset=False # type: ignore - ) - # Need to reverse qubits here (endian-ness) - reversed_qubits = list(reversed(qubits)) - self.tkc.add_gate(pytket_state_prep_box, reversed_qubits) - - elif isinstance(instr.params[0], complex) and len(instr.params) == 1: - # convert int to a binary string and apply X for |1> - integer_parameter = int(instr.params[0].real) - bit_string = bin(integer_parameter)[2:] - circuit = _string_to_circuit( - bit_string, instr.num_qubits, qiskit_instruction=instr - ) - self.tkc.add_circuit(circuit, qubits) - - elif type(instr) == PauliEvolutionGate: + elif type(instr) is PauliEvolutionGate: qpo = _qpo_from_peg(instr, qubits) empty_circ = Circuit(len(qargs)) circ = gen_term_sequence_circuit(qpo, empty_circ) ccbox = CircBox(circ) self.tkc.add_circbox(ccbox, qubits) - elif type(instr) == UnitaryGate: + + elif type(instr) is UnitaryGate: assert len(cargs) == 0 add_qiskit_unitary_to_tkc(self.tkc, instr, qubits, condition_kwargs) elif optype == OpType.Barrier: @@ -494,7 +504,7 @@ def add_qiskit_data( subc.name = instr.name self.tkc.add_circbox(CircBox(subc), qubits + bits, **condition_kwargs) # type: ignore - elif optype == OpType.CU3 and type(instr) == qiskit_gates.CUGate: + elif optype == OpType.CU3 and type(instr) is qiskit_gates.CUGate: if instr.params[-1] == 0: self.tkc.add_gate( optype, From d6a7f9dfe3daf32808c7c886706941d85cdb95df Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:59:03 +0100 Subject: [PATCH 02/23] fix some typing --- pytket/extensions/qiskit/qiskit_convert.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 50d4fadb..edc3d39e 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -302,15 +302,15 @@ def _add_statepreparation_circuit( ) tkc.add_circuit(circuit, qubits) - elif len(instr.params) != 1: - amplitude_list = instr.params + if len(instr.params) != 1: + amplitude_list: list[complex] = instr.params if isinstance(instr, Initialize): pytket_state_prep_box = StatePreparationBox( - amplitude_list, with_initial_reset=True # type: ignore + amplitude_list, with_initial_reset=True ) else: pytket_state_prep_box = StatePreparationBox( - amplitude_list, with_initial_reset=False # type: ignore + amplitude_list, with_initial_reset=False ) # Need to reverse qubits here (endian-ness) reversed_qubits = list(reversed(qubits)) From e2e800d3ec72ccfa4297b592f9003f938e280fec Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:16:59 +0100 Subject: [PATCH 03/23] rework logic of stateprep handling --- pytket/extensions/qiskit/qiskit_convert.py | 52 +++++++++++----------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index edc3d39e..c77df8ac 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -294,36 +294,38 @@ def _add_statepreparation_circuit( tkc: Circuit, qubits: List[Qubit], instr: Instruction ) -> None: # Check how Initialize or StatePrep is constructed - if isinstance(instr.params[0], str): - # Parse string to get the right single qubit gates - circuit_string = "".join(instr.params) - circuit = _string_to_circuit( - circuit_string, instr.num_qubits, qiskit_instruction=instr - ) - tkc.add_circuit(circuit, qubits) if len(instr.params) != 1: - amplitude_list: list[complex] = instr.params - if isinstance(instr, Initialize): - pytket_state_prep_box = StatePreparationBox( - amplitude_list, with_initial_reset=True + if isinstance(instr.params[0], str): + # Parse string to get the right single qubit gates + circuit_string = "".join(instr.params) + circuit = _string_to_circuit( + circuit_string, instr.num_qubits, qiskit_instruction=instr ) + tkc.add_circuit(circuit, qubits) else: - pytket_state_prep_box = StatePreparationBox( - amplitude_list, with_initial_reset=False + amplitude_list = instr.params + if isinstance(instr, Initialize): + pytket_state_prep_box = StatePreparationBox( + amplitude_list, with_initial_reset=True # type: ignore + ) + else: + pytket_state_prep_box = StatePreparationBox( + amplitude_list, with_initial_reset=False # type: ignore + ) + # Need to reverse qubits here (endian-ness) + reversed_qubits = list(reversed(qubits)) + tkc.add_gate(pytket_state_prep_box, reversed_qubits) + else: + if isinstance(instr.params[0], complex): + # convert int to a binary string and apply X for |1> + integer_parameter = int(instr.params[0].real) + bit_string = bin(integer_parameter)[2:] + circuit = _string_to_circuit( + bit_string, instr.num_qubits, qiskit_instruction=instr ) - # Need to reverse qubits here (endian-ness) - reversed_qubits = list(reversed(qubits)) - tkc.add_gate(pytket_state_prep_box, reversed_qubits) - - elif isinstance(instr.params[0], complex) and len(instr.params) == 1: - # convert int to a binary string and apply X for |1> - integer_parameter = int(instr.params[0].real) - bit_string = bin(integer_parameter)[2:] - circuit = _string_to_circuit( - bit_string, instr.num_qubits, qiskit_instruction=instr - ) - tkc.add_circuit(circuit, qubits) + tkc.add_circuit(circuit, qubits) + # TODO raise error class CircuitBuilder: From ff438d45d413da9e046056a88277d9177032a8fe Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:31:23 +0100 Subject: [PATCH 04/23] numpy array typing --- pytket/extensions/qiskit/qiskit_convert.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index c77df8ac..4e11960d 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -294,26 +294,26 @@ def _add_statepreparation_circuit( tkc: Circuit, qubits: List[Qubit], instr: Instruction ) -> None: # Check how Initialize or StatePrep is constructed - if len(instr.params) != 1: if isinstance(instr.params[0], str): # Parse string to get the right single qubit gates - circuit_string = "".join(instr.params) + circuit_string: str = "".join(instr.params) circuit = _string_to_circuit( circuit_string, instr.num_qubits, qiskit_instruction=instr ) tkc.add_circuit(circuit, qubits) else: - amplitude_list = instr.params + amplitude_array = np.array(instr.params) if isinstance(instr, Initialize): pytket_state_prep_box = StatePreparationBox( - amplitude_list, with_initial_reset=True # type: ignore + amplitude_array, with_initial_reset=True ) else: pytket_state_prep_box = StatePreparationBox( - amplitude_list, with_initial_reset=False # type: ignore + amplitude_array, with_initial_reset=False ) # Need to reverse qubits here (endian-ness) + # TODO pass same list of qubits to add_circuit reversed_qubits = list(reversed(qubits)) tkc.add_gate(pytket_state_prep_box, reversed_qubits) else: From 34b2368e954bd4d7a4c95d6f9abc724200f7fc42 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 00:21:40 +0100 Subject: [PATCH 05/23] simplify Reset --- pytket/extensions/qiskit/qiskit_convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 4e11960d..0c022bcc 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -261,7 +261,7 @@ def _string_to_circuit( # If Initialize, add resets if isinstance(qiskit_instruction, Initialize): for qubit in circ.qubits: - circ.add_gate(OpType.Reset, [qubit]) + circ.Reset(qubit) # We iterate through the string in reverse to add the # gates in the correct order (endian-ness). From bd7772b8a7b115c38bd714311f06c153b7c8c1e1 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 00:39:58 +0100 Subject: [PATCH 06/23] separate out ControlledGate handling --- pytket/extensions/qiskit/qiskit_convert.py | 47 ++++++++++++---------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 0c022bcc..d4b77b8b 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -290,6 +290,31 @@ def _string_to_circuit( return circ +def get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: + if c_gate.base_class in _known_qiskit_gate: + # First we check if the gate is in _known_qiskit_gate + # this avoids CZ being converted to CnZ + known_optype = _known_qiskit_gate[c_gate.base_class] + return known_optype + elif c_gate.base_gate.base_class is qiskit_gates.RYGate: + return OpType.CnRy + elif c_gate.base_gate.base_class is qiskit_gates.YGate: + return OpType.CnY + elif c_gate.base_gate.base_class is qiskit_gates.ZGate: + return OpType.CnZ + else: + if ( + c_gate.base_gate.base_class in _known_qiskit_gate + or c_gate.base_gate.base_class is UnitaryGate + ): + return OpType.QControlBox + else: + raise NotImplementedError( + f"Conversion of qiskit ControlledGate with base gate {c_gate.base_gate}" + + "not implemented." + ) + + def _add_statepreparation_circuit( tkc: Circuit, qubits: List[Qubit], instr: Instruction ) -> None: @@ -416,27 +441,7 @@ def add_qiskit_data( self.add_xs(num_ctrl_qubits, ctrl_state, qargs) optype = None if isinstance(instr, ControlledGate): - if instr.base_class in _known_qiskit_gate: - # First we check if the gate is in _known_qiskit_gate - # this avoids CZ being converted to CnZ - optype = _known_qiskit_gate[instr.base_class] - elif instr.base_gate.base_class is qiskit_gates.RYGate: - optype = OpType.CnRy - elif instr.base_gate.base_class is qiskit_gates.YGate: - optype = OpType.CnY - elif instr.base_gate.base_class is qiskit_gates.ZGate: - optype = OpType.CnZ - else: - if ( - instr.base_gate.base_class in _known_qiskit_gate - or instr.base_gate.base_class == UnitaryGate - ): - optype = OpType.QControlBox # QControlBox case handled below - else: - raise NotImplementedError( - f"qiskit ControlledGate with base gate {instr.base_gate}" - + "not implemented." - ) + optype = get_controlled_tket_optype(instr) elif type(instr) in (PauliEvolutionGate, UnitaryGate): pass # Special handling below else: From 686a7b919b58e20bd4a7e76f24f662d26849b57b Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 00:43:47 +0100 Subject: [PATCH 07/23] use pattern matching in ControlledGate handling --- pytket/extensions/qiskit/qiskit_convert.py | 36 ++++++++++++---------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index d4b77b8b..d06fd3db 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -296,23 +296,25 @@ def get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: # this avoids CZ being converted to CnZ known_optype = _known_qiskit_gate[c_gate.base_class] return known_optype - elif c_gate.base_gate.base_class is qiskit_gates.RYGate: - return OpType.CnRy - elif c_gate.base_gate.base_class is qiskit_gates.YGate: - return OpType.CnY - elif c_gate.base_gate.base_class is qiskit_gates.ZGate: - return OpType.CnZ - else: - if ( - c_gate.base_gate.base_class in _known_qiskit_gate - or c_gate.base_gate.base_class is UnitaryGate - ): - return OpType.QControlBox - else: - raise NotImplementedError( - f"Conversion of qiskit ControlledGate with base gate {c_gate.base_gate}" - + "not implemented." - ) + + match c_gate.base_gate.base_class: + case qiskit_gates.RYGate: + return OpType.CnRy + case qiskit_gates.YGate: + return OpType.CnY + case qiskit_gates.ZGate: + return OpType.CnZ + case _: + if ( + c_gate.base_gate.base_class in _known_qiskit_gate + or c_gate.base_gate.base_class is UnitaryGate + ): + return OpType.QControlBox + else: + raise NotImplementedError( + f"Conversion of qiskit ControlledGate with base gate {c_gate.base_gate}" + + "not implemented." + ) def _add_statepreparation_circuit( From 41bf21fa5226e2f797f58afa47f1a6115997a703 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 00:56:08 +0100 Subject: [PATCH 08/23] use pattern matching in string parsing --- pytket/extensions/qiskit/qiskit_convert.py | 47 +++++++++++----------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index d06fd3db..06d07c95 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -265,32 +265,33 @@ def _string_to_circuit( # We iterate through the string in reverse to add the # gates in the correct order (endian-ness). - for count, char in enumerate(reversed(circuit_string)): - if char == "0": - pass - elif char == "1": - circ.X(count) - elif char == "+": - circ.H(count) - elif char == "-": - circ.X(count) - circ.H(count) - elif char == "r": - circ.H(count) - circ.S(count) - elif char == "l": - circ.H(count) - circ.Sdg(count) - else: - raise ValueError( - f"Cannot parse string for character {char}. " - + "The supported characters are {'0', '1', '+', '-', 'r', 'l'}." - ) + for qubit_index, character in enumerate(reversed(circuit_string)): + match character: + case "0": + pass + case "1": + circ.X(qubit_index) + case "+": + circ.H(qubit_index) + case "-": + circ.X(qubit_index) + circ.H(qubit_index) + case "r": + circ.H(qubit_index) + circ.S(qubit_index) + case "l": + circ.H(qubit_index) + circ.Sdg(qubit_index) + case _: + raise ValueError( + f"Cannot parse string for character {character}. " + + "The supported characters are {'0', '1', '+', '-', 'r', 'l'}." + ) return circ -def get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: +def _get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: if c_gate.base_class in _known_qiskit_gate: # First we check if the gate is in _known_qiskit_gate # this avoids CZ being converted to CnZ @@ -443,7 +444,7 @@ def add_qiskit_data( self.add_xs(num_ctrl_qubits, ctrl_state, qargs) optype = None if isinstance(instr, ControlledGate): - optype = get_controlled_tket_optype(instr) + optype = _get_controlled_tket_optype(instr) elif type(instr) in (PauliEvolutionGate, UnitaryGate): pass # Special handling below else: From ae6249f946972b0860ad33026b4c1fb2bb6f917e Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 00:58:18 +0100 Subject: [PATCH 09/23] use is rather than == for type check --- pytket/extensions/qiskit/qiskit_convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 06d07c95..3ca86a97 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -411,13 +411,13 @@ def add_qiskit_data( instr, qargs, cargs = datum.operation, datum.qubits, datum.clbits condition_kwargs = {} if instr.condition is not None: - if type(instr.condition[0]) == ClassicalRegister: + if type(instr.condition[0]) is ClassicalRegister: cond_reg = self.cregmap[instr.condition[0]] condition_kwargs = { "condition_bits": [cond_reg[k] for k in range(len(cond_reg))], "condition_value": instr.condition[1], } - elif type(instr.condition[0]) == Clbit: + elif type(instr.condition[0]) is Clbit: # .find_bit() returns type: # tuple[index, list[tuple[ClassicalRegister, index]]] # We assume each bit belongs to exactly one register. From c999b6bc6c97b9f6d54ce8434b38a8ea521e88fe Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 01:49:14 +0100 Subject: [PATCH 10/23] decouple OpType checking from circuit building --- pytket/extensions/qiskit/qiskit_convert.py | 39 +++++++++++++--------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 3ca86a97..ddb9cc3f 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -318,9 +318,25 @@ def _get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: ) -def _add_statepreparation_circuit( +def _optype_from_qiskit_instruction(instruction: Instruction) -> OpType: + if isinstance(instruction, ControlledGate): + return _get_controlled_tket_optype(instruction) + try: + optype = _known_qiskit_gate[instruction.base_class] + return optype + except KeyError: + raise NotImplementedError( + f"Conversion of qiskit's {instruction.name} instruction is " + + "currently unsupported by qiskit_to_tk. Consider " + + "using QuantumCircuit.decompose() before attempting " + + "conversion." + ) + + +def _add_state_preparation_box( tkc: Circuit, qubits: List[Qubit], instr: Instruction ) -> None: + """ """ # Check how Initialize or StatePrep is constructed if len(instr.params) != 1: if isinstance(instr.params[0], str): @@ -442,21 +458,12 @@ def add_qiskit_data( except AttributeError: pass self.add_xs(num_ctrl_qubits, ctrl_state, qargs) + optype = None - if isinstance(instr, ControlledGate): - optype = _get_controlled_tket_optype(instr) - elif type(instr) in (PauliEvolutionGate, UnitaryGate): - pass # Special handling below - else: - try: - optype = _known_qiskit_gate[instr.base_class] - except KeyError: - raise NotImplementedError( - f"Conversion of qiskit's {instr.name} instruction is " - + "currently unsupported by qiskit_to_tk. Consider " - + "using QuantumCircuit.decompose() before attempting " - + "conversion." - ) + if type(instr) not in (PauliEvolutionGate, UnitaryGate): + # Handling of PauliEvolutionGate and UnitaryGate below + optype = _optype_from_qiskit_instruction(instruction=instr) + qubits = [self.qbmap[qbit] for qbit in qargs] bits = [self.cbmap[bit] for bit in cargs] @@ -483,7 +490,7 @@ def add_qiskit_data( elif isinstance(instr, (Initialize, StatePreparation)): # Check how Initialize or StatePrep is constructed - _add_statepreparation_circuit(self.tkc, qubits, instr) + _add_state_preparation_box(self.tkc, qubits, instr) elif type(instr) is PauliEvolutionGate: qpo = _qpo_from_peg(instr, qubits) From b4406db03bf1707c4167b92e6db769e9020eebaf Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 01:52:35 +0100 Subject: [PATCH 11/23] shorten line for pylint --- pytket/extensions/qiskit/qiskit_convert.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index ddb9cc3f..9b263ad0 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -313,7 +313,8 @@ def _get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: return OpType.QControlBox else: raise NotImplementedError( - f"Conversion of qiskit ControlledGate with base gate {c_gate.base_gate}" + "Conversion of qiskit ControlledGate with base gate " + + f"base gate {c_gate.base_gate}" + "not implemented." ) From 385756a58d84f2322dce73abf660d50eddaee816 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 02:28:17 +0100 Subject: [PATCH 12/23] minor improvements --- pytket/extensions/qiskit/qiskit_convert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 9b263ad0..e665c081 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -334,7 +334,7 @@ def _optype_from_qiskit_instruction(instruction: Instruction) -> OpType: ) -def _add_state_preparation_box( +def _add_state_preparation( tkc: Circuit, qubits: List[Qubit], instr: Instruction ) -> None: """ """ @@ -491,7 +491,7 @@ def add_qiskit_data( elif isinstance(instr, (Initialize, StatePreparation)): # Check how Initialize or StatePrep is constructed - _add_state_preparation_box(self.tkc, qubits, instr) + _add_state_preparation(self.tkc, qubits, instr) elif type(instr) is PauliEvolutionGate: qpo = _qpo_from_peg(instr, qubits) @@ -819,7 +819,7 @@ def append_tk_command_to_qiskit( # Use the U3 gate for tk1_replacement as this is a member of _supported_tket_gates def _tk1_to_u3(a: Param, b: Param, c: Param) -> Circuit: tk1_circ = Circuit(1) - tk1_circ.add_gate(OpType.U3, [b, a - 1 / 2, c + 1 / 2], [0]).add_phase(-(a + c) / 2) + tk1_circ.U3(b, a - 1 / 2, c + 1 / 2, 0).add_phase(-(a + c) / 2) return tk1_circ From 206fbbbee9b1cf3613a865675d16656edd5693e7 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 13:29:36 +0100 Subject: [PATCH 13/23] use python 3.10+ type annotations --- pytket/extensions/qiskit/qiskit_convert.py | 76 ++++++++++------------ 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index e665c081..9ff2b105 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -18,15 +18,11 @@ from collections import defaultdict from typing import ( Callable, - Dict, - List, - Optional, Union, + Optional, Any, Iterable, cast, - Set, - Tuple, TypeVar, TYPE_CHECKING, ) @@ -184,7 +180,7 @@ _known_gate_rev_phase[OpType.Vdg] = (qiskit_gates.SXdgGate, 0.25) # use minor signature hacks to figure out the string names of qiskit Gate objects -_gate_str_2_optype: Dict[str, OpType] = dict() +_gate_str_2_optype: dict[str, OpType] = dict() for gate, optype in _known_qiskit_gate.items(): if gate in ( UnitaryGate, @@ -208,7 +204,7 @@ _gate_str_2_optype_rev[OpType.Unitary1qBox] = "unitary" -def _tk_gate_set(config: QasmBackendConfiguration) -> Set[OpType]: +def _tk_gate_set(config: QasmBackendConfiguration) -> set[OpType]: """Set of tket gate types supported by the qiskit backend""" if config.simulator: gate_set = { @@ -226,7 +222,7 @@ def _tk_gate_set(config: QasmBackendConfiguration) -> Set[OpType]: } -def _qpo_from_peg(peg: PauliEvolutionGate, qubits: List[Qubit]) -> QubitPauliOperator: +def _qpo_from_peg(peg: PauliEvolutionGate, qubits: list[Qubit]) -> QubitPauliOperator: op = peg.operator t = peg.params[0] qpodict = {} @@ -335,7 +331,7 @@ def _optype_from_qiskit_instruction(instruction: Instruction) -> OpType: def _add_state_preparation( - tkc: Circuit, qubits: List[Qubit], instr: Instruction + tkc: Circuit, qubits: list[Qubit], instr: Instruction ) -> None: """ """ # Check how Initialize or StatePrep is constructed @@ -376,8 +372,8 @@ def _add_state_preparation( class CircuitBuilder: def __init__( self, - qregs: List[QuantumRegister], - cregs: Optional[List[ClassicalRegister]] = None, + qregs: list[QuantumRegister], + cregs: Optional[list[ClassicalRegister]] = None, name: Optional[str] = None, phase: Optional[sympy.Expr] = None, ): @@ -408,8 +404,8 @@ def circuit(self) -> Circuit: def add_xs( self, num_ctrl_qubits: Optional[int], - ctrl_state: Optional[Union[str, int]], - qargs: List["Qubit"], + ctrl_state: Optional[str | int], + qargs: list["Qubit"], ) -> None: if ctrl_state is not None: assert isinstance(num_ctrl_qubits, int) @@ -542,8 +538,8 @@ def add_qiskit_data( def add_qiskit_unitary_to_tkc( tkc: Circuit, u_gate: UnitaryGate, - qubits: List[Qubit], - condition_kwargs: Dict[str, Any], + qubits: list[Qubit], + condition_kwargs: dict[str, Any], ) -> None: # Note reversal of qubits, to account for endianness (pytket unitaries # are ILO-BE == DLO-LE; qiskit unitaries are ILO-LE == DLO-BE). @@ -605,7 +601,7 @@ def qiskit_to_tk(qcirc: QuantumCircuit, preserve_param_uuid: bool = False) -> Ci return builder.circuit() -def param_to_tk(p: Union[float, ParameterExpression]) -> sympy.Expr: +def param_to_tk(p: float | ParameterExpression) -> sympy.Expr: if isinstance(p, ParameterExpression): symexpr = p._symbol_expr try: @@ -617,8 +613,8 @@ def param_to_tk(p: Union[float, ParameterExpression]) -> sympy.Expr: def param_to_qiskit( - p: sympy.Expr, symb_map: Dict[Parameter, sympy.Symbol] -) -> Union[float, ParameterExpression]: + p: sympy.Expr, symb_map: dict[Parameter, sympy.Symbol] +) -> float | ParameterExpression: ppi = p * sympy.pi if len(ppi.free_symbols) == 0: return float(ppi.evalf()) @@ -627,19 +623,19 @@ def param_to_qiskit( def _get_params( - op: Op, symb_map: Dict[Parameter, sympy.Symbol] -) -> List[Union[float, ParameterExpression]]: + op: Op, symb_map: dict[Parameter, sympy.Symbol] +) -> list[float | ParameterExpression]: return [param_to_qiskit(p, symb_map) for p in op.params] def append_tk_command_to_qiskit( op: "Op", - args: List["UnitID"], + args: list["UnitID"], qcirc: QuantumCircuit, - qregmap: Dict[str, QuantumRegister], - cregmap: Dict[str, ClassicalRegister], - symb_map: Dict[Parameter, sympy.Symbol], - range_preds: Dict[Bit, Tuple[List["UnitID"], int]], + qregmap: dict[str, QuantumRegister], + cregmap: dict[str, ClassicalRegister], + symb_map: dict[Parameter, sympy.Symbol], + range_preds: dict[Bit, tuple[list["UnitID"], int]], ) -> InstructionSet: optype = op.type if optype == OpType.Measure: @@ -849,7 +845,7 @@ def tk_to_qiskit( if replace_implicit_swaps: tkc.replace_implicit_wire_swaps() qcirc = QuantumCircuit(name=tkc.name) - qreg_sizes: Dict[str, int] = {} + qreg_sizes: dict[str, int] = {} for qb in tkc.qubits: if len(qb.index) != 1: raise NotImplementedError("Qiskit registers must use a single index") @@ -870,7 +866,7 @@ def tk_to_qiskit( cregmap.update({c_reg.name: qis_reg}) qcirc.add_register(qis_reg) symb_map = {Parameter(str(s)): s for s in tkc.free_symbols()} - range_preds: Dict[Bit, Tuple[List["UnitID"], int]] = dict() + range_preds: dict[Bit, tuple[list["UnitID"], int]] = dict() # Apply a rebase to the set of pytket gates which have replacements in qiskit supported_gate_rebase.apply(tkc) @@ -901,7 +897,7 @@ def tk_to_qiskit( return qcirc -def process_characterisation(backend: "BackendV1") -> Dict[str, Any]: +def process_characterisation(backend: "BackendV1") -> dict[str, Any]: """Convert a :py:class:`qiskit.providers.backend.BackendV1` to a dictionary containing device Characteristics @@ -917,7 +913,7 @@ def process_characterisation(backend: "BackendV1") -> Dict[str, Any]: def process_characterisation_from_config( config: QasmBackendConfiguration, properties: Optional[BackendProperties] -) -> Dict[str, Any]: +) -> dict[str, Any]: """Obtain a dictionary containing device Characteristics given config and props. :param config: A IBMQ configuration object @@ -942,7 +938,7 @@ def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any n_qubits = config.n_qubits if coupling_map is None: # Assume full connectivity - arc: Union[FullyConnected, Architecture] = FullyConnected(n_qubits) + arc: FullyConnected | Architecture = FullyConnected(n_qubits) else: arc = Architecture(coupling_map) @@ -992,14 +988,14 @@ def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any K1 = TypeVar("K1") K2 = TypeVar("K2") V = TypeVar("V") - convert_keys_t = Callable[[Callable[[K1], K2], Dict[K1, V]], Dict[K2, V]] + convert_keys_t = Callable[[Callable[[K1], K2], dict[K1, V]], dict[K2, V]] # convert qubits to architecture Nodes convert_keys: convert_keys_t = lambda f, d: {f(k): v for k, v in d.items()} node_errors = convert_keys(lambda q: Node(q), node_errors) link_errors = convert_keys(lambda p: (Node(p[0]), Node(p[1])), link_errors) readout_errors = convert_keys(lambda q: Node(q), readout_errors) - characterisation: Dict[str, Any] = dict() + characterisation: dict[str, Any] = dict() characterisation["NodeErrors"] = node_errors characterisation["EdgeErrors"] = link_errors characterisation["ReadoutErrors"] = readout_errors @@ -1013,8 +1009,8 @@ def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any def get_avg_characterisation( - characterisation: Dict[str, Any] -) -> Dict[str, Dict[Node, float]]: + characterisation: dict[str, Any] +) -> dict[str, dict[Node, float]]: """ Convert gate-specific characterisation into readout, one- and two-qubit errors @@ -1025,19 +1021,19 @@ def get_avg_characterisation( K = TypeVar("K") V1 = TypeVar("V1") V2 = TypeVar("V2") - map_values_t = Callable[[Callable[[V1], V2], Dict[K, V1]], Dict[K, V2]] + map_values_t = Callable[[Callable[[V1], V2], dict[K, V1]], dict[K, V2]] map_values: map_values_t = lambda f, d: {k: f(v) for k, v in d.items()} - node_errors = cast(Dict[Node, Dict[OpType, float]], characterisation["NodeErrors"]) + node_errors = cast(dict[Node, dict[OpType, float]], characterisation["NodeErrors"]) link_errors = cast( - Dict[Tuple[Node, Node], Dict[OpType, float]], characterisation["EdgeErrors"] + dict[tuple[Node, Node], dict[OpType, float]], characterisation["EdgeErrors"] ) readout_errors = cast( - Dict[Node, List[List[float]]], characterisation["ReadoutErrors"] + dict[Node, list[list[float]]], characterisation["ReadoutErrors"] ) - avg: Callable[[Dict[Any, float]], float] = lambda xs: sum(xs.values()) / len(xs) - avg_mat: Callable[[List[List[float]]], float] = ( + avg: Callable[[dict[Any, float]], float] = lambda xs: sum(xs.values()) / len(xs) + avg_mat: Callable[[list[list[float]]], float] = ( lambda xs: (xs[0][1] + xs[1][0]) / 2.0 ) avg_readout_errors = map_values(avg_mat, readout_errors) From 34309a4b899b085525539d6b62eecadc3e0f9a49 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 13:34:32 +0100 Subject: [PATCH 14/23] remove redundant type declarations in docstrings --- pytket/extensions/qiskit/qiskit_convert.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 9ff2b105..836e9674 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -574,15 +574,12 @@ def qiskit_to_tk(qcirc: QuantumCircuit, preserve_param_uuid: bool = False) -> Ci Converts a qiskit :py:class:`qiskit.QuantumCircuit` to a pytket :py:class:`Circuit`. :param qcirc: A circuit to be converted - :type qcirc: QuantumCircuit :param preserve_param_uuid: Whether to preserve symbolic Parameter uuids by appending them to the tket Circuit symbol names as "_UUID:". This can be useful if you want to reassign Parameters after conversion to tket and back, as it is necessary for Parameter object equality to be preserved. - :type preserve_param_uuid: bool :return: The converted circuit - :rtype: Circuit """ circ_name = qcirc.name # Parameter uses a hidden _uuid for equality check @@ -834,12 +831,9 @@ def tk_to_qiskit( circuit will be returned using the tket gates which are supported in qiskit. :param tkcirc: A :py:class:`Circuit` to be converted - :type tkcirc: Circuit :param replace_implicit_swaps: Implement implicit permutation by adding SWAPs to the end of the circuit. - :type replace_implicit_swaps: bool :return: The converted circuit - :rtype: QuantumCircuit """ tkc = tkcirc.copy() # Make a local copy of tkcirc if replace_implicit_swaps: @@ -902,9 +896,7 @@ def process_characterisation(backend: "BackendV1") -> dict[str, Any]: containing device Characteristics :param backend: A backend to be converted - :type backend: BackendV1 :return: A dictionary containing device characteristics - :rtype: dict """ config = backend.configuration() props = backend.properties() @@ -917,11 +909,8 @@ def process_characterisation_from_config( """Obtain a dictionary containing device Characteristics given config and props. :param config: A IBMQ configuration object - :type config: QasmBackendConfiguration :param properties: An optional IBMQ properties object - :type properties: Optional[BackendProperties] :return: A dictionary containing device characteristics - :rtype: dict """ # TODO explicitly check for and separate 1 and 2 qubit gates From 6baed7e5559f3a7bc2742c4a5fccc31a303c908e Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 13:51:20 +0100 Subject: [PATCH 15/23] imporve comments and docstrings --- pytket/extensions/qiskit/qiskit_convert.py | 41 ++++++++++++---------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 836e9674..b70e2a0a 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -247,15 +247,17 @@ def _qpo_from_peg(peg: PauliEvolutionGate, qubits: list[Qubit]) -> QubitPauliOpe def _string_to_circuit( - circuit_string: str, n_qubits: int, qiskit_instruction: Instruction + circuit_string: str, + n_qubits: int, + qiskit_prep: Initialize | StatePreparation, ) -> Circuit: - """Helper function to handle strings in QuantumCircuit.initialize - and QuantumCircuit.prepare_state""" + """Helper function to generate circuits for Initialize + and StatePreparation objects built with strings""" circ = Circuit(n_qubits) # Check if Instruction is Initialize or Statepreparation # If Initialize, add resets - if isinstance(qiskit_instruction, Initialize): + if isinstance(qiskit_prep, Initialize): for qubit in circ.qubits: circ.Reset(qubit) @@ -288,6 +290,7 @@ def _string_to_circuit( def _get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: + """Get a pytket contolled OpType from a qiskit ControlledGate.""" if c_gate.base_class in _known_qiskit_gate: # First we check if the gate is in _known_qiskit_gate # this avoids CZ being converted to CnZ @@ -316,6 +319,7 @@ def _get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: def _optype_from_qiskit_instruction(instruction: Instruction) -> OpType: + """Get a pytket OpType from a qiskit Instruction.""" if isinstance(instruction, ControlledGate): return _get_controlled_tket_optype(instruction) try: @@ -331,21 +335,24 @@ def _optype_from_qiskit_instruction(instruction: Instruction) -> OpType: def _add_state_preparation( - tkc: Circuit, qubits: list[Qubit], instr: Instruction + tkc: Circuit, qubits: list[Qubit], prep: Initialize | StatePreparation ) -> None: - """ """ + """Handles different cases of Initialize and StatePreparation + and appends the appropriate state preparation to a Circuit instance.""" + # Check how Initialize or StatePrep is constructed - if len(instr.params) != 1: - if isinstance(instr.params[0], str): + # With a string, an int or an array of amplitudes + if len(prep.params) != 1: + if isinstance(prep.params[0], str): # Parse string to get the right single qubit gates - circuit_string: str = "".join(instr.params) + circuit_string: str = "".join(prep.params) circuit = _string_to_circuit( - circuit_string, instr.num_qubits, qiskit_instruction=instr + circuit_string, prep.num_qubits, qiskit_prep=prep ) tkc.add_circuit(circuit, qubits) else: - amplitude_array = np.array(instr.params) - if isinstance(instr, Initialize): + amplitude_array = np.array(prep.params) + if isinstance(prep, Initialize): pytket_state_prep_box = StatePreparationBox( amplitude_array, with_initial_reset=True ) @@ -358,13 +365,11 @@ def _add_state_preparation( reversed_qubits = list(reversed(qubits)) tkc.add_gate(pytket_state_prep_box, reversed_qubits) else: - if isinstance(instr.params[0], complex): + if isinstance(prep.params[0], complex): # convert int to a binary string and apply X for |1> - integer_parameter = int(instr.params[0].real) + integer_parameter = int(prep.params[0].real) bit_string = bin(integer_parameter)[2:] - circuit = _string_to_circuit( - bit_string, instr.num_qubits, qiskit_instruction=instr - ) + circuit = _string_to_circuit(bit_string, prep.num_qubits, qiskit_prep=prep) tkc.add_circuit(circuit, qubits) # TODO raise error @@ -486,7 +491,7 @@ def add_qiskit_data( self.tkc.add_qcontrolbox(q_ctrl_box, qubits) elif isinstance(instr, (Initialize, StatePreparation)): - # Check how Initialize or StatePrep is constructed + # Append OpType found by stateprep helpers _add_state_preparation(self.tkc, qubits, instr) elif type(instr) is PauliEvolutionGate: From 2898dc199f680a1cc9d11fd9796311ec3c7c31f9 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 14:47:41 +0100 Subject: [PATCH 16/23] separate some stateprep handling --- pytket/extensions/qiskit/qiskit_convert.py | 26 ++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index b70e2a0a..024e4003 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -30,6 +30,7 @@ from uuid import UUID import numpy as np +from numpy.typing import NDArray from symengine import sympify # type: ignore from symengine.lib import symengine_wrapper # type: ignore @@ -334,6 +335,18 @@ def _optype_from_qiskit_instruction(instruction: Instruction) -> OpType: ) +def _state_prep_box_from_amplitudes( + amplitudes: NDArray[np.complex128], prep: Initialize | StatePreparation +) -> StatePreparationBox: + if isinstance(prep, Initialize): + pytket_state_prep_box = StatePreparationBox(amplitudes, with_initial_reset=True) + else: + pytket_state_prep_box = StatePreparationBox( + amplitudes, with_initial_reset=False + ) + return pytket_state_prep_box + + def _add_state_preparation( tkc: Circuit, qubits: list[Qubit], prep: Initialize | StatePreparation ) -> None: @@ -351,15 +364,10 @@ def _add_state_preparation( ) tkc.add_circuit(circuit, qubits) else: - amplitude_array = np.array(prep.params) - if isinstance(prep, Initialize): - pytket_state_prep_box = StatePreparationBox( - amplitude_array, with_initial_reset=True - ) - else: - pytket_state_prep_box = StatePreparationBox( - amplitude_array, with_initial_reset=False - ) + amplitude_array: NDArray[np.complex128] = np.array(prep.params) + pytket_state_prep_box = _state_prep_box_from_amplitudes( + amplitude_array, prep + ) # Need to reverse qubits here (endian-ness) # TODO pass same list of qubits to add_circuit reversed_qubits = list(reversed(qubits)) From a6899af028d4645f7a58f9a531ec0672e0ba5069 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:08:55 +0100 Subject: [PATCH 17/23] simplify Initialise handling --- pytket/extensions/qiskit/qiskit_convert.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 024e4003..338ee1b9 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -335,18 +335,6 @@ def _optype_from_qiskit_instruction(instruction: Instruction) -> OpType: ) -def _state_prep_box_from_amplitudes( - amplitudes: NDArray[np.complex128], prep: Initialize | StatePreparation -) -> StatePreparationBox: - if isinstance(prep, Initialize): - pytket_state_prep_box = StatePreparationBox(amplitudes, with_initial_reset=True) - else: - pytket_state_prep_box = StatePreparationBox( - amplitudes, with_initial_reset=False - ) - return pytket_state_prep_box - - def _add_state_preparation( tkc: Circuit, qubits: list[Qubit], prep: Initialize | StatePreparation ) -> None: @@ -365,9 +353,10 @@ def _add_state_preparation( tkc.add_circuit(circuit, qubits) else: amplitude_array: NDArray[np.complex128] = np.array(prep.params) - pytket_state_prep_box = _state_prep_box_from_amplitudes( - amplitude_array, prep + pytket_state_prep_box = StatePreparationBox( + amplitude_array, with_initial_reset=(type(prep) is Initialize) ) + # Need to reverse qubits here (endian-ness) # TODO pass same list of qubits to add_circuit reversed_qubits = list(reversed(qubits)) From d8e4c66bee888415c45d44f430c834614d105b11 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 17:35:01 +0100 Subject: [PATCH 18/23] minor typing fixes --- pytket/extensions/qiskit/qiskit_convert.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 338ee1b9..03bebcd7 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -463,8 +463,8 @@ def add_qiskit_data( # Handling of PauliEvolutionGate and UnitaryGate below optype = _optype_from_qiskit_instruction(instruction=instr) - qubits = [self.qbmap[qbit] for qbit in qargs] - bits = [self.cbmap[bit] for bit in cargs] + qubits: list[Qubit] = [self.qbmap[qbit] for qbit in qargs] + bits: list[Bit] = [self.cbmap[bit] for bit in cargs] if optype == OpType.QControlBox: params = [param_to_tk(p) for p in instr.base_gate.params] @@ -479,7 +479,9 @@ def add_qiskit_data( sub_circ, instr.base_gate, sub_circ.qubits, condition_kwargs ) else: - base_tket_gate = _known_qiskit_gate[instr.base_gate.base_class] + base_tket_gate: OpType = _known_qiskit_gate[ + instr.base_gate.base_class + ] sub_circ.add_gate( base_tket_gate, params, list(range(n_base_qubits)) ) @@ -784,7 +786,7 @@ def append_tk_command_to_qiskit( ) from error params = _get_params(op, symb_map) g = gatetype(*params) - if type(phase) == float: + if type(phase) is float: qcirc.global_phase += phase * np.pi else: qcirc.global_phase += sympify(phase * sympy.pi) From 807a1e3e76acd2407312e876c6e886487d547cac Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Sun, 25 Aug 2024 13:40:16 +0100 Subject: [PATCH 19/23] helper function for getting condition kwargs --- pytket/extensions/qiskit/qiskit_convert.py | 62 +++++++++++++--------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 03bebcd7..6196c899 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -297,7 +297,6 @@ def _get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: # this avoids CZ being converted to CnZ known_optype = _known_qiskit_gate[c_gate.base_class] return known_optype - match c_gate.base_gate.base_class: case qiskit_gates.RYGate: return OpType.CnRy @@ -371,6 +370,34 @@ def _add_state_preparation( # TODO raise error +def _get_pytket_condition_kwargs( + instruction: Instruction, + cregmap: dict[str, ClassicalRegister], + circuit: QuantumCircuit, +) -> dict[str, Any]: + if type(instruction.condition[0]) is ClassicalRegister: + cond_reg = cregmap[instruction.condition[0]] + condition_kwargs = { + "condition_bits": [cond_reg[k] for k in range(len(cond_reg))], + "condition_value": instruction.condition[1], + } + return condition_kwargs + elif type(instruction.condition[0]) is Clbit: + # .find_bit() returns type: + # tuple[index, list[tuple[ClassicalRegister, index]]] + # We assume each bit belongs to exactly one register. + index = circuit.find_bit(instruction.condition[0])[0] + register = circuit.find_bit(instruction.condition[0])[1][0][0] + cond_reg = cregmap[register] + condition_kwargs = { + "condition_bits": [cond_reg[index]], + "condition_value": instruction.condition[1], + } + return condition_kwargs + else: + raise NotImplementedError("condition must contain classical bit or register") + + class CircuitBuilder: def __init__( self, @@ -424,29 +451,17 @@ def add_qiskit_data( data = data or circuit.data for datum in data: instr, qargs, cargs = datum.operation, datum.qubits, datum.clbits + + qubits: list[Qubit] = [self.qbmap[qbit] for qbit in qargs] + bits: list[Bit] = [self.cbmap[bit] for bit in cargs] + condition_kwargs = {} if instr.condition is not None: - if type(instr.condition[0]) is ClassicalRegister: - cond_reg = self.cregmap[instr.condition[0]] - condition_kwargs = { - "condition_bits": [cond_reg[k] for k in range(len(cond_reg))], - "condition_value": instr.condition[1], - } - elif type(instr.condition[0]) is Clbit: - # .find_bit() returns type: - # tuple[index, list[tuple[ClassicalRegister, index]]] - # We assume each bit belongs to exactly one register. - index = circuit.find_bit(instr.condition[0])[0] - register = circuit.find_bit(instr.condition[0])[1][0][0] - cond_reg = self.cregmap[register] - condition_kwargs = { - "condition_bits": [cond_reg[index]], - "condition_value": instr.condition[1], - } - else: - raise NotImplementedError( - "condition must contain classical bit or register" - ) + condition_kwargs = _get_pytket_condition_kwargs( + instruction=instr, + cregmap=self.cregmap, + circuit=circuit, + ) # Controlled operations may be controlled on values other than all-1. Handle # this by prepending and appending X gates on the control qubits. @@ -463,9 +478,6 @@ def add_qiskit_data( # Handling of PauliEvolutionGate and UnitaryGate below optype = _optype_from_qiskit_instruction(instruction=instr) - qubits: list[Qubit] = [self.qbmap[qbit] for qbit in qargs] - bits: list[Bit] = [self.cbmap[bit] for bit in cargs] - if optype == OpType.QControlBox: params = [param_to_tk(p) for p in instr.base_gate.params] n_base_qubits = instr.base_gate.num_qubits From 4bb723da3f30894225ca0a26456cb188ac5a279a Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:35:25 +0100 Subject: [PATCH 20/23] remove duplicate import --- pytket/extensions/qiskit/qiskit_convert.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 5a8877b8..a17c84c7 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -20,7 +20,6 @@ Callable, Optional, Union, - Optional, Any, Iterable, cast, From 2731e6eb94b752ddeac0465beeb5f841cc6297ef Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:27:43 +0100 Subject: [PATCH 21/23] raise error for failed state prep conversion --- pytket/extensions/qiskit/qiskit_convert.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index e2e7fc0b..30dfd188 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -357,7 +357,6 @@ def _add_state_preparation( ) # Need to reverse qubits here (endian-ness) - # TODO pass same list of qubits to add_circuit reversed_qubits = list(reversed(qubits)) tkc.add_gate(pytket_state_prep_box, reversed_qubits) else: @@ -367,7 +366,10 @@ def _add_state_preparation( bit_string = bin(integer_parameter)[2:] circuit = _string_to_circuit(bit_string, prep.num_qubits, qiskit_prep=prep) tkc.add_circuit(circuit, qubits) - # TODO raise error + else: + raise TypeError( + "Unrecognised type of Instruction.params when trying to convert Initialize or StatePreparation instruction." + ) def _get_pytket_condition_kwargs( From b1895b45288559c268819a283aa40c55610b3312 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:39:47 +0100 Subject: [PATCH 22/23] shorten line for pylint --- pytket/extensions/qiskit/qiskit_convert.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 30dfd188..1704733d 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -368,7 +368,8 @@ def _add_state_preparation( tkc.add_circuit(circuit, qubits) else: raise TypeError( - "Unrecognised type of Instruction.params when trying to convert Initialize or StatePreparation instruction." + "Unrecognised type of Instruction.params " + + "when trying to convert Initialize or StatePreparation instruction." ) From 37de5e465c6d2e715399ffd34fb8663f8216c9ee Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:42:39 +0100 Subject: [PATCH 23/23] remove extra variable assignment and reduce indentation --- pytket/extensions/qiskit/qiskit_convert.py | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 1704733d..79559051 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -295,8 +295,8 @@ def _get_controlled_tket_optype(c_gate: ControlledGate) -> OpType: if c_gate.base_class in _known_qiskit_gate: # First we check if the gate is in _known_qiskit_gate # this avoids CZ being converted to CnZ - known_optype = _known_qiskit_gate[c_gate.base_class] - return known_optype + return _known_qiskit_gate[c_gate.base_class] + match c_gate.base_gate.base_class: case qiskit_gates.RYGate: return OpType.CnRy @@ -359,18 +359,17 @@ def _add_state_preparation( # Need to reverse qubits here (endian-ness) reversed_qubits = list(reversed(qubits)) tkc.add_gate(pytket_state_prep_box, reversed_qubits) + elif isinstance(prep.params[0], complex): + # convert int to a binary string and apply X for |1> + integer_parameter = int(prep.params[0].real) + bit_string = bin(integer_parameter)[2:] + circuit = _string_to_circuit(bit_string, prep.num_qubits, qiskit_prep=prep) + tkc.add_circuit(circuit, qubits) else: - if isinstance(prep.params[0], complex): - # convert int to a binary string and apply X for |1> - integer_parameter = int(prep.params[0].real) - bit_string = bin(integer_parameter)[2:] - circuit = _string_to_circuit(bit_string, prep.num_qubits, qiskit_prep=prep) - tkc.add_circuit(circuit, qubits) - else: - raise TypeError( - "Unrecognised type of Instruction.params " - + "when trying to convert Initialize or StatePreparation instruction." - ) + raise TypeError( + "Unrecognised type of Instruction.params " + + "when trying to convert Initialize or StatePreparation instruction." + ) def _get_pytket_condition_kwargs(