diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index e1f25e61327..39f56814de4 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -128,6 +128,10 @@ with QNodes. [(#4831)](https://github.com/PennyLaneAI/pennylane/pull/4831) +* `qml.pow(op)` and `qml.QubitUnitary.pow()` now also work with Tensorflow data raised to an + integer power. + [(#4827)](https://github.com/PennyLaneAI/pennylane/pull/4827) +

Contributors ✍️

This release contains contributions from (in alphabetical order): diff --git a/pennylane/ops/op_math/pow.py b/pennylane/ops/op_math/pow.py index 0d865463623..1efcde6752d 100644 --- a/pennylane/ops/op_math/pow.py +++ b/pennylane/ops/op_math/pow.py @@ -259,7 +259,7 @@ def label(self, decimals=None, base_label=None, cache=None): @staticmethod def _matrix(scalar, mat): - if isinstance(scalar, int): + if isinstance(scalar, int) and qml.math.get_deep_interface(mat) != "tensorflow": return qmlmath.linalg.matrix_power(mat, scalar) return fractional_matrix_power(mat, scalar) diff --git a/pennylane/ops/qubit/matrix_ops.py b/pennylane/ops/qubit/matrix_ops.py index eca006e0529..a88905925da 100644 --- a/pennylane/ops/qubit/matrix_ops.py +++ b/pennylane/ops/qubit/matrix_ops.py @@ -20,6 +20,7 @@ from itertools import product import numpy as np +from scipy.linalg import fractional_matrix_power from pennylane.math import norm, cast, eye, zeros, transpose, conj, sqrt, sqrt_matrix from pennylane import numpy as pnp @@ -232,9 +233,14 @@ def adjoint(self): return QubitUnitary(qml.math.moveaxis(qml.math.conj(U), -2, -1), wires=self.wires) def pow(self, z): - if isinstance(z, int): - return [QubitUnitary(qml.math.linalg.matrix_power(self.matrix(), z), wires=self.wires)] - return super().pow(z) + mat = self.matrix() + if isinstance(z, int) and qml.math.get_deep_interface(mat) != "tensorflow": + pow_mat = qml.math.linalg.matrix_power(mat, z) + elif self.batch_size is not None or qml.math.shape(z) != (): + return super().pow(z) + else: + pow_mat = qml.math.convert_like(fractional_matrix_power(mat, z), mat) + return [QubitUnitary(pow_mat, wires=self.wires)] def _controlled(self, wire): return qml.ControlledQubitUnitary(*self.parameters, control_wires=wire, wires=self.wires) diff --git a/tests/ops/op_math/test_controlled_ops.py b/tests/ops/op_math/test_controlled_ops.py index 6dafd4ba8ff..e777b688546 100644 --- a/tests/ops/op_math/test_controlled_ops.py +++ b/tests/ops/op_math/test_controlled_ops.py @@ -19,6 +19,7 @@ import numpy as np import pytest +from scipy.linalg import fractional_matrix_power from scipy.sparse import csr_matrix from scipy.stats import unitary_group @@ -393,7 +394,7 @@ def test_pow_broadcasted(self, n): assert qml.math.allclose(pow_ops[0].data[0], op_mat_to_pow) def test_noninteger_pow(self): - """Test that a ControlledQubitUnitary raised to a non-integer power raises an error.""" + """Test that a ControlledQubitUnitary raised to a non-integer power evalutes.""" U1 = np.array( [ [0.73708696 + 0.61324932j, 0.27034258 + 0.08685028j], @@ -403,8 +404,11 @@ def test_noninteger_pow(self): op = qml.ControlledQubitUnitary(U1, control_wires=("b", "c"), wires="a") - with pytest.raises(qml.operation.PowUndefinedError): - op.pow(0.12) + z = 0.12 + [pow_op] = op.pow(z) + expected = np.eye(8, dtype=complex) + expected[-2:, -2:] = fractional_matrix_power(U1, z) + assert qml.math.allequal(pow_op.matrix(), expected) def test_noninteger_pow_broadcasted(self): """Test that a ControlledQubitUnitary raised to a non-integer power raises an error.""" diff --git a/tests/ops/op_math/test_pow_op.py b/tests/ops/op_math/test_pow_op.py index af7f455089a..7ed54391d15 100644 --- a/tests/ops/op_math/test_pow_op.py +++ b/tests/ops/op_math/test_pow_op.py @@ -706,9 +706,9 @@ def check_matrix(self, param, z): base = qml.IsingZZ(param, wires=(0, 1)) op = Pow(base, z) - mat = qml.matrix(op) - shortcut = base.pow(z)[0] - shortcut_mat = qml.matrix(shortcut) + mat = op.matrix() + [shortcut] = base.pow(z) + shortcut_mat = shortcut.matrix() return qml.math.allclose(mat, shortcut_mat) @@ -751,6 +751,15 @@ def test_matrix_against_shortcut_tf(self, z): param = tf.Variable(2.34) assert self.check_matrix(param, z) + @pytest.mark.tf + def test_matrix_tf_int_z(self): + """Test that matrix works with integer power.""" + import tensorflow as tf + + theta = tf.Variable(1.0) + mat = qml.pow(qml.RX(theta, wires=0), z=3).matrix() + assert qml.math.allclose(mat, qml.RX.compute_matrix(3)) + def test_matrix_wire_order(self): """Test that the wire_order keyword rearranges ording.""" @@ -796,7 +805,7 @@ def test_sparse_matrix_exists_int_exponent(self): sparse_mat_array = sparse_mat.toarray() assert qml.math.allclose(sparse_mat_array, H_cubed.toarray()) - assert qml.math.allclose(sparse_mat_array, qml.matrix(op)) + assert qml.math.allclose(sparse_mat_array, op.matrix()) def test_sparse_matrix_float_exponent(self): """Test that even a sparse-matrix defining op raised to a float power diff --git a/tests/ops/qubit/test_matrix_ops.py b/tests/ops/qubit/test_matrix_ops.py index 79cc0291c5c..5fe6bc5f7b2 100644 --- a/tests/ops/qubit/test_matrix_ops.py +++ b/tests/ops/qubit/test_matrix_ops.py @@ -25,7 +25,7 @@ from pennylane import numpy as pnp from pennylane.operation import DecompositionUndefinedError from pennylane.wires import Wires -from pennylane.ops.qubit.matrix_ops import _walsh_hadamard_transform +from pennylane.ops.qubit.matrix_ops import _walsh_hadamard_transform, fractional_matrix_power class TestQubitUnitary: @@ -38,9 +38,10 @@ def test_qubit_unitary_noninteger_pow(self): ) op = qml.QubitUnitary(U, wires="a") + [pow_op] = op.pow(0.123) + expected = fractional_matrix_power(U, 0.123) - with pytest.raises(qml.operation.PowUndefinedError): - op.pow(0.123) + assert qml.math.allclose(pow_op.matrix(), expected) def test_qubit_unitary_noninteger_pow_broadcasted(self): """Test broadcasted QubitUnitary raised to a non-integer power raises an error.""" @@ -188,6 +189,16 @@ def test_qubit_unitary_tf(self, U, num_wires): with pytest.raises(ValueError, match="must be of shape"): qml.QubitUnitary(U, wires=range(num_wires + 1)).matrix() + @pytest.mark.tf + def test_qubit_unitary_int_pow_tf(self): + """Test that QubitUnitary.pow works with tf and int z values.""" + import tensorflow as tf + + mat = tf.Variable([[1, 0], [0, tf.exp(1j)]]) + expected = tf.Variable([[1, 0], [0, tf.exp(3j)]]) + [op] = qml.QubitUnitary(mat, wires=[0]).pow(3) + assert qml.math.allclose(op.matrix(), expected) + @pytest.mark.jax @pytest.mark.parametrize( "U,num_wires", [(H, 1), (np.kron(H, H), 2), (np.tensordot([1j, -1, 1], H, axes=0), 1)]