diff --git a/pennylane/transforms/decompositions/clifford_plus_t/__init__.py b/pennylane/transforms/decompositions/clifford_plus_t/__init__.py index 6522cb38315..ae9c5f03e32 100644 --- a/pennylane/transforms/decompositions/clifford_plus_t/__init__.py +++ b/pennylane/transforms/decompositions/clifford_plus_t/__init__.py @@ -15,4 +15,4 @@ the helper functions needed to compute it. """ -from .cliffordt_transform import clifford_t_decomposition, check_clifford_t +from .cliffordt_transform import clifford_t_decomposition diff --git a/pennylane/transforms/decompositions/clifford_plus_t/cliffordt_transform.py b/pennylane/transforms/decompositions/clifford_plus_t/cliffordt_transform.py index 67182356470..97aa5c16dd5 100644 --- a/pennylane/transforms/decompositions/clifford_plus_t/cliffordt_transform.py +++ b/pennylane/transforms/decompositions/clifford_plus_t/cliffordt_transform.py @@ -48,7 +48,7 @@ qml.ISWAP, ] -_PARAMETER_GATES = [qml.RX, qml.RY, qml.RZ, qml.Rot, qml.GlobalPhase] +_PARAMETER_GATES = [qml.RX, qml.RY, qml.RZ, qml.Rot, qml.PhaseShift, qml.GlobalPhase] _CLIFFORD_T_GATES = _CLIFFORD_T_ONE_GATES + _CLIFFORD_T_TWO_GATES @@ -164,11 +164,11 @@ def _rot_decompose(op): ops_ = _rot_decompose(op.base.adjoint()) elif isinstance(op, qml.RX): ops_ = _simplify_param(theta, qml.PauliX(wires=wires)) - if ops_ is None: + if ops_ is None: # Use Rx = H @ Rz @ H ops_ = [qml.Hadamard(wires), qml.RZ(theta, wires), qml.Hadamard(wires)] elif isinstance(op, qml.RY): ops_ = _simplify_param(theta, qml.PauliY(wires=wires)) - if ops_ is None: + if ops_ is None: # Use Ry = S @ H @ Rz @ H @ S.dag ops_ = [ qml.S(wires), qml.Hadamard(wires), @@ -180,6 +180,13 @@ def _rot_decompose(op): ops_ = _simplify_param(theta, qml.PauliZ(wires=wires)) if ops_ is None: ops_ = [qml.RZ(theta, wires)] + elif isinstance(op, qml.PhaseShift): + ops_ = _simplify_param(theta, qml.PauliZ(wires=wires)) + if ops_ is None: + ops_ = [qml.RZ(theta, wires), qml.GlobalPhase(theta / 2)] + else: + ops_.append(qml.GlobalPhase(-theta / 2)) + else: raise ValueError( f"Operation {op} is not a valid Pauli rotation: qml.RX, qml.RY, qml.RZ and qml.Rot" @@ -262,14 +269,15 @@ def _merge_pauli_rotations(operations, merge_ops=None): # pylint: disable= too-many-nested-blocks, too-many-branches, too-many-statements, unnecessary-lambda-assignment @transform -def clifford_t_decomposition(tape: QuantumTape, epsilon=1e-8) -> (Sequence[QuantumTape], Callable): +def clifford_t_decomposition( + tape: QuantumTape, epsilon=1e-8, max_depth=6 +) -> (Sequence[QuantumTape], Callable): r"""Unrolls the tape into Clifford+T basis""" # Build the basis set and the pipeline for intial compilation pass - basis_set = [op.__name__ for op in _PARAMETER_GATES + _CLIFFORD_T_GATES] + basis_set = [op.name for op in _PARAMETER_GATES + _CLIFFORD_T_GATES] pipelines = [remove_barrier, commute_controlled, cancel_inverses, merge_rotations] - max_depth = 6 # can be tweaked expanded_tape = tape.expand(depth=max_depth, stop_at=lambda op: op.name in basis_set) for transf in pipelines: @@ -287,8 +295,8 @@ def clifford_t_decomposition(tape: QuantumTape, epsilon=1e-8) -> (Sequence[Quant gphase_ops.append(op) # Check whether the operation is a Clifford or a T-gate - elif check_clifford_t(op) and len(op.parameters) < 2: - if len(op.parameters): + elif op.name in basis_set and check_clifford_t(op): + if op.num_params: decomp_ops.extend(_rot_decompose(op)) else: decomp_ops.append(op) diff --git a/tests/transforms/test_clifford_t/test_cliffordt_transform.py b/tests/transforms/test_clifford_t/test_cliffordt_transform.py index 2f3b4eec1e6..5676f183848 100644 --- a/tests/transforms/test_clifford_t/test_cliffordt_transform.py +++ b/tests/transforms/test_clifford_t/test_cliffordt_transform.py @@ -21,6 +21,7 @@ from pennylane.transforms.decompositions.clifford_plus_t.cliffordt_transform import ( check_clifford_t, clifford_t_decomposition, + _rot_decompose, _one_qubit_decompose, _two_qubit_decompose, _CLIFFORD_T_GATES, @@ -28,15 +29,17 @@ from pennylane.transforms.optimization.optimization_utils import _fuse_global_phases -_CLIFFORD_PHASE_GATES = _CLIFFORD_T_GATES + [qml.GlobalPhase] +_SKIP_GATES = [qml.Barrier, qml.Snapshot, qml.WireCut, qml.GlobalPhase] +_CLIFFORD_PHASE_GATES = _CLIFFORD_T_GATES + _SKIP_GATES def circuit_1(): """Circuit 1 with quantum chemistry gates""" - qml.RX(1.0, wires=[0]) + qml.RZ(1.0, wires=[0]) qml.PhaseShift(1.0, wires=[1]) qml.SingleExcitation(2.0, wires=[1, 2]) qml.DoubleExcitation(2.0, wires=[1, 2, 3, 4]) + qml.Snapshot() return qml.expval(qml.PauliZ(0)) @@ -95,15 +98,18 @@ def test_clifford_checker(self, op, res): """Test Clifford checker operation for gate""" assert check_clifford_t(op) == res - @pytest.mark.parametrize(("circuit"), [circuit_1, circuit_2, circuit_3, circuit_4, circuit_5]) - def test_decomposition(self, circuit): + @pytest.mark.parametrize( + ("circuit, max_depth"), + [(circuit_1, 1), (circuit_2, 2), (circuit_3, 3), (circuit_4, 4), (circuit_5, 5)], + ) + def test_decomposition(self, circuit, max_depth): """Test decomposition for the Clifford transform.""" with qml.tape.QuantumTape() as tape: circuit() - [tape], _ = clifford_t_decomposition(tape) - + [tape], _ = clifford_t_decomposition(tape, max_depth=max_depth) + print(tape.operations) assert all( any( ( @@ -189,3 +195,54 @@ def test_two_qubit_decomposition(self, op): where=matrix_op != 0, )[qml.math.nonzero(qml.math.round(matrix_op, 10))] assert qml.math.allclose(phase / phase[0], qml.math.ones(qml.math.shape(phase)[0])) + + @pytest.mark.parametrize( + ("op"), + [ + qml.adjoint(qml.RX(1.0, wires=["b"])), + qml.Rot(1, 2, 3, wires=[2]), + ], + ) + def test_rot_decomposition(self, op): + """Test decomposition for the Clifford transform.""" + + decomp_ops = _fuse_global_phases(_rot_decompose(op)) + + assert all( + any( + ( + isinstance(op, gate) or isinstance(getattr(op, "base", None), gate) + for gate in _CLIFFORD_PHASE_GATES + [qml.RZ] + ) + ) + for op in decomp_ops + ) + + decomp_ops, global_ops = decomp_ops[:-1], decomp_ops[-1] + matrix_op = reduce( + lambda x, y: x @ y, [qml.matrix(op) for op in decomp_ops][::-1] + ) * qml.matrix(global_ops) + + # check for matrice equivalence up to global phase + phase = qml.math.divide( + matrix_op, + qml.matrix(op), + out=qml.math.zeros_like(matrix_op, dtype=complex), + where=matrix_op != 0, + )[qml.math.nonzero(qml.math.round(matrix_op, 10))] + assert qml.math.allclose(phase / phase[0], qml.math.ones(qml.math.shape(phase)[0])) + + @pytest.mark.parametrize( + ("op"), + [ + qml.U1(1.0, wires=["b"]), + ], + ) + def test_raise_with_rot_decomposition(self, op): + """Test that exception is correctly raise when caclulating expectation values of multiple Hamiltonians""" + + with pytest.raises( + ValueError, + match="qml.RX, qml.RY, qml.RZ and qml.Rot", + ): + _fuse_global_phases(_rot_decompose(op))