Skip to content

Commit

Permalink
make commutative_cancellation basis aware (Qiskit#5672)
Browse files Browse the repository at this point in the history
* fixes Qiskit#5644

* move change to commutation analysis to new pr.

* Update qiskit/transpiler/passes/optimization/commutative_cancellation.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Update qiskit/transpiler/passes/optimization/commutative_cancellation.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Update qiskit/transpiler/passes/optimization/commutative_cancellation.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Update qiskit/transpiler/passes/optimization/commutative_cancellation.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* check global phase

* minor fix

* preserve global phase in commutative_cancellation

* Update test/python/transpiler/test_commutative_cancellation.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* add blank line

* add release note

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>
Co-authored-by: Luciano Bello <bel@zurich.ibm.com>
Co-authored-by: Kevin Krsulich <kevin.krsulich@ibm.com>
  • Loading branch information
4 people authored Mar 3, 2021
1 parent a4604cb commit 0d85759
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 12 deletions.
47 changes: 43 additions & 4 deletions qiskit/transpiler/passes/optimization/commutative_cancellation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit.library.standard_gates.u1 import U1Gate
from qiskit.circuit.library.standard_gates.rx import RXGate
from qiskit.circuit.library.standard_gates.p import PhaseGate
from qiskit.circuit.library.standard_gates.rz import RZGate


_CUTOFF_PRECISION = 1E-5

Expand All @@ -35,8 +38,22 @@ class CommutativeCancellation(TransformationPass):
H, X, Y, Z, CX, CY, CZ
"""

def __init__(self):
def __init__(self, basis_gates=None):
"""
CommutativeCancellation initializer.
Args:
basis_gates (list[str]): Basis gates to consider, e.g. `['u3', 'cx']`. For the effects
of this pass, the basis is the set intersection between the `basis` parameter
and the gates in the dag.
"""
super().__init__()
if basis_gates:
self.basis = set(basis_gates)
else:
self.basis = set()

self._var_z_map = {'rz': RZGate, 'p': PhaseGate, 'u1': U1Gate}
self.requires.append(CommutationAnalysis())

def run(self, dag):
Expand All @@ -51,6 +68,18 @@ def run(self, dag):
Raises:
TranspilerError: when the 1-qubit rotation gates are not found
"""
var_z_gate = None
z_var_gates = [gate for gate in dag.count_ops().keys()
if gate in self._var_z_map]
if z_var_gates:
# priortize z gates in circuit
var_z_gate = self._var_z_map[next(iter(z_var_gates))]
else:
z_var_gates = [gate for gate in self.basis
if gate in self._var_z_map]
if z_var_gates:
var_z_gate = self._var_z_map[next(iter(z_var_gates))]

# Now the gates supported are hard-coded
q_gate_list = ['cx', 'cy', 'cz', 'h', 'y']

Expand All @@ -75,7 +104,7 @@ def run(self, dag):
num_qargs = len(node.qargs)
if num_qargs == 1 and node.name in q_gate_list:
cancellation_sets[(node.name, wire, com_set_idx)].append(node)
if num_qargs == 1 and node.name in ['z', 'u1', 'rz', 't', 's']:
if num_qargs == 1 and node.name in ['p', 'z', 'u1', 'rz', 't', 's']:
cancellation_sets[('z_rotation', wire, com_set_idx)].append(node)
if num_qargs == 1 and node.name in ['rx', 'x']:
cancellation_sets[('x_rotation', wire, com_set_idx)].append(node)
Expand All @@ -88,6 +117,8 @@ def run(self, dag):
cancellation_sets[q2_key].append(node)

for cancel_set_key in cancellation_sets:
if cancel_set_key[0] == 'z_rotation' and var_z_gate is None:
continue
set_len = len(cancellation_sets[cancel_set_key])
if set_len > 1 and cancel_set_key[0] in q_gate_list:
gates_to_cancel = cancellation_sets[cancel_set_key]
Expand All @@ -98,13 +129,14 @@ def run(self, dag):
run = cancellation_sets[cancel_set_key]
run_qarg = run[0].qargs[0]
total_angle = 0.0 # lambda
total_phase = 0.0
for current_node in run:
if (current_node.condition is not None
or len(current_node.qargs) != 1
or current_node.qargs[0] != run_qarg):
raise TranspilerError("internal error")

if current_node.name in ['u1', 'rz', 'rx']:
if current_node.name in ['p', 'u1', 'rz', 'rx']:
current_angle = float(current_node.op.params[0])
elif current_node.name in ['z', 'x']:
current_angle = np.pi
Expand All @@ -115,19 +147,26 @@ def run(self, dag):

# Compose gates
total_angle = current_angle + total_angle
if current_node.op.definition:
total_phase += current_node.op.definition.global_phase

# Replace the data of the first node in the run
if cancel_set_key[0] == 'z_rotation':
new_op = U1Gate(total_angle)
new_op = var_z_gate(total_angle)
elif cancel_set_key[0] == 'x_rotation':
new_op = RXGate(total_angle)

new_op_phase = 0
if np.mod(total_angle, (2 * np.pi)) > _CUTOFF_PRECISION:
new_qarg = QuantumRegister(1, 'q')
new_dag = DAGCircuit()
new_dag.add_qreg(new_qarg)
new_dag.apply_operation_back(new_op, [new_qarg[0]])
dag.substitute_node_with_dag(run[0], new_dag)
if new_op.definition:
new_op_phase = new_op.definition.global_phase

dag.global_phase = total_phase - new_op_phase

# Delete the other nodes in the run
for current_node in run[1:]:
Expand Down
3 changes: 2 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ def _direction_condition(property_set):
def _opt_control(property_set):
return not property_set['depth_fixed_point']

_opt = [Optimize1qGatesDecomposition(basis_gates), CommutativeCancellation()]
_opt = [Optimize1qGatesDecomposition(basis_gates),
CommutativeCancellation(basis_gates=basis_gates)]

# 9. Schedule the circuit only when scheduling_method is supplied
if scheduling_method:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
upgrade:
- |
This makes it optionally possible to tell the
:class:`qiskit.transpiler.passes.optimization.CommutativeCancellation`
pass about target basis. Previously the pass would automatically
replace consecutive gates which commute with ZGate with the
U1Gate. Functionally the target basis is the union of the gates in
the circuit and the target basis. If no variable z-rotation gate
exists no commutative cancellation will occur. This change may
lead to different behavior if using this pass without specifying a
basis and supplying a circuit with no variable z-rotation gate so
a basis gate should be explicitly supplied.
93 changes: 87 additions & 6 deletions test/python/transpiler/test_commutative_cancellation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
from qiskit.test import QiskitTestCase

from qiskit import QuantumRegister, QuantumCircuit
from qiskit.circuit.library import U1Gate
from qiskit.circuit.library import U1Gate, RZGate
from qiskit.transpiler import PassManager, PropertySet
from qiskit.transpiler.passes import CommutationAnalysis, CommutativeCancellation, FixedPoint, Size
from qiskit.quantum_info import Operator


class TestCommutativeCancellation(QiskitTestCase):
Expand Down Expand Up @@ -71,7 +72,7 @@ def test_all_gates(self):
new_circuit = passmanager.run(circuit)

expected = QuantumCircuit(qr)
expected.append(U1Gate(2.0), [qr[0]])
expected.append(RZGate(2.0), [qr[0]])
expected.rx(1.0, qr[0])

self.assertEqual(expected, new_circuit)
Expand Down Expand Up @@ -368,9 +369,9 @@ def test_commutative_circuit2(self):
passmanager.append(CommutativeCancellation())
new_circuit = passmanager.run(circuit)
expected = QuantumCircuit(qr)
expected.append(U1Gate(np.pi * 17 / 12), [qr[2]])
expected.append(RZGate(np.pi * 17 / 12), [qr[2]])
expected.cx(qr[2], qr[1])

expected.global_phase = (np.pi * 17 / 12 - (2 * np.pi / 3)) / 2
self.assertEqual(expected, new_circuit)

def test_commutative_circuit3(self):
Expand Down Expand Up @@ -412,8 +413,8 @@ def test_commutative_circuit3(self):
do_while=lambda property_set: not property_set['size_fixed_point'])
new_circuit = passmanager.run(circuit)
expected = QuantumCircuit(qr)
expected.append(U1Gate(np.pi * 17 / 12), [qr[2]])
expected.append(U1Gate(np.pi * 2 / 3), [qr[3]])
expected.append(RZGate(np.pi * 17 / 12), [qr[2]])
expected.append(RZGate(np.pi * 2 / 3), [qr[3]])
expected.cx(qr[2], qr[1])

