Source code for pennylane.ops.functions.is_commuting
# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Defines `is_commuting`, an function for determining if two functions commute.
"""
import numpy as np
import pennylane as qml
from pennylane.ops.op_math import Prod, SProd, Sum
def _pword_is_commuting(pauli_word_1, pauli_word_2):
r"""Checks if two Pauli words commute.
Args:
pauli_word_1 (Operator): first Pauli word in commutator.
pauli_word_2 (Operator): second Pauli word in commutator.
Returns:
bool: returns True if the input Pauli commute, False otherwise.
**Example**
>>> pauli_word_1 = qml.prod(qml.X("a"), qml.Y("b"))
>>> pauli_word_2 = qml.prod(qml.Z("a"), qml.Z("c"))
>>> _pword_is_commuting(pauli_word_1, pauli_word_2)
False
>>> pauli_word_1 = qml.sum(qml.X('a') , qml.Y('b'))
>>> pauli_word_2 = qml.sum(qml.Z('c') , qml.X('a'))
>>> _pword_is_commuting(pauli_word_1, pauli_word_2)
True
"""
pr1 = pauli_word_1.pauli_rep
pr2 = pauli_word_2.pauli_rep
comm = pr1.commutator(pr2)
comm.simplify()
return comm == qml.pauli.pauli_arithmetic.PauliSentence({})
def _get_target_name(op):
"""Get the name for the target operation. If the operation is not controlled, this is
simplify the operation's name.
"""
_control_base_map = {
"CNOT": "PauliX",
"CZ": "PauliZ",
"CCZ": "PauliZ",
"CY": "PauliY",
"CH": "Hadamard",
"CSWAP": "SWAP",
"Toffoli": "PauliX",
"ControlledPhaseShift": "PhaseShift",
"CRX": "RX",
"CRY": "RY",
"CRZ": "RZ",
"CRot": "Rot",
"MultiControlledX": "PauliX",
}
if op.name in _control_base_map:
return _control_base_map[op.name]
if isinstance(op, qml.ops.op_math.Controlled): # pylint: disable=no-member
return op.base.name
return op.name
def _check_mat_commutation(op1, op2):
"""Uses matrices and matrix multiplication to determine whether op1 and op2 commute.
``op1`` and ``op2`` must be on the same wires.
"""
op1_mat = op1.matrix()
op2_mat = op2.matrix()
mat_12 = np.matmul(op1_mat, op2_mat)
mat_21 = np.matmul(op2_mat, op1_mat)
return qml.math.allclose(mat_12, mat_21)
def _create_commute_function():
"""This function constructs the ``_commutes`` helper utility function while using closure
to hide the ``commutation_map`` data away from the global scope of the file.
This function only needs to be called a single time.
Returns:
function
"""
pauliz_group = {
"PauliZ",
"ctrl",
"S",
"Adjoint(S)",
"T",
"Adjoint(T)",
"RZ",
"PhaseShift",
"MultiRZ",
"Identity",
"U1",
"IsingZZ",
}
swap_group = {"SWAP", "ISWAP", "SISWAP", "Identity", "Adjoint(ISWAP)", "Adjoint(SISWAP)"}
paulix_group = {"PauliX", "SX", "RX", "Identity", "IsingXX", "Adjoint(SX)"}
pauliy_group = {"PauliY", "RY", "Identity", "IsingYY"}
commutation_map = {}
for group in [paulix_group, pauliy_group, pauliz_group, swap_group]:
for op in group:
commutation_map[op] = group
identity_only = {"Hadamard", "U2", "U3", "Rot"}
for op in identity_only:
commutation_map[op] = {"Identity", op}
commutation_map["Identity"] = pauliz_group.union(
swap_group, paulix_group, pauliy_group, identity_only
)
def commutes_inner(op_name1, op_name2):
"""Determine whether or not two operations commute.
Relies on ``commutation_map`` from the enclosing namespace of ``_create_commute_function``.
Args:
op_name1 (str): name of one operation.
op_name2 (str): name of the second operation.
Returns:
bool: True if the operations commute, False otherwise.
"""
return op_name1 in commutation_map[op_name2]
return commutes_inner
_commutes = _create_commute_function()
def _check_opmath_operations(operation1, operation2):
"""Check that `Tensor`, `SProd`, `Prod`, and `Sum` instances only contain Pauli words."""
for op in [operation1, operation2]:
if op.pauli_rep is not None:
continue
if isinstance(op, (qml.operation.Tensor, SProd, Prod, Sum)):
raise qml.QuantumFunctionError(
f"Operation {op} currently not supported. Tensor, Prod, Sprod, and Sum instances must have a valid Pauli representation."
)
def intersection(wires1, wires2):
r"""Check if two sets of wires intersect.
Args:
wires1 (pennylane.wires.Wires): First set of wires.
wires2 (pennylane.wires.Wires: Second set of wires.
Returns:
bool: True if the two sets of wires are not disjoint and False if disjoint.
"""
return len(qml.wires.Wires.shared_wires([wires1, wires2])) != 0
def check_commutation_two_non_simplified_crot(operation1, operation2):
r"""Check commutation for two CRot that were not simplified.
Args:
operation1 (pennylane.Operation): First operation.
operation2 (pennylane.Operation): Second operation.
Returns:
bool: True if commutation, False otherwise.
"""
# Two non simplified CRot
target_wires_1 = qml.wires.Wires(
[w for w in operation1.wires if w not in operation1.control_wires]
)
target_wires_2 = qml.wires.Wires(
[w for w in operation2.wires if w not in operation2.control_wires]
)
control_control = intersection(operation1.control_wires, operation2.control_wires)
target_target = intersection(target_wires_1, target_wires_2)
if control_control:
if target_target:
return _check_mat_commutation(operation1, operation2)
# control_control and not target_target
return True
if target_target:
return _check_mat_commutation(
qml.Rot(*operation1.data, wires=operation1.wires[1]),
qml.Rot(*operation2.data, wires=operation2.wires[1]),
)
return False
def check_commutation_two_non_simplified_rotations(operation1, operation2):
r"""Check that the operations are two non simplified operations. If it is the case, then it checks commutation
for two rotations that were not simplified.
Only allowed ops are `U2`, `U3`, `Rot`, `CRot`.
Args:
operation1 (pennylane.Operation): First operation.
operation2 (pennylane.Operation): Second operation.
Returns:
bool: True if commutation, False otherwise, None if not two rotations.
"""
target_wires_1 = qml.wires.Wires(
[w for w in operation1.wires if w not in operation1.control_wires]
)
target_wires_2 = qml.wires.Wires(
[w for w in operation2.wires if w not in operation2.control_wires]
)
if operation1.name == "CRot":
if intersection(target_wires_1, operation2.wires):
op1_rot = qml.Rot(*operation1.data, wires=target_wires_1)
return _check_mat_commutation(op1_rot, operation2)
return _commutes(operation2.name, "ctrl")
if operation2.name == "CRot":
if intersection(target_wires_2, operation1.wires):
op2_rot = qml.Rot(*operation2.data, wires=target_wires_2)
return _check_mat_commutation(op2_rot, operation1)
return _commutes(operation1.name, "ctrl")
return _check_mat_commutation(operation1, operation2)
unsupported_operations = [
"PauliRot",
"QubitDensityMatrix",
"CVNeuralNetLayers",
"ApproxTimeEvolution",
"ArbitraryUnitary",
"CommutingEvolution",
"DisplacementEmbedding",
"SqueezingEmbedding",
"Exp",
]
non_commuting_operations = [
# StatePrepBase
"QubitStateVector",
"StatePrep",
"BasisState",
# Templates
"ArbitraryStatePreparation",
"BasisStatePreparation",
"MottonenStatePreparation",
"QubitCarry",
"QubitSum",
"SingleExcitation",
"SingleExcitationMinus",
"SingleExcitationPlus",
"DoubleExcitation",
"DoubleExcitationPlus",
"DoubleExcitationMinus",
"BasicEntanglerLayers",
"GateFabric",
"ParticleConservingU1",
"ParticleConservingU2",
"RandomLayers",
"SimplifiedTwoDesign",
"StronglyEntanglingLayers",
"AllSinglesDoubles",
"FermionicDoubleExcitation",
"FermionicSingleExcitation",
"Grover",
"kUpCCGSD",
"Permute",
"QFT",
"QuantumMonteCarlo",
"QuantumPhaseEstimation",
"UCCSD",
"MPS",
"TTN",
"AmplitudeEmbedding",
"AngleEmbedding",
"BasisEmbedding",
"IQPEmbedding",
"QAOAEmbedding",
# utility ops
"Barrier",
"WireCut",
"Snapshot",
]
[docs]def is_commuting(operation1, operation2):
r"""Check if two operations are commuting using a lookup table.
A lookup table is used to check the commutation between the
controlled, targeted part of operation 1 with the controlled, targeted part of operation 2.
.. note::
Most qubit-based PennyLane operations are supported --- CV operations
are not supported at this time.
Unsupported qubit-based operations include:
:class:`~.PauliRot`, :class:`~.QubitDensityMatrix`, :class:`~.CVNeuralNetLayers`,
:class:`~.ApproxTimeEvolution`, :class:`~.ArbitraryUnitary`, :class:`~.CommutingEvolution`,
:class:`~.DisplacementEmbedding`, :class:`~.SqueezingEmbedding`
:class:`~.Exp`
Args:
operation1 (.Operation): A first quantum operation.
operation2 (.Operation): A second quantum operation.
Returns:
bool: True if the operations commute, False otherwise.
**Example**
>>> qml.is_commuting(qml.X(0), qml.Z(0))
False
"""
# pylint: disable=too-many-branches
# pylint: disable=too-many-return-statements
if operation1.name in unsupported_operations or isinstance(
operation1, (qml.operation.CVOperation, qml.operation.Channel)
):
raise qml.QuantumFunctionError(f"Operation {operation1.name} not supported.")
if operation2.name in unsupported_operations or isinstance(
operation2, (qml.operation.CVOperation, qml.operation.Channel)
):
raise qml.QuantumFunctionError(f"Operation {operation2.name} not supported.")
if operation1.pauli_rep is not None and operation2.pauli_rep is not None:
return _pword_is_commuting(operation1, operation2)
# operations are disjoints
if not intersection(operation1.wires, operation2.wires):
return True
# Simplify the rotations if possible
with qml.QueuingManager.stop_recording():
operation1 = qml.simplify(operation1)
operation2 = qml.simplify(operation2)
# Arithmetic non-disjoint operations only contain Pauli words
_check_opmath_operations(operation1, operation2)
# Operation is in the non commuting list
if operation1.name in non_commuting_operations or operation2.name in non_commuting_operations:
return False
# Two CRot that cannot be simplified
if operation1.name == "CRot" and operation2.name == "CRot":
return check_commutation_two_non_simplified_crot(operation1, operation2)
if "Identity" in (operation1.name, operation2.name):
return True
# Check if operations are non simplified rotations and return commutation if it is the case.
op_set = {"U2", "U3", "Rot", "CRot"}
if operation1.name in op_set and operation2.name in op_set:
return check_commutation_two_non_simplified_rotations(operation1, operation2)
ctrl_base_1 = _get_target_name(operation1)
ctrl_base_2 = _get_target_name(operation2)
op1_control_wires = getattr(operation1, "control_wires", {})
op2_control_wires = getattr(operation2, "control_wires", {})
target_wires_1 = qml.wires.Wires([w for w in operation1.wires if w not in op1_control_wires])
target_wires_2 = qml.wires.Wires([w for w in operation2.wires if w not in op2_control_wires])
if intersection(target_wires_1, target_wires_2) and not _commutes(ctrl_base_1, ctrl_base_2):
return False
if intersection(target_wires_1, op2_control_wires) and not _commutes("ctrl", ctrl_base_1):
return False
if intersection(target_wires_2, op1_control_wires) and not _commutes("ctrl", ctrl_base_2):
return False
return True
_modules/pennylane/ops/functions/is_commuting
Download Python script
Download Notebook
View on GitHub