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

Operator equality with (clean) ancillas #12968

Merged
merged 3 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
76 changes: 76 additions & 0 deletions qiskit/quantum_info/operators/operator_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
Additional utilities for Operators.
"""

from __future__ import annotations

from qiskit.quantum_info import Operator
from qiskit.quantum_info.operators.predicates import matrix_equal


def _equal_with_ancillas(
op1: Operator,
op2: Operator,
ancilla_qubits: list[int],
ignore_phase: bool = False,
rtol: float | None = None,
atol: float | None = None,
) -> bool:
r"""Test if two Operators are equal on the subspace where ancilla qubits
are :math:`|0\rangle`.

Args:
op1 (Operator): an operator object.
op2 (Operator): an operator object.
ancilla_qubits (list[int]): a list of clean ancilla qubits.
ignore_phase (bool): ignore complex-phase difference between matrices.
rtol (float): relative tolerance value for comparison.
atol (float): absolute tolerance value for comparison.

Returns:
bool: True iff operators are equal up to clean ancilla qubits.
"""
if op1.dim != op2.dim:
return False

if atol is None:
atol = op1.atol
if rtol is None:
rtol = op1.rtol

num_qubits = op1._op_shape._num_qargs_l
num_non_ancillas = num_qubits - len(ancilla_qubits)

# Find a permutation that moves all ancilla qubits to the back
pattern = []
ancillas = []
for q in range(num_qubits):
if q not in ancilla_qubits:
pattern.append(q)
else:
ancillas.append(q)
pattern = pattern + ancillas

# Apply this permutation to both operators
permuted1 = op1.apply_permutation(pattern)
permuted2 = op2.apply_permutation(pattern)

# Restrict to the subspace where ancillas are 0
restricted1 = permuted1.data[: 2**num_non_ancillas, : 2**num_qubits]
restricted2 = permuted2.data[: 2**num_non_ancillas, : 2**num_qubits]

return matrix_equal(
restricted1, restricted2.data, ignore_phase=ignore_phase, rtol=rtol, atol=atol
)
24 changes: 24 additions & 0 deletions test/python/quantum_info/operators/test_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from qiskit.transpiler.layout import Layout, TranspileLayout
from qiskit.quantum_info.operators import Operator, ScalarOp
from qiskit.quantum_info.operators.predicates import matrix_equal
from qiskit.quantum_info.operators.operator_utils import _equal_with_ancillas
from qiskit.compiler.transpiler import transpile
from qiskit.circuit import Qubit
from qiskit.circuit.library import Permutation, PermutationGate
Expand Down Expand Up @@ -1255,6 +1256,29 @@ def test_apply_permutation_dimensions(self):
op2 = op.apply_permutation([2, 0, 1], front=True)
self.assertEqual(op2.input_dims(), (4, 2, 3))

def test_equality_with_ancillas(self):
"""Check correctness of the equal_with_ancillas method."""

# The two circuits below are equal provided that qubit 1 is initially |0>.
qc1 = QuantumCircuit(4)
qc1.x(0)
qc1.x(2)
qc1.cx(1, 0)
qc1.cx(1, 2)
qc1.cx(1, 3)
op1 = Operator(qc1)

qc2 = QuantumCircuit(4)
qc2.x(0)
qc2.x(2)
op2 = Operator(qc2)

self.assertNotEqual(op1, op2)
self.assertFalse(_equal_with_ancillas(op1, op2, []))
self.assertTrue(_equal_with_ancillas(op1, op2, [1]))
self.assertFalse(_equal_with_ancillas(op1, op2, [2]))
self.assertTrue(_equal_with_ancillas(op1, op2, [2, 1]))


if __name__ == "__main__":
unittest.main()
Loading