self.assertEqual(expected, new_circuit)
Expand Down Expand Up @@ -524,6 +525,86 @@ def test_conditional_gates_dont_commute(self):

self.assertEqual(circuit, new_circuit)

def test_basis_01(self):
"""Test basis priority change, phase gate"""
circuit = QuantumCircuit(1)
circuit.s(0)
circuit.z(0)
circuit.t(0)
circuit.rz(np.pi, 0)
passmanager = PassManager()
passmanager.append(CommutativeCancellation(basis_gates=['cx', 'p', 'sx']))
new_circuit = passmanager.run(circuit)
expected = QuantumCircuit(1)
expected.rz(11 * np.pi / 4, 0)
expected.global_phase = 11 * np.pi / 4 / 2 - np.pi / 2

self.assertEqual(new_circuit, expected)

def test_basis_02(self):
"""Test basis priority change, Rz gate"""
circuit = QuantumCircuit(1)
circuit.s(0)
circuit.z(0)
circuit.t(0)
passmanager = PassManager()
passmanager.append(CommutativeCancellation(basis_gates=['cx', 'rz', 'sx']))
new_circuit = passmanager.run(circuit)

expected = QuantumCircuit(1)
expected.rz(7 * np.pi / 4, 0)
expected.global_phase = 7 * np.pi / 4 / 2
self.assertEqual(new_circuit, expected)

