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

Oxidize the internals of Optimize1qGatesDecomposition #9578

Merged
merged 20 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0815082
Oxidize the internals of Optimize1qGatesDecomposition
mtreinish Feb 13, 2023
d0871d6
Move all error calculation to rust
mtreinish Feb 14, 2023
1650193
Merge remote-tracking branch 'origin/main' into rusty-euler-one_qubit
mtreinish Feb 14, 2023
81679b6
Remove parallel iteration over multiple target basis
mtreinish Feb 14, 2023
4d8dbf0
Fix small oversights in internal pass usage
mtreinish Feb 14, 2023
8b56342
Add release note
mtreinish Feb 14, 2023
66fde83
Simplify logic to construct error map
mtreinish Feb 16, 2023
89db1da
Update comments, docstrings, and variable names in optimize_1q_decomp…
mtreinish Feb 16, 2023
a3be3c6
Make constant list of valid bases more local
mtreinish Feb 16, 2023
6d01f43
Merge remote-tracking branch 'origin/main' into rusty-euler-one_qubit
mtreinish Feb 16, 2023
b192444
Remove clippy unwrap suppression and use match syntax
mtreinish Feb 16, 2023
01c7e33
Merge branch 'main' into rusty-euler-one_qubit
mtreinish Feb 28, 2023
90243e8
Update releasenotes/notes/speedup-one-qubit-optimize-pass-483429af948…
mtreinish Mar 2, 2023
37f3975
Use to_object() instead of clone().into_py()
mtreinish Mar 2, 2023
b8992a7
Remove out of date comment
mtreinish Mar 2, 2023
5745c83
Use FnOnce for X type in circuit_psx_gen
mtreinish Mar 2, 2023
95f6974
Add rem_euclid comment
mtreinish Mar 2, 2023
e6ec7e6
Merge remote-tracking branch 'origin/main' into rusty-euler-one_qubit
mtreinish Mar 2, 2023
dee6bac
Fix u3/u321 condition in _possible_decomposers
mtreinish Mar 2, 2023
a0c19f6
Merge branch 'main' into rusty-euler-one_qubit
mergify[bot] Mar 2, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ def _resynthesize(self, run, qubit):
operator = run[0].op.to_matrix()
for gate in run[1:]:
operator = gate.op.to_matrix().dot(operator)
return self._optimize1q._resynthesize_run(operator, qubit)
return self._optimize1q._gate_sequence_to_dag(
self._optimize1q._resynthesize_run(operator, qubit)
)

@staticmethod
def _replace_subdag(dag, old_run, new_dag):
Expand Down
144 changes: 89 additions & 55 deletions qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,45 @@
"""Optimize chains of single-qubit gates using Euler 1q decomposer"""

import logging
from functools import partial
import numpy as np
import math

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passes.utils import control_flow
from qiskit.quantum_info.synthesis import one_qubit_decompose
from qiskit._accelerate import euler_one_qubit_decomposer
from qiskit.circuit.library.standard_gates import (
UGate,
PhaseGate,
U3Gate,
U2Gate,
U1Gate,
RXGate,
RYGate,
RZGate,
RGate,
SXGate,
XGate,
)
from qiskit.circuit import Qubit
from qiskit.dagcircuit.dagcircuit import DAGCircuit


logger = logging.getLogger(__name__)

NAME_MAP = {
"u": UGate,
"u1": U1Gate,
"u2": U2Gate,
"u3": U3Gate,
"p": PhaseGate,
"rx": RXGate,
"ry": RYGate,
"rz": RZGate,
"r": RGate,
"sx": SXGate,
"x": XGate,
}
mtreinish marked this conversation as resolved.
Show resolved Hide resolved


class Optimize1qGatesDecomposition(TransformationPass):
"""Optimize chains of single-qubit gates by combining them into a single gate.
Expand Down Expand Up @@ -58,6 +88,23 @@ def __init__(self, basis=None, target=None):
self._global_decomposers = _possible_decomposers(None)
self._basis_gates = None

self.error_map = self._build_error_map()

def _build_error_map(self):
if self._target is not None:
error_map = euler_one_qubit_decomposer.OneQubitGateErrorMap(self._target.num_qubits)
for qubit in range(self._target.num_qubits):
gate_error = {}
for gate in self._target:
if self._target[gate] is not None:
props = self._target[gate].get((qubit,), None)
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
if props is not None and props.error is not None:
gate_error[gate] = props.error
error_map.add_qubit(gate_error)
return error_map
else:
return None

def _resynthesize_run(self, matrix, qubit=None):
"""
Resynthesizes one 2x2 `matrix`, typically extracted via `dag.collect_1q_runs`.
Expand All @@ -81,13 +128,23 @@ def _resynthesize_run(self, matrix, qubit=None):
self._local_decomposers_cache[qubits_tuple] = decomposers
else:
decomposers = self._global_decomposers
best_synth_circuit = euler_one_qubit_decomposer.unitary_to_gate_sequence(
matrix,
decomposers,
qubit,
self.error_map,
)
return best_synth_circuit

