From 0cefb51b7d2e260ee190241f7df4e465af8b7561 Mon Sep 17 00:00:00 2001 From: Hosseinberg <34165958+Hosseinberg@users.noreply.github.com> Date: Mon, 24 Apr 2023 16:03:53 -0400 Subject: [PATCH 01/10] Add support for ControlledQubitUnitary --- pennylane_qulacs/qulacs_device.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pennylane_qulacs/qulacs_device.py b/pennylane_qulacs/qulacs_device.py index 216310f..4fcd4ed 100644 --- a/pennylane_qulacs/qulacs_device.py +++ b/pennylane_qulacs/qulacs_device.py @@ -21,7 +21,7 @@ import numpy as np from pennylane import QubitDevice, DeviceError -from pennylane.ops import QubitStateVector, BasisState, QubitUnitary, CRZ, PhaseShift, Adjoint +from pennylane.ops import QubitStateVector, BasisState, QubitUnitary, ControlledQubitUnitary, CRZ, PhaseShift, Adjoint import qulacs.gate as gate from qulacs import QuantumCircuit, QuantumState, Observable @@ -88,6 +88,7 @@ class QulacsDevice(QubitDevice): "QubitStateVector": None, "BasisState": None, "QubitUnitary": None, + "ControlledQubitUnitary": None, "Toffoli": gate.TOFFOLI, "CSWAP": gate.FREDKIN, "CRZ": crz, @@ -175,6 +176,8 @@ def apply_operations(self, operations): self._apply_basis_state(op) elif isinstance(op, QubitUnitary): self._apply_qubit_unitary(op, inverse) + elif isinstance(op, ControlledQubitUnitary): + self._apply_controlled_qubit_unitary(op, inverse) elif isinstance(op, (CRZ, PhaseShift)): self._apply_matrix(op, inverse) else: @@ -254,6 +257,30 @@ def _apply_qubit_unitary(self, op, inverse=False): self._circuit.add_gate(unitary_gate) unitary_gate.update_quantum_state(self._state) + def _apply_controlled_qubit_unitary(self, op, inverse=False): + """Apply controlled unitary to state""" + # translate op wire labels to consecutive wire labels used by the device + device_wires = self.map_wires(op.wires) + control_wires = self.map_wires(op.control_wires) + control_values = op.control_values + par = op.parameters + + if len(par[0]) != 2 ** len(device_wires): + raise ValueError("Unitary matrix must be of shape (2**wires, 2**wires).") + + if inverse: + par[0] = par[0].conj().T + + # reverse wires (could also change par[0]) + reverse_wire_labels = device_wires.tolist()[::-1] + reverse_control_wire_labels = control_wires.tolist()[::-1] + reverse_control_value = control_values[::-1] + unitary_gate = gate.DenseMatrix(reverse_wire_labels, par[0]) + for i,j in enumerate(reverse_control_wire_labels): + unitary_gate.add_control_qubit(j, reverse_control_value[i]) + self._circuit.add_gate(unitary_gate) + unitary_gate.update_quantum_state(self._state) + def _apply_matrix(self, op, inverse=False): """Apply predefined gate-matrix to state (must follow qulacs convention)""" # translate op wire labels to consecutive wire labels used by the device From 501654f981f7166e900de0213a91cda483be1a08 Mon Sep 17 00:00:00 2001 From: hosseinberg Date: Mon, 24 Apr 2023 23:33:36 +0000 Subject: [PATCH 02/10] adds target and control qubits --- pennylane_qulacs/qulacs_device.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pennylane_qulacs/qulacs_device.py b/pennylane_qulacs/qulacs_device.py index 4fcd4ed..db2a540 100644 --- a/pennylane_qulacs/qulacs_device.py +++ b/pennylane_qulacs/qulacs_device.py @@ -261,21 +261,22 @@ def _apply_controlled_qubit_unitary(self, op, inverse=False): """Apply controlled unitary to state""" # translate op wire labels to consecutive wire labels used by the device device_wires = self.map_wires(op.wires) + target_wires = self.map_wires(op.target_wires) control_wires = self.map_wires(op.control_wires) control_values = op.control_values par = op.parameters - if len(par[0]) != 2 ** len(device_wires): - raise ValueError("Unitary matrix must be of shape (2**wires, 2**wires).") + if len(par[0]) != 2 ** len(target_wires): + raise ValueError("Unitary matrix must be of shape (2**target_wires, 2**target_wires).") if inverse: par[0] = par[0].conj().T # reverse wires (could also change par[0]) - reverse_wire_labels = device_wires.tolist()[::-1] + reverse_target_wire_labels = target_wires.tolist()[::-1] reverse_control_wire_labels = control_wires.tolist()[::-1] reverse_control_value = control_values[::-1] - unitary_gate = gate.DenseMatrix(reverse_wire_labels, par[0]) + unitary_gate = gate.DenseMatrix(reverse_target_wire_labels, par[0]) for i,j in enumerate(reverse_control_wire_labels): unitary_gate.add_control_qubit(j, reverse_control_value[i]) self._circuit.add_gate(unitary_gate) From 4a48c278d1b9123815ade2074495daa997dcce36 Mon Sep 17 00:00:00 2001 From: Hosseinberg <34165958+Hosseinberg@users.noreply.github.com> Date: Tue, 25 Apr 2023 00:31:34 -0400 Subject: [PATCH 03/10] adds some checks --- pennylane_qulacs/qulacs_device.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pennylane_qulacs/qulacs_device.py b/pennylane_qulacs/qulacs_device.py index db2a540..eb1638e 100644 --- a/pennylane_qulacs/qulacs_device.py +++ b/pennylane_qulacs/qulacs_device.py @@ -177,7 +177,10 @@ def apply_operations(self, operations): elif isinstance(op, QubitUnitary): self._apply_qubit_unitary(op, inverse) elif isinstance(op, ControlledQubitUnitary): - self._apply_controlled_qubit_unitary(op, inverse) + if len(op.control_wires) == 0: + self._apply_qubit_unitary(op, inverse) + else: + self._apply_controlled_qubit_unitary(op, inverse) elif isinstance(op, (CRZ, PhaseShift)): self._apply_matrix(op, inverse) else: @@ -266,6 +269,9 @@ def _apply_controlled_qubit_unitary(self, op, inverse=False): control_values = op.control_values par = op.parameters + if len(control_wires) + len(target_wires) == len(device_wires): + raise ValueError("len(device_wire) should be equal to len(control_wires) + len(target_wires)") + if len(par[0]) != 2 ** len(target_wires): raise ValueError("Unitary matrix must be of shape (2**target_wires, 2**target_wires).") From 3efa677f579e6b778228a15f0f94e64e4b62cfce Mon Sep 17 00:00:00 2001 From: Hosseinberg <34165958+Hosseinberg@users.noreply.github.com> Date: Tue, 25 Apr 2023 01:00:35 -0400 Subject: [PATCH 04/10] nit --- pennylane_qulacs/qulacs_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_qulacs/qulacs_device.py b/pennylane_qulacs/qulacs_device.py index eb1638e..45779f5 100644 --- a/pennylane_qulacs/qulacs_device.py +++ b/pennylane_qulacs/qulacs_device.py @@ -269,7 +269,7 @@ def _apply_controlled_qubit_unitary(self, op, inverse=False): control_values = op.control_values par = op.parameters - if len(control_wires) + len(target_wires) == len(device_wires): + if len(control_wires) + len(target_wires) != len(device_wires): raise ValueError("len(device_wire) should be equal to len(control_wires) + len(target_wires)") if len(par[0]) != 2 ** len(target_wires): From f52651ec8e5504d6380adef4601e895ebe388236 Mon Sep 17 00:00:00 2001 From: hosseinberg Date: Wed, 26 Apr 2023 04:24:56 +0000 Subject: [PATCH 05/10] adds test --- tests/test_apply.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_apply.py b/tests/test_apply.py index a8991f0..c727f03 100755 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -249,6 +249,34 @@ def test_qubit_unitary(self, init_state, mat, tol): expected = mat @ state assert np.allclose(res, expected, tol) + @pytest.mark.parametrize("control_wires", [[0], [0, 1]]) + @pytest.mark.parametrize("mat", [U, U2]) + def test_controlled_qubit_unitary(self, init_state, mat, control_wires, tol): + """Test ControlledQubitUnitary application""" + + N = int(np.log2(len(mat))) + dev = QulacsDevice(N + len(control_wires)) + state = init_state(N + len(control_wires)) + #import pdb + #pdb.set_trace() + + op = qml.ControlledQubitUnitary(mat, wires=list(range(len(control_wires),N+len(control_wires))), control_wires=control_wires) + dev.apply([qml.QubitStateVector(state, wires=list(range(N + len(control_wires)))), op]) + dev._obs_queue = [] + + res = dev.state + zero_op = np.array([[1,0], [0,0]]) + one_op = np.array([[0,0], [0,1]]) + if control_wires == [0]: + expected = (np.kron(zero_op, np.eye(2 ** N)) + np.kron(one_op, mat)) @ state + elif control_wires == [0, 1]: + expected = (np.kron(np.kron(zero_op, zero_op), np.eye(2 ** N)) + + np.kron(np.kron(zero_op, one_op), np.eye(2 ** N)) + + np.kron(np.kron(one_op, zero_op), np.eye(2 ** N)) + + np.kron(np.kron(one_op, one_op), mat)) @ state + + assert np.allclose(res, expected, tol) + def test_invalid_qubit_state_unitary(self): """Test that an exception is raised if the unitary matrix is the wrong size""" From 611ef31939492676f1770240e1455946c9a437c2 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 26 Apr 2023 04:42:45 +0000 Subject: [PATCH 06/10] nit --- tests/test_apply.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_apply.py b/tests/test_apply.py index c727f03..68db616 100755 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -257,16 +257,15 @@ def test_controlled_qubit_unitary(self, init_state, mat, control_wires, tol): N = int(np.log2(len(mat))) dev = QulacsDevice(N + len(control_wires)) state = init_state(N + len(control_wires)) - #import pdb - #pdb.set_trace() - op = qml.ControlledQubitUnitary(mat, wires=list(range(len(control_wires),N+len(control_wires))), control_wires=control_wires) + op = qml.ControlledQubitUnitary(mat, wires=list(range(len(control_wires), N+len(control_wires))), + control_wires=control_wires) dev.apply([qml.QubitStateVector(state, wires=list(range(N + len(control_wires)))), op]) dev._obs_queue = [] res = dev.state - zero_op = np.array([[1,0], [0,0]]) - one_op = np.array([[0,0], [0,1]]) + zero_op = np.array([[1, 0], [0, 0]]) + one_op = np.array([[0, 0], [0, 1]]) if control_wires == [0]: expected = (np.kron(zero_op, np.eye(2 ** N)) + np.kron(one_op, mat)) @ state elif control_wires == [0, 1]: From 016238ccb2bbd5420fa9240ad30d96fd14651861 Mon Sep 17 00:00:00 2001 From: Hosseinberg <34165958+Hosseinberg@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:58:43 -0400 Subject: [PATCH 07/10] getting N more accurately Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> --- tests/test_apply.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_apply.py b/tests/test_apply.py index 68db616..eb6dc54 100755 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -254,7 +254,7 @@ def test_qubit_unitary(self, init_state, mat, tol): def test_controlled_qubit_unitary(self, init_state, mat, control_wires, tol): """Test ControlledQubitUnitary application""" - N = int(np.log2(len(mat))) + N = int(np.round(np.log2(len(mat)))) dev = QulacsDevice(N + len(control_wires)) state = init_state(N + len(control_wires)) From b63973097bef0cbd4336938be4febb1f0e782193 Mon Sep 17 00:00:00 2001 From: Hosseinberg <34165958+Hosseinberg@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:59:07 -0400 Subject: [PATCH 08/10] Update pennylane_qulacs/qulacs_device.py Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> --- pennylane_qulacs/qulacs_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_qulacs/qulacs_device.py b/pennylane_qulacs/qulacs_device.py index 45779f5..84f77ad 100644 --- a/pennylane_qulacs/qulacs_device.py +++ b/pennylane_qulacs/qulacs_device.py @@ -267,7 +267,7 @@ def _apply_controlled_qubit_unitary(self, op, inverse=False): target_wires = self.map_wires(op.target_wires) control_wires = self.map_wires(op.control_wires) control_values = op.control_values - par = op.parameters + par = op.base.matrix() if len(control_wires) + len(target_wires) != len(device_wires): raise ValueError("len(device_wire) should be equal to len(control_wires) + len(target_wires)") From 52a8d7c4d557bbcaf58f7f9ed048b23a641bc9de Mon Sep 17 00:00:00 2001 From: Hosseinberg <34165958+Hosseinberg@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:54:11 -0400 Subject: [PATCH 09/10] Update pennylane_qulacs/qulacs_device.py Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> --- pennylane_qulacs/qulacs_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_qulacs/qulacs_device.py b/pennylane_qulacs/qulacs_device.py index 84f77ad..41f2400 100644 --- a/pennylane_qulacs/qulacs_device.py +++ b/pennylane_qulacs/qulacs_device.py @@ -276,7 +276,7 @@ def _apply_controlled_qubit_unitary(self, op, inverse=False): raise ValueError("Unitary matrix must be of shape (2**target_wires, 2**target_wires).") if inverse: - par[0] = par[0].conj().T + par = par.conj().T # reverse wires (could also change par[0]) reverse_target_wire_labels = target_wires.tolist()[::-1] From cbe163266354fb0cd3cf4e94172302a8e5df88c9 Mon Sep 17 00:00:00 2001 From: Hosseinberg <34165958+Hosseinberg@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:54:26 -0400 Subject: [PATCH 10/10] Update pennylane_qulacs/qulacs_device.py Co-authored-by: Frederik Wilde <42576579+frederikwilde@users.noreply.github.com> --- pennylane_qulacs/qulacs_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_qulacs/qulacs_device.py b/pennylane_qulacs/qulacs_device.py index 41f2400..4123c82 100644 --- a/pennylane_qulacs/qulacs_device.py +++ b/pennylane_qulacs/qulacs_device.py @@ -272,7 +272,7 @@ def _apply_controlled_qubit_unitary(self, op, inverse=False): if len(control_wires) + len(target_wires) != len(device_wires): raise ValueError("len(device_wire) should be equal to len(control_wires) + len(target_wires)") - if len(par[0]) != 2 ** len(target_wires): + if qml.math.shape(par) != (2 ** len(target_wires), 2 ** len(target_wires)): raise ValueError("Unitary matrix must be of shape (2**target_wires, 2**target_wires).") if inverse: