Skip to content

Commit bdbbb7e

Browse files
Merge branch 'main' into random-cliff-array-sort
2 parents 37ed227 + f97ef06 commit bdbbb7e

6 files changed

Lines changed: 105 additions & 7 deletions

File tree

crates/transpiler/src/passes/commutative_optimization.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ fn compare_params(params1: &[Param], params2: &[Param]) -> PyResult<bool> {
6969

7070
/// List of symmetric gates, that is the gate remains the same under all
7171
/// permutations of its arguments.
72-
static SYMMETRIC_GATES: [StandardGate; 13] = [
72+
static SYMMETRIC_GATES: [StandardGate; 12] = [
7373
StandardGate::CZ,
7474
StandardGate::Swap,
7575
StandardGate::ISwap,
@@ -81,7 +81,6 @@ static SYMMETRIC_GATES: [StandardGate; 13] = [
8181
StandardGate::RYY,
8282
StandardGate::RZZ,
8383
StandardGate::XXMinusYY,
84-
StandardGate::XXPlusYY,
8584
StandardGate::CCZ,
8685
];
8786

@@ -103,6 +102,35 @@ static MERGEABLE_ROTATION_GATES: [StandardGate; 12] = [
103102
StandardGate::CU1,
104103
];
105104

105+
/// Check if `inst` is symmetric for some special values of its parameters,
106+
/// taking tolerance into account.
107+
fn is_special_symmetric(inst: &PackedInstruction, tol: f64) -> bool {
108+
// For now, this only handles the standard XXPlusYY gate.
109+
if let OperationRef::StandardGate(StandardGate::XXPlusYY) = inst.op.view() {
110+
// From the matrix representation, applying XXPlusYY(theta, beta) on reversed qubits [q2, q1]
111+
// is the same as applying XXPlusYY(theta, -beta) on [q1, q2].
112+
// The direct computation for the average gate fidelity between XXPlusYY(theta, beta) and
113+
// XXPlusYY(theta, -beta) gives the following: 1/5 + 4/5 [1-sin^(theta/2) sin^2(beta)]^2.
114+
match inst.params_view() {
115+
[Param::Float(theta), Param::Float(beta)] => {
116+
// If both theta and beta are floating-point, we can evaluate the above condition
117+
// directly.
118+
let x = (1.0 - (theta / 2.0).sin().powf(2.0) * beta.sin().powf(2.0)).powf(2.0);
119+
return x > 1.0 - 5. / 4. * tol;
120+
}
121+
[Param::ParameterExpression(_theta), Param::Float(beta)] => {
122+
// If theta is parametric, we take the estimate that works for all theta.
123+
let x = (1.0 - beta.sin().powf(2.0)).powf(2.0);
124+
return x > 1.0 - 5. / 4. * tol;
125+
}
126+
_ => {
127+
return false;
128+
}
129+
}
130+
}
131+
false
132+
}
133+
106134
/// Computes the canonical representative of a packed instruction, and in particular:
107135
/// * replaces all types of Z-rotations by RZ-gates,
108136
/// * replaces all types of X-rotations by RX-gates,
@@ -114,6 +142,8 @@ static MERGEABLE_ROTATION_GATES: [StandardGate; 12] = [
114142
/// * `dag` - The output [DAGCircuit]. We use its `qargs_interner` to store sorted
115143
/// qubits for symmetric gates.
116144
/// * `inst` - The instruction to canonicalize.
145+
/// * `tol` - The tolerance used for fidelity computations (for instance, checking
146+
/// whether a gate is symmetric within the specified tolerance).
117147
///
118148
/// # Returns:
119149
///
@@ -122,6 +152,7 @@ static MERGEABLE_ROTATION_GATES: [StandardGate; 12] = [
122152
fn canonicalize(
123153
dag: &mut DAGCircuit,
124154
inst: &PackedInstruction,
155+
tol: f64,
125156
) -> Option<(PackedInstruction, Param)> {
126157
// ToDo: possibly consider other rotations as well (e.g. CS -> CRZ).
127158
let rotation = match inst.op.view() {
@@ -179,7 +210,7 @@ fn canonicalize(
179210
}
180211

181212
if let OperationRef::StandardGate(standard_gate) = inst.op.view() {
182-
if SYMMETRIC_GATES.contains(&standard_gate) {
213+
if SYMMETRIC_GATES.contains(&standard_gate) || is_special_symmetric(inst, tol) {
183214
let qargs = dag.get_qargs(inst.qubits);
184215
if !qargs.is_sorted() {
185216
let mut sorted_qargs = qargs.to_vec();
@@ -604,7 +635,7 @@ pub fn run_commutative_optimization(
604635
continue;
605636
}
606637

607-
if let Some((new_instruction, phase_update)) = canonicalize(&mut new_dag, instr1) {
638+
if let Some((new_instruction, phase_update)) = canonicalize(&mut new_dag, instr1, tol) {
608639
node_actions[idx1] = NodeAction::Canonical(new_instruction, phase_update);
609640
}
610641

qiskit/circuit/library/generalized_gates/linear_function.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from qiskit.circuit.library.generalized_gates.permutation import PermutationGate
2020
from qiskit.utils.deprecation import deprecate_func
2121

22-
2322
from qiskit.quantum_info import Clifford
2423

2524

@@ -150,6 +149,19 @@ def __init__(
150149
name="linear_function", num_qubits=len(linear), params=[linear, original_circuit]
151150
)
152151

152+
def inverse(self, annotated: bool = False) -> LinearFunction:
153+
"""Returns the inverse of this linear function.
154+
155+
Args:
156+
annotated: when set to ``True``, this is typically used to return an
157+
:class:`.AnnotatedOperation` with an inverse modifier set instead of a concrete
158+
:class:`.Gate`. However, for this class this argument is ignored as the inverse
159+
of this gate is always a :class:`.LinearFunction`.
160+
"""
161+
from qiskit.synthesis.linear import calc_inverse_matrix
162+
163+
return LinearFunction(linear=calc_inverse_matrix(self.linear))
164+
153165
@staticmethod
154166
def _circuit_to_mat(qc: QuantumCircuit):
155167
"""This creates a nxn matrix corresponding to the given quantum circuit."""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
fixes:
3+
- |
4+
Previously, calling ``inverse`` on a :class:`.LinearFunction` raised an error;
5+
this is no longer the case.
6+
See `#16184 <https://github.com/Qiskit/qiskit/issues/16184>`__.
7+
8+
features_circuits:
9+
- |
10+
Added a :meth:`.LinearFunction.inverse` method that returns a
11+
:class:`.LinearFunction` representing the inverse transformation.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed a bug in the :class:`.CommutativeOptimization` transpiler pass
5+
where pairs of :class:`.XXPlusYYGate` gates acting on the same qubits
6+
but in reversed order were incorrectly simplified. However, the
7+
simplification is performed in special cases where the gate and its
8+
reversed versions are inverse within the specified tolerance, for
9+
instance when the argument `beta` is a multiple of `pi`.
10+
11+
See `#16161 <https://github.com/Qiskit/qiskit/issues/16161>`__.
12+
13+

test/python/circuit/library/test_linear_function.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,16 @@ def test_repeat_method(self, num_qubits):
515515
linear_function = LinearFunction(linear_circuit)
516516
self.assertTrue(Operator(linear_function.repeat(2)), operator @ operator)
517517

518+
def test_inverse(self):
519+
"""Test correctness of the ``inverse`` method."""
520+
mat = [[1, 0, 0], [1, 1, 0], [1, 1, 1]]
521+
inverse_mat = [[1, 0, 0], [1, 1, 0], [0, 1, 1]]
522+
523+
linear_function = LinearFunction(mat)
524+
inverse_linear_function = linear_function.inverse()
525+
expected_inverse_linear_function = LinearFunction(inverse_mat)
526+
self.assertTrue(np.array_equal(inverse_linear_function, expected_inverse_linear_function))
527+
518528

519529
if __name__ == "__main__":
520530
unittest.main()

test/python/transpiler/test_commutative_optimization.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,9 @@ def test_consecutive_cancellations(self):
167167
RXXGate(0.4),
168168
RYYGate(0.4),
169169
RZZGate(0.4),
170-
XXMinusYYGate(0.4),
171-
XXPlusYYGate(0.4),
170+
XXPlusYYGate(0.4, 0.0),
171+
XXPlusYYGate(0.4, np.pi),
172+
XXMinusYYGate(0.4, 0.2),
172173
)
173174
def test_symmetric_gates_cancel(self, symmetric_gate):
174175
"""Test that various symmetric gates cancel."""
@@ -183,6 +184,26 @@ def test_symmetric_gates_cancel(self, symmetric_gate):
183184
self.assertEqual(Operator(expected), Operator(qc))
184185
self.assertEqual(qct, expected)
185186

187+
def test_nonsymmetric_gates_do_not_canel(self):
188+
"""Test that non-symmetric gates do not cancel."""
189+
qc = QuantumCircuit(2)
190+
qc.append(XXPlusYYGate(0.4, 0.2), [0, 1])
191+
qc.append(XXPlusYYGate(0.4, 0.2).inverse(), [1, 0]) # note reversed order of qubits
192+
193+
qct = CommutativeOptimization()(qc)
194+
195+
self.assertEqual(qct, qc)
196+
197+
def test_symmetric_xxplusyy_gates_cancel(self):
198+
"""Test for cancellation XXPlusYY gates with parametric values of theta."""
199+
symmetric_gate = XXPlusYYGate(Parameter("t"), np.pi)
200+
qc = QuantumCircuit(2)
201+
qc.append(symmetric_gate, [0, 1])
202+
qc.append(symmetric_gate.inverse(), [1, 0]) # note reversed order of qubits
203+
qct = CommutativeOptimization()(qc)
204+
expected = QuantumCircuit(2)
205+
self.assertEqual(qct, expected)
206+
186207
def test_symmetric_iswap_gates_cancel(self):
187208
"""Test that iSwap gates cancel."""
188209
# iSwap is handled separately as its inverse is not a standard gate

0 commit comments

Comments
 (0)