Skip to content
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
11 changes: 11 additions & 0 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,17 @@ def filter_fn(node):
group_list = rx.collect_runs(self._multi_graph, filter_fn)
return set(tuple(x) for x in group_list)

def collect_1q_runs(self):
"""Return a set of non-conditional runs of 1q "op" nodes."""

def filter_fn(node):
return node.type == 'op' and len(node.qargs) == 1 \
and len(node.cargs) == 0 and node.condition is None \
and not node.op.is_parameterized() \

group_list = rx.collect_runs(self._multi_graph, filter_fn)
return set(tuple(x) for x in group_list)

def nodes_on_wire(self, wire, only_ops=False):
"""
Iterator for nodes that affect a given wire.
Expand Down
53 changes: 19 additions & 34 deletions qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

"""Optimize chains of single-qubit gates using Euler 1q decomposer"""

from itertools import groupby
import logging

import numpy as np
Expand Down Expand Up @@ -51,11 +50,11 @@ def __init__(self, basis=None):
}
self.basis = None
if basis:
self.basis = []
basis_set = set(basis)
for basis_name, gates in self.euler_basis_names.items():
if set(gates).issubset(basis_set):
self.basis = basis_name
break
self.basis.append(OneQubitEulerDecomposer(basis_name))

def run(self, dag):
"""Run the Optimize1qGatesDecomposition pass on `dag`.
Expand All @@ -69,45 +68,31 @@ def run(self, dag):
if not self.basis:
LOG.info("Skipping pass because no basis is set")
return dag
decomposer = OneQubitEulerDecomposer(self.basis)
runs = dag.collect_runs(self.euler_basis_names[self.basis])
runs = _split_runs_on_parameters(runs)
runs = dag.collect_1q_runs()
for run in runs:
# Don't try to optimize a single 1q gate
if len(run) <= 1:
params = run[0].op.params
# Remove single identity gates
if run[0].op.name in self.euler_basis_names[self.basis] and len(
params) > 0 and np.array_equal(run[0].op.to_matrix(),
np.eye(2)):
if len(params) > 0 and np.array_equal(run[0].op.to_matrix(),
np.eye(2)):
dag.remove_op_node(run[0])
# Don't try to optimize a single 1q gate
continue

new_circs = []
q = QuantumRegister(1, "q")
qc = QuantumCircuit(1)
for gate in run:
qc.append(gate.op, [q[0]], [])

qc._append(gate.op, [q[0]], [])
operator = Operator(qc)
new_circ = decomposer(operator)
new_dag = circuit_to_dag(new_circ)
dag.substitute_node_with_dag(run[0], new_dag)
# Delete the other nodes in the run
for current_node in run[1:]:
dag.remove_op_node(current_node)
for decomposer in self.basis:
new_circs.append(decomposer(operator))
if new_circs:
new_circ = min(new_circs, key=lambda circ: circ.depth())
if qc.depth() > new_circ.depth():
new_dag = circuit_to_dag(new_circ)
dag.substitute_node_with_dag(run[0], new_dag)
# Delete the other nodes in the run
for current_node in run[1:]:
dag.remove_op_node(current_node)
return dag


def _split_runs_on_parameters(runs):
"""Finds runs containing parameterized gates and splits them into sequential
runs excluding the parameterized gates.
"""

out = []
for run in runs:
groups = groupby(run, lambda x: x.op.is_parameterized())

for group_is_parameterized, gates in groups:
if not group_is_parameterized:
out.append(list(gates))

