Skip to content

Commit fb81116

Browse files
Operator equality with (clean) ancillas (Qiskit#12968)
* initial commit * making the function private; also adding ignore_phase arg * applying review suggestions
1 parent 6f48240 commit fb81116

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# This code is part of Qiskit.
2+
#
3+
# (C) Copyright IBM 2024.
4+
#
5+
# This code is licensed under the Apache License, Version 2.0. You may
6+
# obtain a copy of this license in the LICENSE.txt file in the root directory
7+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
#
9+
# Any modifications or derivative works of this code must retain this
10+
# copyright notice, and modified files need to carry a notice indicating
11+
# that they have been altered from the originals.
12+
13+
"""
14+
Additional utilities for Operators.
15+
"""
16+
17+
from __future__ import annotations
18+
19+
from qiskit.quantum_info import Operator
20+
from qiskit.quantum_info.operators.predicates import matrix_equal
21+
22+
23+
def _equal_with_ancillas(
24+
op1: Operator,
25+
op2: Operator,
26+
ancilla_qubits: list[int],
27+
ignore_phase: bool = False,
28+
rtol: float | None = None,
29+
atol: float | None = None,
30+
) -> bool:
31+
r"""Test if two Operators are equal on the subspace where ancilla qubits
32+
are :math:`|0\rangle`.
33+
34+
Args:
35+
op1 (Operator): an operator object.
36+
op2 (Operator): an operator object.
37+
ancilla_qubits (list[int]): a list of clean ancilla qubits.
38+
ignore_phase (bool): ignore complex-phase difference between matrices.
39+
rtol (float): relative tolerance value for comparison.
40+
atol (float): absolute tolerance value for comparison.
41+
42+
Returns:
43+
bool: True iff operators are equal up to clean ancilla qubits.
44+
"""
45+
if op1.dim != op2.dim:
46+
return False
47+
48+
if atol is None:
49+
atol = op1.atol
50+
if rtol is None:
51+
rtol = op1.rtol
52+
53+
num_qubits = op1._op_shape._num_qargs_l
54+
num_non_ancillas = num_qubits - len(ancilla_qubits)
55+
56+
# Find a permutation that moves all ancilla qubits to the back
57+
pattern = []
58+
ancillas = []
59+
for q in range(num_qubits):
60+
if q not in ancilla_qubits:
61+
pattern.append(q)
62+
else:
63+
ancillas.append(q)
64+
pattern = pattern + ancillas
65+
66+
# Apply this permutation to both operators
67+
permuted1 = op1.apply_permutation(pattern)
68+
permuted2 = op2.apply_permutation(pattern)
69+
70+
# Restrict to the subspace where ancillas are 0
71+
restricted1 = permuted1.data[: 2**num_non_ancillas, : 2**num_qubits]
72+
restricted2 = permuted2.data[: 2**num_non_ancillas, : 2**num_qubits]
73+
74+
return matrix_equal(
75+
restricted1, restricted2.data, ignore_phase=ignore_phase, rtol=rtol, atol=atol
76+
)

test/python/quantum_info/operators/test_operator.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from qiskit.transpiler.layout import Layout, TranspileLayout
3232
from qiskit.quantum_info.operators import Operator, ScalarOp
3333
from qiskit.quantum_info.operators.predicates import matrix_equal
34+
from qiskit.quantum_info.operators.operator_utils import _equal_with_ancillas
3435
from qiskit.compiler.transpiler import transpile
3536
from qiskit.circuit import Qubit
3637
from qiskit.circuit.library import Permutation, PermutationGate
@@ -1255,6 +1256,29 @@ def test_apply_permutation_dimensions(self):
12551256
op2 = op.apply_permutation([2, 0, 1], front=True)
12561257
self.assertEqual(op2.input_dims(), (4, 2, 3))
12571258

1259+
def test_equality_with_ancillas(self):
1260+
"""Check correctness of the equal_with_ancillas method."""
1261+
1262+
# The two circuits below are equal provided that qubit 1 is initially |0>.
1263+
qc1 = QuantumCircuit(4)
1264+
qc1.x(0)
1265+
qc1.x(2)
1266+
qc1.cx(1, 0)
1267+
qc1.cx(1, 2)
1268+
qc1.cx(1, 3)
1269+
op1 = Operator(qc1)
1270+
1271+
qc2 = QuantumCircuit(4)
1272+
qc2.x(0)
1273+
qc2.x(2)
1274+
op2 = Operator(qc2)
1275+
1276+
self.assertNotEqual(op1, op2)
1277+
self.assertFalse(_equal_with_ancillas(op1, op2, []))
1278+
self.assertTrue(_equal_with_ancillas(op1, op2, [1]))
1279+
self.assertFalse(_equal_with_ancillas(op1, op2, [2]))
1280+
self.assertTrue(_equal_with_ancillas(op1, op2, [2, 1]))
1281+
12581282

12591283
if __name__ == "__main__":
12601284
unittest.main()

0 commit comments

Comments
 (0)