diff --git a/lambeq/backend/converters/tk.py b/lambeq/backend/converters/tk.py index 3f8934f3..3cee41ad 100644 --- a/lambeq/backend/converters/tk.py +++ b/lambeq/backend/converters/tk.py @@ -204,9 +204,10 @@ def to_tk(circuit: Diagram): qubits: list[int] = [] circuit = circuit.init_and_discard() - def remove_ket1(_, box: Box) -> Diagram | Box: + def remove_ketbra1(_, box: Box) -> Diagram | Box: ob_map: dict[Box, Diagram] - ob_map = {Ket(1): Ket(0) >> X} # type: ignore[dict-item] + ob_map = {Ket(1): Ket(0) >> X, # type: ignore[dict-item] + Bra(1): X >> Bra(0)} # type: ignore[dict-item] return ob_map.get(box, box) def prepare_qubits(qubits: list[int], @@ -313,7 +314,7 @@ def add_gate(qubits: list[int], box: Box, offset: int) -> None: circuit = Functor(target_category=quantum, # type: ignore [assignment] ob=lambda _, x: x, - ar=remove_ket1)(circuit) # type: ignore [arg-type] + ar=remove_ketbra1)(circuit) # type: ignore [arg-type] for left, box, _ in circuit: if isinstance(box, Ket): qubits = prepare_qubits(qubits, box, left.count(qubit)) diff --git a/lambeq/backend/pennylane.py b/lambeq/backend/pennylane.py index 4724b355..77cc9010 100644 --- a/lambeq/backend/pennylane.py +++ b/lambeq/backend/pennylane.py @@ -42,6 +42,7 @@ from __future__ import annotations from itertools import product +import sys from typing import TYPE_CHECKING import pennylane as qml @@ -49,7 +50,7 @@ import sympy import torch -from lambeq.backend.quantum import Scalar +from lambeq.backend.quantum import Measure, Scalar if TYPE_CHECKING: from lambeq.backend.quantum import Diagram @@ -216,9 +217,16 @@ def to_pennylane(lambeq_circuit: Diagram, probabilities=False, The PennyLane circuit equivalent to the input lambeq circuit. """ - if lambeq_circuit.is_mixed: - raise ValueError('Only pure quantum circuits are currently ' - 'supported.') + + if any(isinstance(box, Measure) for box in lambeq_circuit.boxes): + raise ValueError('Only pure circuits, or circuits with discards' + ' are currently supported.') + + if lambeq_circuit.is_mixed and lambeq_circuit.cod: + # Some qubits discarded, some left open + print('Warning: Circuit includes both discards and open codomain' + ' wires. All open wires will be discarded during conversion', + file=sys.stderr) tk_circ = lambeq_circuit.to_tk() op_list, params_list, wires_list, symbols_set = ( @@ -238,6 +246,7 @@ def to_pennylane(lambeq_circuit: Diagram, probabilities=False, wires_list, probabilities, post_selection, + lambeq_circuit.is_mixed, scalar, tk_circ.n_qubits, backend_config, @@ -252,7 +261,7 @@ class PennyLaneCircuit: """Implement a pennylane circuit with post-selection.""" def __init__(self, ops, symbols, params, wires, probabilities, - post_selection, scale, n_qubits, backend_config, + post_selection, mixed, scale, n_qubits, backend_config, diff_method): self._ops = ops self._symbols = symbols @@ -260,6 +269,7 @@ def __init__(self, ops, symbols, params, wires, probabilities, self._wires = wires self._probabilities = probabilities self._post_selection = post_selection + self._mixed = mixed self._scale = scale self._n_qubits = n_qubits self._backend_config = backend_config @@ -400,6 +410,8 @@ def circuit(circ_params): for op, params, wires in zip(self._ops, circ_params, self._wires): op(*[2 * torch.pi * p for p in params], wires=wires) + if self._mixed: + return qml.density_matrix(self._post_selection.keys()) if self._probabilities: return qml.probs(wires=range(self._n_qubits)) else: @@ -424,6 +436,10 @@ def post_selected_circuit(self, params): """ states = self._circuit(params) + if self._mixed: + # Select the all-zeros subsystem + return states[0][0] + open_wires = self._n_qubits - len(self._post_selection) post_selected_states = states[self._valid_states] post_selected_states *= (self._scale ** 2 if self._probabilities diff --git a/tests/backend/test_pennylane_interface.py b/tests/backend/test_pennylane_interface.py index 0bb17c32..072aa255 100644 --- a/tests/backend/test_pennylane_interface.py +++ b/tests/backend/test_pennylane_interface.py @@ -98,6 +98,16 @@ def test_pennylane_circuit_mixed_error(): snake.to_pennylane() +def test_pennylane_circuit_mixed_warning(capsys): + bell_state = Diagram.caps(qubit, qubit) + bell_discarded = bell_state >> Discard() @ Id(qubit) + _ = bell_discarded.to_pennylane() + captured = capsys.readouterr() + assert captured.err == ('Warning: Circuit includes both discards and open ' + 'codomain wires. All open wires will be discarded ' + 'during conversion\n') + + def test_pennylane_circuit_draw(capsys): bell_state = Diagram.caps(qubit, qubit) bell_effect = bell_state[::-1]