new_circs = [decomposer._decompose(matrix) for decomposer in decomposers]
def _gate_sequence_to_dag(self, best_synth_circuit):
qubits = [Qubit()]
out_dag = DAGCircuit()
out_dag.add_qubits(qubits)
out_dag.global_phase = best_synth_circuit.global_phase

if len(new_circs) == 0:
return None
else:
return min(new_circs, key=partial(_error, target=self._target, qubit=qubit))
for gate_name, angles in best_synth_circuit:
out_dag.apply_operation_back(NAME_MAP[gate_name](*angles), qubits)
return out_dag

def _substitution_checks(self, dag, old_run, new_circ, basis, qubit):
"""
Expand Down Expand Up @@ -115,11 +172,8 @@ def _substitution_checks(self, dag, old_run, new_circ, basis, qubit):
# then we _try_ to decompose, using the results if we see improvement.
return (
uncalibrated_and_not_basis_p
or (
uncalibrated_p
and _error(new_circ, self._target, qubit) < _error(old_run, self._target, qubit)
)
or np.isclose(_error(new_circ, self._target, qubit), 0)
or (uncalibrated_p and self._error(new_circ, qubit) < self._error(old_run, qubit))
or math.isclose(self._error(new_circ, qubit)[0], 0)
)

@control_flow.trivial_recurse
Expand Down Expand Up @@ -147,62 +201,42 @@ def run(self, dag):
basis = self._target.operation_names_for_qargs((qubit,))

if new_dag is not None and self._substitution_checks(dag, run, new_dag, basis, qubit):
new_dag = self._gate_sequence_to_dag(new_dag)
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
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 _error(self, circuit, qubit):
"""
Calculate a rough error for a `circuit` that runs on a specific
`qubit` of `target` (circuit could also be a list of DAGNodes)

Use basis errors from target if available, otherwise use length
of circuit as a weak proxy for error.
"""
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(circuit, euler_one_qubit_decomposer.OneQubitGateSequence):
return euler_one_qubit_decomposer.compute_error_one_qubit_sequence(
circuit, qubit, self.error_map
)
else:
circuit_list = [(x.op.name, []) for x in circuit]
return euler_one_qubit_decomposer.compute_error_list(
circuit_list, qubit, self.error_map
)


def _possible_decomposers(basis_set):
decomposers = []
if basis_set is None:
decomposers = [
one_qubit_decompose.OneQubitEulerDecomposer(basis, use_dag=True)
for basis in one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES
]
decomposers = list(one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES)
else:
euler_basis_gates = one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES
for euler_basis_name, gates in euler_basis_gates.items():
if set(gates).issubset(basis_set):
decomposer = one_qubit_decompose.OneQubitEulerDecomposer(
euler_basis_name, use_dag=True
)
decomposers.append(decomposer)
decomposers.append(euler_basis_name)
if "U3" in basis_set and "U321" in basis_set:
decomposers.remove("U3")
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
return decomposers


def _error(circuit, target=None, qubit=None):
"""
Calculate a rough error for a `circuit` that runs on a specific
`qubit` of `target` (circuit could also be a list of DAGNodes)

Use basis errors from target if available, otherwise use length
of circuit as a weak proxy for error.
"""
if target is None:
if isinstance(circuit, list):
return len(circuit)
else:
return len(circuit._multi_graph) - 2
else:
if isinstance(circuit, list):
gate_fidelities = [
1 - getattr(target[node.name].get((qubit,)), "error", 0.0) for node in circuit
]
else:
gate_fidelities = [
1 - getattr(target[inst.op.name].get((qubit,)), "error", 0.0)
for inst in circuit.op_nodes()
]
gate_error = 1 - np.product(gate_fidelities)
if gate_error == 0.0:
if isinstance(circuit, list):
return -100 + len(circuit)
else:
return -100 + len(
circuit._multi_graph
) # prefer shorter circuits among those with zero error
else:
return gate_error
4 changes: 3 additions & 1 deletion qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,9 @@ def run(self, unitary, **options):

if unitary.shape == (2, 2):
_decomposer1q = Optimize1qGatesDecomposition(basis_gates, target)
return _decomposer1q._resynthesize_run(unitary, qubits[0]) # already in dag format
return _decomposer1q._gate_sequence_to_dag(
_decomposer1q._resynthesize_run(unitary, qubits[0])
) # already in dag format
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
elif unitary.shape == (4, 4):
# select synthesizers that can lower to the target
if target is not None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
features:
- |
The runtime performance of the :class:`~.Optimize1qGatesDecomposition`
transpiler pass hass been significantly improved. This was done by both
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
rewriting all the computation for the pass in Rust and also decreasing
the amount of intermediate objects created as part of the pass's
execution. This should also correspond to a similar improvement
in the runtime performance of :func:`~.transpile` with the
``optimization_level`` keyword argument set to ``1``, ``2``, or ``3``.
Loading