Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow taking the adjoint of fractional power operations #5835

Merged
merged 5 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pennylane/ops/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ def identity_op(*params):
def adjoint(self):
return I(wires=self.wires)

def pow(self, _):
# pylint: disable=unused-argument
def pow(self, z):
return [I(wires=self.wires)]


Expand Down
12 changes: 11 additions & 1 deletion pennylane/ops/op_math/pow.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import pennylane as qml
from pennylane import math as qmlmath
from pennylane.operation import (
AdjointUndefinedError,
DecompositionUndefinedError,
Observable,
Operation,
Expand Down Expand Up @@ -341,8 +342,17 @@ def generator(self):
def pow(self, z):
return [Pow(base=self.base, z=self.z * z)]

# pylint: disable=arguments-renamed, invalid-overridden-method
@property
def has_adjoint(self):
return isinstance(self.z, int)
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved

def adjoint(self):
return Pow(base=qml.adjoint(self.base), z=self.z)
if isinstance(self.z, int):
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved
return Pow(base=qml.adjoint(self.base), z=self.z)
raise AdjointUndefinedError(
"The adjoint of Pow operators only is well-defined for integer powers."
)

def simplify(self) -> Union["Pow", Identity]:
# try using pauli_rep:
Expand Down
48 changes: 44 additions & 4 deletions tests/ops/op_math/test_pow_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import pennylane as qml
from pennylane import numpy as np
from pennylane.operation import DecompositionUndefinedError
from pennylane.operation import AdjointUndefinedError, DecompositionUndefinedError
from pennylane.ops.op_math.controlled import ControlledOp
from pennylane.ops.op_math.pow import Pow, PowOperation

Expand Down Expand Up @@ -144,7 +144,7 @@ class CustomOp(qml.operation.Operation):
assert "control_wires" in dir(op)

def test_observable(self, power_method):
"""Test that when the base is an Observable, Adjoint will also inherit from Observable."""
"""Test that when the base is an Observable, Pow will also inherit from Observable."""

class CustomObs(qml.operation.Observable):
num_wires = 1
Expand All @@ -166,7 +166,7 @@ class CustomObs(qml.operation.Observable):

@pytest.mark.usefixtures("use_legacy_opmath")
def test_observable_legacy_opmath(self, power_method):
"""Test that when the base is an Observable, Adjoint will also inherit from Observable."""
"""Test that when the base is an Observable, Pow will also inherit from Observable."""

class CustomObs(qml.operation.Observable):
num_wires = 1
Expand Down Expand Up @@ -317,6 +317,26 @@ def test_has_matrix_false(self, power_method):

assert op.has_matrix is False

@pytest.mark.parametrize("z", [-2, 3, 2])
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved
def test_has_adjoint_true(self, z, power_method):
"""Test `has_adjoint` property is true for integer powers."""
# Note that even if the base would have `base.has_adjoint=False`, `qml.adjoint`
# would succeed because it would create an `Adjoint(base)` operator.
base = qml.PauliX(0)
op: Pow = power_method(base=base, z=z)

assert op.has_adjoint is True

@pytest.mark.parametrize("z", [-2.0, 1.0, 0.32])
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved
def test_has_adjoint_false(self, z, power_method):
"""Test `has_adjoint` property is false for non-integer powers."""
# Note that the integer power check is a type check, so that floats like 2.
# are not considered to be integers.

op: Pow = power_method(base=TempOperator(wires=0), z=z)

assert op.has_adjoint is False

@pytest.mark.parametrize("z", [1, 3])
def test_has_decomposition_true_via_int(self, power_method, z):
"""Test `has_decomposition` property is true if the power is an interger."""
Expand Down Expand Up @@ -465,6 +485,26 @@ def test_pauli_rep_none_if_base_pauli_rep_none(self, power_method):
op = power_method(base, z=2)
assert op.pauli_rep is None

@pytest.mark.parametrize("z", [-2, 3, 2])
def test_adjoint_integer_power(self, z, power_method):
"""Test the `adjoint` method for integer powers."""
base = qml.PauliX(0)
op: Pow = power_method(base=base, z=z)
adj_op = op.adjoint()

assert isinstance(adj_op, Pow)
assert adj_op.z is op.z
assert qml.equal(adj_op.base, qml.ops.Adjoint(qml.X(0)))

@pytest.mark.parametrize("z", [-2.0, 1.0, 0.32])
def test_adjoint_non_integer_power_raises(self, z, power_method):
"""Test that the `adjoint` method raises and error for non-integer powers."""

base = qml.PauliX(0)
op: Pow = power_method(base=base, z=z)
with pytest.raises(AdjointUndefinedError, match="The adjoint of Pow operators"):
_ = op.adjoint()


class TestSimplify:
"""Test Pow simplify method and depth property."""
Expand All @@ -476,7 +516,7 @@ def test_depth_property(self):

def test_simplify_nested_pow_ops(self):
"""Test the simplify method with nested pow operations."""
pow_op = Pow(base=Pow(base=qml.adjoint(Pow(base=qml.CNOT([1, 0]), z=1.2)), z=2), z=5)
pow_op = Pow(base=Pow(base=qml.adjoint(Pow(base=qml.CNOT([1, 0]), z=2)), z=1.2), z=5)
EmilianoG-byte marked this conversation as resolved.
Show resolved Hide resolved
final_op = qml.Identity([1, 0])
simplified_op = pow_op.simplify()

Expand Down
Loading