From 50efc33dd57b3dae6dde6ad2bca3269fa822b773 Mon Sep 17 00:00:00 2001 From: dwierichs Date: Wed, 22 Dec 2021 11:32:22 +0100 Subject: [PATCH 1/7] suggested change --- doc/releases/changelog-dev.md | 6 +++++- pennylane/devices/default_qubit.py | 4 +++- pennylane/devices/default_qubit_autograd.py | 1 + pennylane/devices/default_qubit_jax.py | 1 + pennylane/devices/default_qubit_tf.py | 1 + pennylane/devices/default_qubit_torch.py | 1 + 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 483da06aaef..db5c291ce35 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -134,6 +134,10 @@

Bug fixes

+* Fixes a bug in `DefaultQubit` where the second derivative of QNodes at + positions corresponding to vanishing state vector amplitudes is wrong. + [(#20xx)](https://github.com/PennyLaneAI/pennylane/pull/20xx) + * Fixes a bug where PennyLane didn't require v0.20.0 of PennyLane-Lightning, but raised an error with versions of Lightning earlier than v0.20.0 due to the new batch execution pipeline. @@ -159,4 +163,4 @@ This release contains contributions from (in alphabetical order): -Juan Miguel Arrazola, Ali Asadi, Esther Cruz, Olivia Di Matteo, Diego Guala, Ankit Khandelwal, Antal Száva, David Wierichs, Shaoming Zhang \ No newline at end of file +Juan Miguel Arrazola, Ali Asadi, Esther Cruz, Olivia Di Matteo, Diego Guala, Ankit Khandelwal, Antal Száva, David Wierichs, Shaoming Zhang diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index a368407cc82..21ae8b5621c 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -789,5 +789,7 @@ def analytic_probability(self, wires=None): if self._state is None: return None - prob = self.marginal_prob(self._abs(self._flatten(self._state)) ** 2, wires) + real_state = self._real(self._flatten(self._state)) + imag_state = self._imag(self._flatten(self._state)) + prob = self.marginal_prob(real_state ** 2 + imag_state ** 2, wires) return prob diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py index 260ca58c836..0fa762e59af 100644 --- a/pennylane/devices/default_qubit_autograd.py +++ b/pennylane/devices/default_qubit_autograd.py @@ -92,6 +92,7 @@ class DefaultQubitAutograd(DefaultQubit): _transpose = staticmethod(np.transpose) _tensordot = staticmethod(np.tensordot) _conj = staticmethod(np.conj) + _real = staticmethod(np.real) _imag = staticmethod(np.imag) _roll = staticmethod(np.roll) _stack = staticmethod(np.stack) diff --git a/pennylane/devices/default_qubit_jax.py b/pennylane/devices/default_qubit_jax.py index b54d59a838a..1a218f077cf 100644 --- a/pennylane/devices/default_qubit_jax.py +++ b/pennylane/devices/default_qubit_jax.py @@ -148,6 +148,7 @@ def circuit(): ) ) _conj = staticmethod(jnp.conj) + _real = staticmethod(jnp.real) _imag = staticmethod(jnp.imag) _roll = staticmethod(jnp.roll) _stack = staticmethod(jnp.stack) diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py index 521384e4736..d569dc7afaf 100644 --- a/pennylane/devices/default_qubit_tf.py +++ b/pennylane/devices/default_qubit_tf.py @@ -140,6 +140,7 @@ class DefaultQubitTF(DefaultQubit): _transpose = staticmethod(tf.transpose) _tensordot = staticmethod(tf.tensordot) _conj = staticmethod(tf.math.conj) + _real = staticmethod(tf.math.real) _imag = staticmethod(tf.math.imag) _roll = staticmethod(tf.roll) _stack = staticmethod(tf.stack) diff --git a/pennylane/devices/default_qubit_torch.py b/pennylane/devices/default_qubit_torch.py index 71838cbe074..30125d1c204 100644 --- a/pennylane/devices/default_qubit_torch.py +++ b/pennylane/devices/default_qubit_torch.py @@ -150,6 +150,7 @@ def circuit(x): _transpose = staticmethod(lambda a, axes=None: a.permute(*axes)) _asnumpy = staticmethod(lambda x: x.cpu().numpy()) _conj = staticmethod(torch.conj) + _real = staticmethod(torch.real) _imag = staticmethod(torch.imag) _norm = staticmethod(torch.norm) _flatten = staticmethod(torch.flatten) From 5f12f101c575e7343aaffd038b21d645f42cca43 Mon Sep 17 00:00:00 2001 From: dwierichs Date: Wed, 22 Dec 2021 11:51:39 +0100 Subject: [PATCH 2/7] add tests for special hessian bug --- tests/devices/test_default_qubit_autograd.py | 17 ++++++++++++ tests/devices/test_default_qubit_jax.py | 16 ++++++++++++ tests/devices/test_default_qubit_tf.py | 27 ++++++++++++++++++++ tests/devices/test_default_qubit_torch.py | 20 +++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/tests/devices/test_default_qubit_autograd.py b/tests/devices/test_default_qubit_autograd.py index 00d7e553a6f..c09f5720ac9 100644 --- a/tests/devices/test_default_qubit_autograd.py +++ b/tests/devices/test_default_qubit_autograd.py @@ -280,6 +280,23 @@ def circuit(a, b): ) assert np.allclose(res, expected_grad, atol=tol, rtol=0) + @pytest.mark.parametrize("x, shift", [(0., 0.), (0.5, -0.5)]) + def test_hessian_at_zero(self, x, shift): + """Tests that the Hessian at vanishing state vector amplitudes + is correct.""" + dev = qml.device("default.qubit.autograd", wires=1) + + @qml.qnode(dev, interface="autograd") + def circuit(x): + qml.RY(shift, wires=0) + qml.RY(x, wires=0) + return qml.expval(qml.PauliZ(0)) + + assert qml.math.isclose(qml.jacobian(circuit)(x), 0.0) + assert qml.math.isclose(qml.jacobian(qml.jacobian(circuit))(x), -1.0) + assert qml.math.isclose(qml.grad(qml.grad(circuit))(x), -1.0) + + @pytest.mark.parametrize("operation", [qml.U3, qml.U3.decomposition]) @pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "finite-diff"]) def test_autograd_interface_gradient(self, operation, diff_method, tol): diff --git a/tests/devices/test_default_qubit_jax.py b/tests/devices/test_default_qubit_jax.py index 9187771b7bb..da4ef91f44b 100644 --- a/tests/devices/test_default_qubit_jax.py +++ b/tests/devices/test_default_qubit_jax.py @@ -543,6 +543,22 @@ def circuit(a, b): assert jnp.allclose(jnp.array(res), jnp.array(expected_grad), atol=tol, rtol=0) + @pytest.mark.parametrize("x, shift", [(0., 0.), (0.5, -0.5)]) + def test_hessian_at_zero(self, x, shift): + """Tests that the Hessian at vanishing state vector amplitudes + is correct.""" + dev = qml.device("default.qubit.jax", wires=1) + + @qml.qnode(dev, interface="jax") + def circuit(x): + qml.RY(shift, wires=0) + qml.RY(x, wires=0) + return qml.expval(qml.PauliZ(0)) + + assert qml.math.isclose(jax.grad(circuit)(x), 0.0) + assert qml.math.isclose(jax.jacobian(jax.jacobian(circuit))(x), -1.0) + assert qml.math.isclose(jax.grad(jax.grad(circuit))(x), -1.0) + @pytest.mark.parametrize("operation", [qml.U3, qml.U3.decomposition]) @pytest.mark.parametrize("diff_method", ["backprop"]) def test_jax_interface_gradient(self, operation, diff_method, tol): diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py index 416060c432e..78fc5f5deab 100644 --- a/tests/devices/test_default_qubit_tf.py +++ b/tests/devices/test_default_qubit_tf.py @@ -1323,6 +1323,33 @@ def circuit(a, b): res = tape.gradient(res, [a_tf, b_tf]) assert np.allclose(res, expected_grad, atol=tol, rtol=0) + @pytest.mark.parametrize("x, shift", [(0., 0.), (0.5, -0.5)]) + def test_hessian_at_zero(self, x, shift): + """Tests that the Hessian at vanishing state vector amplitudes + is correct.""" + dev = qml.device("default.qubit.tf", wires=1) + + shift = tf.constant(shift) + x = tf.Variable(x) + + @qml.qnode(dev, interface="tf") + def circuit(x): + qml.RY(shift, wires=0) + qml.RY(x, wires=0) + return qml.expval(qml.PauliZ(0)) + + with tf.GradientTape(persistent=True) as t2: + with tf.GradientTape(persistent=True) as t1: + value = circuit(x) + grad = t1.gradient(value, x) + jac = t1.jacobian(value, x) + hess_grad = t2.gradient(grad, x) + hess_jac = t2.jacobian(jac, x) + + assert qml.math.isclose(grad, 0.0) + assert qml.math.isclose(hess_grad, -1.0) + assert qml.math.isclose(hess_jac, -1.0) + @pytest.mark.parametrize("operation", [qml.U3, qml.U3.decomposition]) @pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "finite-diff"]) def test_tf_interface_gradient(self, operation, diff_method, tol): diff --git a/tests/devices/test_default_qubit_torch.py b/tests/devices/test_default_qubit_torch.py index a3605f528ad..c68c604ddef 100644 --- a/tests/devices/test_default_qubit_torch.py +++ b/tests/devices/test_default_qubit_torch.py @@ -1425,6 +1425,26 @@ def circuit(a, b): assert torch.allclose(a.grad, -0.5 * torch.sin(a) * (torch.cos(b) + 1), atol=tol, rtol=0) assert torch.allclose(b.grad, 0.5 * torch.sin(b) * (1 - torch.cos(a))) + @pytest.mark.parametrize("x, shift", [(0., 0.), (0.5, -0.5)]) + def test_hessian_at_zero(self, torch_device, x, shift): + """Tests that the Hessian at vanishing state vector amplitudes + is correct.""" + dev = qml.device("default.qubit.torch", wires=1, torch_device=torch_device) + + x = torch.tensor(x, requires_grad=True) + + @qml.qnode(dev, interface="torch") + def circuit(x): + qml.RY(shift, wires=0) + qml.RY(x, wires=0) + return qml.expval(qml.PauliZ(0)) + + grad = torch.autograd.functional.jacobian(circuit, x) + hess = torch.autograd.functional.hessian(circuit, x) + + assert qml.math.isclose(grad, torch.tensor(0.0)) + assert qml.math.isclose(hess, torch.tensor(-1.0)) + @pytest.mark.parametrize("operation", [qml.U3, qml.U3.decomposition]) @pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "finite-diff"]) def test_torch_interface_gradient(self, torch_device, operation, diff_method, tol): From 14fa3d4b2093d147b14cbec73a338a5b1db9378d Mon Sep 17 00:00:00 2001 From: dwierichs Date: Wed, 22 Dec 2021 11:59:42 +0100 Subject: [PATCH 3/7] improvement --- pennylane/devices/default_qubit.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index 21ae8b5621c..beb9e45c22c 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -789,7 +789,8 @@ def analytic_probability(self, wires=None): if self._state is None: return None - real_state = self._real(self._flatten(self._state)) - imag_state = self._imag(self._flatten(self._state)) + self._flatten(self._state) + real_state = self._real(flat_state) + imag_state = self._imag(flat_state) prob = self.marginal_prob(real_state ** 2 + imag_state ** 2, wires) return prob From 6e5f76385782331e863c588b1f7259713cef57a9 Mon Sep 17 00:00:00 2001 From: dwierichs Date: Wed, 22 Dec 2021 12:00:00 +0100 Subject: [PATCH 4/7] PR number --- doc/releases/changelog-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index db5c291ce35..527bb68bde1 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -136,7 +136,7 @@ * Fixes a bug in `DefaultQubit` where the second derivative of QNodes at positions corresponding to vanishing state vector amplitudes is wrong. - [(#20xx)](https://github.com/PennyLaneAI/pennylane/pull/20xx) + [(#2057)](https://github.com/PennyLaneAI/pennylane/pull/2057) * Fixes a bug where PennyLane didn't require v0.20.0 of PennyLane-Lightning, but raised an error with versions of Lightning earlier than v0.20.0 due to From 52a93124a200c97ac503b33106fbe25b4198990b Mon Sep 17 00:00:00 2001 From: dwierichs Date: Wed, 22 Dec 2021 12:00:11 +0100 Subject: [PATCH 5/7] black --- tests/devices/test_default_qubit_autograd.py | 3 +-- tests/devices/test_default_qubit_jax.py | 2 +- tests/devices/test_default_qubit_tf.py | 2 +- tests/devices/test_default_qubit_torch.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/devices/test_default_qubit_autograd.py b/tests/devices/test_default_qubit_autograd.py index c09f5720ac9..11e57036482 100644 --- a/tests/devices/test_default_qubit_autograd.py +++ b/tests/devices/test_default_qubit_autograd.py @@ -280,7 +280,7 @@ def circuit(a, b): ) assert np.allclose(res, expected_grad, atol=tol, rtol=0) - @pytest.mark.parametrize("x, shift", [(0., 0.), (0.5, -0.5)]) + @pytest.mark.parametrize("x, shift", [(0.0, 0.0), (0.5, -0.5)]) def test_hessian_at_zero(self, x, shift): """Tests that the Hessian at vanishing state vector amplitudes is correct.""" @@ -296,7 +296,6 @@ def circuit(x): assert qml.math.isclose(qml.jacobian(qml.jacobian(circuit))(x), -1.0) assert qml.math.isclose(qml.grad(qml.grad(circuit))(x), -1.0) - @pytest.mark.parametrize("operation", [qml.U3, qml.U3.decomposition]) @pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "finite-diff"]) def test_autograd_interface_gradient(self, operation, diff_method, tol): diff --git a/tests/devices/test_default_qubit_jax.py b/tests/devices/test_default_qubit_jax.py index da4ef91f44b..148a7445ee3 100644 --- a/tests/devices/test_default_qubit_jax.py +++ b/tests/devices/test_default_qubit_jax.py @@ -543,7 +543,7 @@ def circuit(a, b): assert jnp.allclose(jnp.array(res), jnp.array(expected_grad), atol=tol, rtol=0) - @pytest.mark.parametrize("x, shift", [(0., 0.), (0.5, -0.5)]) + @pytest.mark.parametrize("x, shift", [(0.0, 0.0), (0.5, -0.5)]) def test_hessian_at_zero(self, x, shift): """Tests that the Hessian at vanishing state vector amplitudes is correct.""" diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py index 78fc5f5deab..d3fb8c53674 100644 --- a/tests/devices/test_default_qubit_tf.py +++ b/tests/devices/test_default_qubit_tf.py @@ -1323,7 +1323,7 @@ def circuit(a, b): res = tape.gradient(res, [a_tf, b_tf]) assert np.allclose(res, expected_grad, atol=tol, rtol=0) - @pytest.mark.parametrize("x, shift", [(0., 0.), (0.5, -0.5)]) + @pytest.mark.parametrize("x, shift", [(0.0, 0.0), (0.5, -0.5)]) def test_hessian_at_zero(self, x, shift): """Tests that the Hessian at vanishing state vector amplitudes is correct.""" diff --git a/tests/devices/test_default_qubit_torch.py b/tests/devices/test_default_qubit_torch.py index c68c604ddef..0d89ea667ec 100644 --- a/tests/devices/test_default_qubit_torch.py +++ b/tests/devices/test_default_qubit_torch.py @@ -1425,7 +1425,7 @@ def circuit(a, b): assert torch.allclose(a.grad, -0.5 * torch.sin(a) * (torch.cos(b) + 1), atol=tol, rtol=0) assert torch.allclose(b.grad, 0.5 * torch.sin(b) * (1 - torch.cos(a))) - @pytest.mark.parametrize("x, shift", [(0., 0.), (0.5, -0.5)]) + @pytest.mark.parametrize("x, shift", [(0.0, 0.0), (0.5, -0.5)]) def test_hessian_at_zero(self, torch_device, x, shift): """Tests that the Hessian at vanishing state vector amplitudes is correct.""" From cddd1c36c3295242505a0e4c8f309cdc51c66713 Mon Sep 17 00:00:00 2001 From: dwierichs Date: Wed, 22 Dec 2021 12:00:50 +0100 Subject: [PATCH 6/7] typo --- pennylane/devices/default_qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index beb9e45c22c..1c8828d8cb3 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -789,7 +789,7 @@ def analytic_probability(self, wires=None): if self._state is None: return None - self._flatten(self._state) + flat_state = self._flatten(self._state) real_state = self._real(flat_state) imag_state = self._imag(flat_state) prob = self.marginal_prob(real_state ** 2 + imag_state ** 2, wires) From 553e8c3501ca7d7dcd8ffe2b7d0c48da1feebe78 Mon Sep 17 00:00:00 2001 From: David Wierichs Date: Wed, 22 Dec 2021 13:13:05 +0100 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Josh Izaac --- tests/devices/test_default_qubit_autograd.py | 2 +- tests/devices/test_default_qubit_jax.py | 2 +- tests/devices/test_default_qubit_tf.py | 2 +- tests/devices/test_default_qubit_torch.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/devices/test_default_qubit_autograd.py b/tests/devices/test_default_qubit_autograd.py index 11e57036482..a707d777c4f 100644 --- a/tests/devices/test_default_qubit_autograd.py +++ b/tests/devices/test_default_qubit_autograd.py @@ -286,7 +286,7 @@ def test_hessian_at_zero(self, x, shift): is correct.""" dev = qml.device("default.qubit.autograd", wires=1) - @qml.qnode(dev, interface="autograd") + @qml.qnode(dev, interface="autograd", diff_method="backprop") def circuit(x): qml.RY(shift, wires=0) qml.RY(x, wires=0) diff --git a/tests/devices/test_default_qubit_jax.py b/tests/devices/test_default_qubit_jax.py index 148a7445ee3..4c2221268d3 100644 --- a/tests/devices/test_default_qubit_jax.py +++ b/tests/devices/test_default_qubit_jax.py @@ -549,7 +549,7 @@ def test_hessian_at_zero(self, x, shift): is correct.""" dev = qml.device("default.qubit.jax", wires=1) - @qml.qnode(dev, interface="jax") + @qml.qnode(dev, interface="jax", diff_method="backprop") def circuit(x): qml.RY(shift, wires=0) qml.RY(x, wires=0) diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py index d3fb8c53674..dcb79f78d22 100644 --- a/tests/devices/test_default_qubit_tf.py +++ b/tests/devices/test_default_qubit_tf.py @@ -1332,7 +1332,7 @@ def test_hessian_at_zero(self, x, shift): shift = tf.constant(shift) x = tf.Variable(x) - @qml.qnode(dev, interface="tf") + @qml.qnode(dev, interface="tf", diff_method="backprop") def circuit(x): qml.RY(shift, wires=0) qml.RY(x, wires=0) diff --git a/tests/devices/test_default_qubit_torch.py b/tests/devices/test_default_qubit_torch.py index 0d89ea667ec..8e3371a2bf7 100644 --- a/tests/devices/test_default_qubit_torch.py +++ b/tests/devices/test_default_qubit_torch.py @@ -1433,7 +1433,7 @@ def test_hessian_at_zero(self, torch_device, x, shift): x = torch.tensor(x, requires_grad=True) - @qml.qnode(dev, interface="torch") + @qml.qnode(dev, interface="torch", diff_method="backprop") def circuit(x): qml.RY(shift, wires=0) qml.RY(x, wires=0)