return out
108 changes: 108 additions & 0 deletions test/python/dagcircuit/test_dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
from qiskit.circuit import Measure
from qiskit.circuit import Reset
from qiskit.circuit import Gate, Instruction
from qiskit.circuit import Parameter
from qiskit.circuit.library.standard_gates.i import IGate
from qiskit.circuit.library.standard_gates.h import HGate
from qiskit.circuit.library.standard_gates.x import CXGate
from qiskit.circuit.library.standard_gates.z import CZGate
from qiskit.circuit.library.standard_gates.x import XGate
from qiskit.circuit.library.standard_gates.y import YGate
from qiskit.circuit.library.standard_gates.u1 import U1Gate
from qiskit.circuit.barrier import Barrier
from qiskit.dagcircuit.exceptions import DAGCircuitError
Expand Down Expand Up @@ -708,6 +710,112 @@ def test_dag_collect_runs_conditional_in_middle(self):
self.assertEqual(['h'], [x.name for x in run])
self.assertEqual([[self.qubit0]], [x.qargs for x in run])

def test_dag_collect_1q_runs(self):
"""Test the collect_1q_runs method with 3 different gates."""
self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0])
self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0])
self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0])
self.dag.apply_operation_back(CXGate(), [self.qubit2, self.qubit1])
self.dag.apply_operation_back(CXGate(), [self.qubit1, self.qubit2])
self.dag.apply_operation_back(HGate(), [self.qubit2])
collected_runs = self.dag.collect_1q_runs()
self.assertEqual(len(collected_runs), 2)
for run in collected_runs:
if run[0].name == 'h':
self.assertEqual(len(run), 1)
self.assertEqual(['h'], [x.name for x in run])
self.assertEqual([[self.qubit2]], [x.qargs for x in run])
elif run[0].name == 'u1':
self.assertEqual(len(run), 3)
self.assertEqual(['u1'] * 3, [x.name for x in run])
self.assertEqual(
[[self.qubit0], [self.qubit0], [self.qubit0]],
[x.qargs for x in run])
else:
self.fail('Unknown run encountered')

def test_dag_collect_1q_runs_start_with_conditional(self):
"""Test collect 1q runs with a conditional at the start of the run."""
h_gate = HGate()
h_gate.condition = self.condition
self.dag.apply_operation_back(
h_gate, [self.qubit0])
self.dag.apply_operation_back(HGate(), [self.qubit0])
self.dag.apply_operation_back(HGate(), [self.qubit0])
collected_runs = self.dag.collect_1q_runs()
self.assertEqual(len(collected_runs), 1)
run = collected_runs.pop()
self.assertEqual(len(run), 2)
self.assertEqual(['h', 'h'], [x.name for x in run])
self.assertEqual([[self.qubit0], [self.qubit0]],
[x.qargs for x in run])

def test_dag_collect_1q_runs_conditional_in_middle(self):
"""Test collect_1q_runs with a conditional in the middle of a run."""
h_gate = HGate()
h_gate.condition = self.condition
self.dag.apply_operation_back(HGate(), [self.qubit0])
self.dag.apply_operation_back(
h_gate, [self.qubit0])
self.dag.apply_operation_back(HGate(), [self.qubit0])
collected_runs = self.dag.collect_1q_runs()
# Should return 2 single h gate runs (1 before condition, 1 after)
self.assertEqual(len(collected_runs), 2)
for run in collected_runs:
self.assertEqual(len(run), 1)
self.assertEqual(['h'], [x.name for x in run])
self.assertEqual([[self.qubit0]], [x.qargs for x in run])

def test_dag_collect_1q_runs_with_parameterized_gate(self):
"""Test collect 1q splits on parameterized gates."""
theta = Parameter('theta')
self.dag.apply_operation_back(HGate(), [self.qubit0])
self.dag.apply_operation_back(HGate(), [self.qubit0])
self.dag.apply_operation_back(U1Gate(theta), [self.qubit0])
self.dag.apply_operation_back(XGate(), [self.qubit0])
self.dag.apply_operation_back(XGate(), [self.qubit0])
collected_runs = self.dag.collect_1q_runs()
self.assertEqual(len(collected_runs), 2)
run_gates = [[x.name for x in run] for run in collected_runs]
self.assertIn(['h', 'h'], run_gates)
self.assertIn(['x', 'x'], run_gates)
self.assertNotIn('u1', [x.name for run in collected_runs for x in run])