def test_basis_03(self):
"""Test no specified basis"""
circuit = QuantumCircuit(1)
circuit.s(0)
circuit.z(0)
circuit.t(0)
passmanager = PassManager()
passmanager.append(CommutativeCancellation())
new_circuit = passmanager.run(circuit)

expected = QuantumCircuit(1)
expected.s(0)
expected.z(0)
expected.t(0)
self.assertEqual(new_circuit, expected)

def test_basis_global_phase_01(self):
"""Test no specified basis, rz"""
circ = QuantumCircuit(1)
circ.rz(np.pi/2, 0)
circ.p(np.pi/2, 0)
circ.p(np.pi/2, 0)
passmanager = PassManager()
passmanager.append(CommutativeCancellation())
ccirc = passmanager.run(circ)
self.assertEqual(Operator(circ), Operator(ccirc))

def test_basis_global_phase_02(self):
"""Test no specified basis, p"""
circ = QuantumCircuit(1)
circ.p(np.pi/2, 0)
circ.rz(np.pi/2, 0)
circ.p(np.pi/2, 0)
passmanager = PassManager()
passmanager.append(CommutativeCancellation())
ccirc = passmanager.run(circ)
self.assertEqual(Operator(circ), Operator(ccirc))

def test_basis_global_phase_03(self):
"""Test global phase preservation if cummulative z-rotation is 0"""
circ = QuantumCircuit(1)
circ.rz(np.pi/2, 0)
circ.p(np.pi/2, 0)
circ.z(0)
passmanager = PassManager()
passmanager.append(CommutativeCancellation())
ccirc = passmanager.run(circ)
self.assertEqual(Operator(circ), Operator(ccirc))


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion test/python/transpiler/test_passmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def callback(**kwargs):
calls.append(out_dict)

passmanager = PassManager()
passmanager.append(CommutativeCancellation())
passmanager.append(CommutativeCancellation(basis_gates=['u1', 'u2', 'u3', 'cx']))
passmanager.run(circuit, callback=callback)
self.assertEqual(len(calls), 2)
self.assertEqual(len(calls[0]), 5)
Expand Down

0 comments on commit 0d85759

Please sign in to comment.