def test_dag_collect_1q_runs_with_cx_in_middle(self):
"""Test collect_1q_runs_with a cx in the middle of the run."""
self.dag.apply_operation_back(HGate(), [self.qubit0])
self.dag.apply_operation_back(HGate(), [self.qubit0])
self.dag.apply_operation_back(U1Gate(3.14), [self.qubit0])
self.dag.apply_operation_back(U1Gate(3.14), [self.qubit1])
self.dag.apply_operation_back(U1Gate(3.14), [self.qubit1])
self.dag.apply_operation_back(HGate(), [self.qubit1])
self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1])
self.dag.apply_operation_back(YGate(), [self.qubit0])
self.dag.apply_operation_back(YGate(), [self.qubit0])
self.dag.apply_operation_back(XGate(), [self.qubit1])
self.dag.apply_operation_back(XGate(), [self.qubit1])
collected_runs = self.dag.collect_1q_runs()
self.assertEqual(len(collected_runs), 4)
for run in collected_runs:
if run[0].name == 'h':
self.assertEqual(len(run), 3)
self.assertEqual(['h', 'h', 'u1'], [x.name for x in run])
self.assertEqual([[self.qubit0]] * 3, [x.qargs for x in run])
elif run[0].name == 'u1':
self.assertEqual(len(run), 3)
self.assertEqual(['u1', 'u1', 'h'], [x.name for x in run])
self.assertEqual([[self.qubit1]] * 3, [x.qargs for x in run])
elif run[0].name == 'x':
self.assertEqual(len(run), 2)
self.assertEqual(['x', 'x'], [x.name for x in run])
self.assertEqual([[self.qubit1]] * 2, [x.qargs for x in run])
elif run[0].name == 'y':
self.assertEqual(len(run), 2)
self.assertEqual(['y', 'y'], [x.name for x in run])
self.assertEqual([[self.qubit0]] * 2, [x.qargs for x in run])
else:
self.fail("Unknown run encountered")


class TestDagLayers(QiskitTestCase):
"""Test finding layers on the dag"""
Expand Down
28 changes: 28 additions & 0 deletions test/python/transpiler/test_optimize_1q_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from qiskit.circuit import QuantumRegister, QuantumCircuit, ClassicalRegister
from qiskit.circuit.library.standard_gates import U3Gate
from qiskit.circuit.random import random_circuit
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import Optimize1qGatesDecomposition
from qiskit.transpiler.passes import BasisTranslator
Expand Down Expand Up @@ -316,6 +317,33 @@ def test_identity_u1x(self):
result = passmanager.run(circuit)
self.assertEqual([], result.data)

def test_overcomplete_basis(self):
"""Test optimization with an overcomplete basis."""
circuit = random_circuit(3, 3, seed=42)
basis = ['rz', 'rxx', 'rx', 'ry', 'p', 'sx', 'u', 'cx']
passmanager = PassManager()
passmanager.append(BasisTranslator(sel, basis))
basis_translated = passmanager.run(circuit)
passmanager = PassManager()
passmanager.append(Optimize1qGatesDecomposition(basis))
result_full = passmanager.run(basis_translated)
self.assertTrue(Operator(circuit).equiv(Operator(result_full)))
self.assertGreater(basis_translated.depth(), result_full.depth())

def test_euler_decomposition_worse(self):
"""Ensure we don't decompose to a deeper circuit."""
circuit = QuantumCircuit(1)
circuit.rx(-np.pi / 2, 0)
circuit.rz(-np.pi / 2, 0)
basis = ['rx', 'rz']
passmanager = PassManager()
passmanager.append(BasisTranslator(sel, basis))
passmanager.append(Optimize1qGatesDecomposition(basis))
result = passmanager.run(circuit)
# decomposition of circuit will result in 3 gates instead of 2
# assert optimization pass doesn't use it.
self.assertEqual(result, circuit)


if __name__ == '__main__':
unittest.main()