Skip to content

Commit

Permalink
Add DAGCircuit output option to OneQubitEulerDecomposer
Browse files Browse the repository at this point in the history
This commit adds a new option to the euler one qubit decomposer class,
use_dag, which when set to True will return a DAGCircuit object for the
decomposed matrix instead of a QuantumCircuit object. This is useful for
transpiler passes that call the decomposer so that they don't have to
convert from a circuit to a dag. The passes that use the decomposer are
also updated to use this new option.

This was originally attempted before in Qiskit#5926 but was abandoned because
at the time there was no real performance improvement from doing this.
However, since then this class has changed quite a bit, and after Qiskit#9185
the overhead of conversion between QuantumCircuit and DAGCircuit is a
more critical component of the runtime performance of the 1 qubit
optimization pass. By operating natively in DAGCircuit from the
decomposer we're able to remove this overhead and directly substitute
the output of the decomposer in the pass.
  • Loading branch information
mtreinish committed Nov 23, 2022
1 parent 6dd8c22 commit e66a2ae
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 116 deletions.
198 changes: 101 additions & 97 deletions qiskit/quantum_info/synthesis/one_qubit_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

import numpy as np

from qiskit._accelerate import euler_one_qubit_decomposer
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.quantumregister import Qubit
from qiskit.circuit.library.standard_gates import (
UGate,
PhaseGate,
Expand All @@ -33,7 +34,6 @@
)
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators.predicates import is_unitary_matrix
from qiskit._accelerate import euler_one_qubit_decomposer

DEFAULT_ATOL = 1e-12

Expand Down Expand Up @@ -114,19 +114,41 @@ class OneQubitEulerDecomposer:
:math:`R\left(\theta+\pi,\frac{\pi}{2}-\lambda\right)`
"""

def __init__(self, basis="U3"):
def __init__(self, basis="U3", use_dag=False):
"""Initialize decomposer
Supported bases are: 'U', 'PSX', 'ZSXX', 'ZSX', 'U321', 'U3', 'U1X', 'RR', 'ZYZ', 'ZXZ',
'XYX', 'XZX'.
Args:
basis (str): the decomposition basis [Default: 'U3']
use_dag (bool): If true the output from calls to the decomposer
will be a :class:`~qiskit.dagcircuit.DAGCircuit` object instead of
:class:`~qiskit.circuit.QuantumCircuit`.
Raises:
QiskitError: If input basis is not recognized.
"""
self.basis = basis # sets: self._basis, self._params, self._circuit
self.use_dag = use_dag

def build_circuit(self, gates, global_phase):
"""Return the circuit or dag object from a list of gates."""
qr = [Qubit()]
if self.use_dag:
from qiskit.dagcircuit import dagcircuit

dag = dagcircuit.DAGCircuit()
dag.global_phase = global_phase
dag.add_qubits(qr)
for gate in gates:
dag.apply_operation_back(gate, [qr[0]])
return dag
else:
circuit = QuantumCircuit(qr, global_phase=global_phase)
for gate in gates:
circuit._append(gate, [qr[0]], [])
return circuit

def __call__(self, unitary, simplify=True, atol=DEFAULT_ATOL):
"""Decompose single qubit gate into a circuit.
Expand Down Expand Up @@ -254,8 +276,8 @@ def _params_u1x(mat):
theta, phi, lam, phase = OneQubitEulerDecomposer._params_zyz(mat)
return theta, phi, lam, phase - 0.5 * (theta + phi + lam)

@staticmethod
def _circuit_kak(
self,
theta,
phi,
lam,
Expand Down Expand Up @@ -289,8 +311,7 @@ def _circuit_kak(
QuantumCircuit: The assembled circuit.
"""
gphase = phase - (phi + lam) / 2
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr)
circuit = []
if not simplify:
atol = -1.0
# Early return for the middle-gate-free case
Expand All @@ -302,10 +323,9 @@ def _circuit_kak(
lam = _mod_2pi(lam, atol)
if abs(lam) > atol:

circuit._append(k_gate(lam), [qr[0]], [])
circuit.append(k_gate(lam))
gphase += lam / 2
circuit.global_phase = gphase
return circuit
return self.build_circuit(circuit, gphase)
if abs(theta - np.pi) < atol:
gphase += phi
lam, phi = lam - phi, 0
Expand All @@ -316,14 +336,13 @@ def _circuit_kak(
lam = _mod_2pi(lam, atol)
if abs(lam) > atol:
gphase += lam / 2
circuit._append(k_gate(lam), [qr[0]], [])
circuit._append(a_gate(theta), [qr[0]], [])
circuit.append(k_gate(lam))
circuit.append(a_gate(theta))
phi = _mod_2pi(phi, atol)
if abs(phi) > atol:
gphase += phi / 2
circuit._append(k_gate(phi), [qr[0]], [])
circuit.global_phase = gphase
return circuit
circuit.append(k_gate(phi))
return self.build_circuit(circuit, gphase)

def _circuit_zyz(
self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL, allow_non_canonical=True
Expand Down Expand Up @@ -385,167 +404,152 @@ def _circuit_xyx(
a_gate=RYGate,
)

@staticmethod
def _circuit_u3(theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr, global_phase=phase)
def _circuit_u3(self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
circuit = []
phi = _mod_2pi(phi, atol)
lam = _mod_2pi(lam, atol)
if not simplify or abs(theta) > atol or abs(phi) > atol or abs(lam) > atol:
circuit._append(U3Gate(theta, phi, lam), [qr[0]], [])
return circuit
circuit.append(U3Gate(theta, phi, lam))
return self.build_circuit(circuit, phase)

@staticmethod
def _circuit_u321(theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr, global_phase=phase)
def _circuit_u321(self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
circuit = []
if not simplify:
atol = -1.0
if abs(theta) < atol:
tot = _mod_2pi(phi + lam, atol)
if abs(tot) > atol:
circuit._append(U1Gate(tot), [qr[0]], [])
circuit.append(U1Gate(tot))
elif abs(theta - np.pi / 2) < atol:
circuit._append(U2Gate(_mod_2pi(phi, atol), _mod_2pi(lam, atol)), [qr[0]], [])
circuit.append(U2Gate(_mod_2pi(phi, atol), _mod_2pi(lam, atol)))
else:
circuit._append(U3Gate(theta, _mod_2pi(phi, atol), _mod_2pi(lam, atol)), [qr[0]], [])
return circuit
circuit.append(U3Gate(theta, _mod_2pi(phi, atol), _mod_2pi(lam, atol)))
return self.build_circuit(circuit, phase)

@staticmethod
def _circuit_u(theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr, global_phase=phase)
def _circuit_u(self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
circuit = []
if not simplify:
atol = -1.0
phi = _mod_2pi(phi, atol)
lam = _mod_2pi(lam, atol)
if abs(theta) > atol or abs(phi) > atol or abs(lam) > atol:
circuit._append(UGate(theta, phi, lam), [qr[0]], [])
return circuit
circuit.append(UGate(theta, phi, lam))
return self.build_circuit(circuit, phase)

@staticmethod
def _circuit_psx_gen(theta, phi, lam, phase, atol, pfun, xfun, xpifun=None):
def _circuit_psx_gen(self, theta, phi, lam, phase, atol, pfun, xfun, xpifun=None):
"""
Generic X90, phase decomposition
NOTE: `pfun` is responsible for eliding gates where appropriate (e.g., at angle value 0).
"""
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr, global_phase=phase)
circuit = []
global_phase = [phase]
# Early return for zero SX decomposition
if np.abs(theta) < atol:
pfun(circuit, qr, lam + phi)
return circuit
pfun(circuit, lam + phi, global_phase)
return self.build_circuit(circuit, global_phase[0])
# Early return for single SX decomposition
if abs(theta - np.pi / 2) < atol:
pfun(circuit, qr, lam - np.pi / 2)
xfun(circuit, qr)
pfun(circuit, qr, phi + np.pi / 2)
return circuit
pfun(circuit, lam - np.pi / 2, global_phase)
xfun(circuit, global_phase)
pfun(circuit, phi + np.pi / 2, global_phase)
return self.build_circuit(circuit, global_phase[0])
# General double SX decomposition
if abs(theta - np.pi) < atol:
circuit.global_phase += lam
global_phase[0] += lam
phi, lam = phi - lam, 0
if abs(_mod_2pi(lam + np.pi)) < atol or abs(_mod_2pi(phi)) < atol:
lam, theta, phi = lam + np.pi, -theta, phi + np.pi
circuit.global_phase -= theta
global_phase[0] -= theta
# Shift theta and phi to turn the decomposition from
# RZ(phi).RY(theta).RZ(lam) = RZ(phi).RX(-pi/2).RZ(theta).RX(pi/2).RZ(lam)
# into RZ(phi+pi).SX.RZ(theta+pi).SX.RZ(lam) .
theta, phi = theta + np.pi, phi + np.pi
circuit.global_phase -= np.pi / 2
global_phase[0] -= np.pi / 2
# Emit circuit
pfun(circuit, qr, lam)
pfun(circuit, lam, global_phase)
if xpifun and abs(_mod_2pi(theta)) < atol:
xpifun(circuit, qr)
xpifun(circuit)
else:
xfun(circuit, qr)
pfun(circuit, qr, theta)
xfun(circuit, qr)
pfun(circuit, qr, phi)
xfun(circuit, global_phase)
pfun(circuit, theta, global_phase)
xfun(circuit, global_phase)
pfun(circuit, phi, global_phase)

return circuit
return self.build_circuit(circuit, global_phase[0])

@staticmethod
def _circuit_psx(theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
def _circuit_psx(self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
if not simplify:
atol = -1.0

def fnz(circuit, qr, phi):
def fnz(circuit, phi, _global_phase):
phi = _mod_2pi(phi, atol)
if abs(phi) > atol:
circuit._append(PhaseGate(phi), [qr[0]], [])
circuit.append(PhaseGate(phi))

def fnx(circuit, qr):
circuit._append(SXGate(), [qr[0]], [])
def fnx(circuit, _global_phase):
circuit.append(SXGate())

return OneQubitEulerDecomposer._circuit_psx_gen(theta, phi, lam, phase, atol, fnz, fnx)
return self._circuit_psx_gen(theta, phi, lam, phase, atol, fnz, fnx)

@staticmethod
def _circuit_zsx(theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
def _circuit_zsx(self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
if not simplify:
atol = -1.0

def fnz(circuit, qr, phi):
def fnz(circuit, phi, global_phase):
phi = _mod_2pi(phi, atol)
if abs(phi) > atol:
circuit._append(RZGate(phi), [qr[0]], [])
circuit.global_phase += phi / 2
circuit.append(RZGate(phi))
global_phase[0] += phi / 2

def fnx(circuit, qr):
circuit._append(SXGate(), [qr[0]], [])
def fnx(circuit, _global_phase):
circuit.append(SXGate())

return OneQubitEulerDecomposer._circuit_psx_gen(theta, phi, lam, phase, atol, fnz, fnx)
return self._circuit_psx_gen(theta, phi, lam, phase, atol, fnz, fnx)

@staticmethod
def _circuit_u1x(theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
def _circuit_u1x(self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
if not simplify:
atol = -1.0

def fnz(circuit, qr, phi):
def fnz(circuit, phi, _global_phase):
phi = _mod_2pi(phi, atol)
if abs(phi) > atol:
circuit._append(U1Gate(phi), [qr[0]], [])
circuit.append(U1Gate(phi))

def fnx(circuit, qr):
circuit.global_phase += np.pi / 4
circuit._append(RXGate(np.pi / 2), [qr[0]], [])
def fnx(circuit, global_phase):
global_phase[0] += np.pi / 4
circuit.append(RXGate(np.pi / 2))

return OneQubitEulerDecomposer._circuit_psx_gen(theta, phi, lam, phase, atol, fnz, fnx)
return self._circuit_psx_gen(theta, phi, lam, phase, atol, fnz, fnx)

@staticmethod
def _circuit_zsxx(theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
def _circuit_zsxx(self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
if not simplify:
atol = -1.0

def fnz(circuit, qr, phi):
def fnz(circuit, phi, global_phase):
phi = _mod_2pi(phi, atol)
if abs(phi) > atol:
circuit._append(RZGate(phi), [qr[0]], [])
circuit.global_phase += phi / 2
circuit.append(RZGate(phi))
global_phase[0] += phi / 2

def fnx(circuit, qr):
circuit._append(SXGate(), [qr[0]], [])
def fnx(circuit, _global_phase):
circuit.append(SXGate())

def fnxpi(circuit, qr):
circuit._append(XGate(), [qr[0]], [])
def fnxpi(circuit):
circuit.append(XGate())

return OneQubitEulerDecomposer._circuit_psx_gen(
theta, phi, lam, phase, atol, fnz, fnx, fnxpi
)
return self._circuit_psx_gen(theta, phi, lam, phase, atol, fnz, fnx, fnxpi)

@staticmethod
def _circuit_rr(theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr, global_phase=phase)
def _circuit_rr(self, theta, phi, lam, phase, simplify=True, atol=DEFAULT_ATOL):
circuit = []
if not simplify:
atol = -1.0
if abs(theta) < atol and abs(phi) < atol and abs(lam) < atol:
return circuit
return self.build_circuit(circuit, phase)
if abs(theta - np.pi) > atol:
circuit._append(RGate(theta - np.pi, _mod_2pi(np.pi / 2 - lam, atol)), [qr[0]], [])
circuit._append(RGate(np.pi, _mod_2pi(0.5 * (phi - lam + np.pi), atol)), [qr[0]], [])
return circuit
circuit.append(RGate(theta - np.pi, _mod_2pi(np.pi / 2 - lam, atol)))
circuit.append(RGate(np.pi, _mod_2pi(0.5 * (phi - lam + np.pi), atol)))
return self.build_circuit(circuit, phase)


def _mod_2pi(angle: float, atol: float = 0):
Expand Down
17 changes: 9 additions & 8 deletions qiskit/transpiler/passes/optimization/optimize_1q_commutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
import logging
from collections import deque

from qiskit.circuit import QuantumCircuit
from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit import QuantumRegister
from qiskit.circuit.library.standard_gates import CXGate, RZXGate
from qiskit.converters import circuit_to_dag
from qiskit.dagcircuit import DAGOpNode
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passes.optimization.optimize_1q_decomposition import (
Expand Down Expand Up @@ -157,18 +157,19 @@ def _resynthesize(self, new_run):
NOTE: Returns None when resynthesis is not possible.
"""
if len(new_run) == 0:
return QuantumCircuit(1)
dag = DAGCircuit()
dag.add_qreg(QuantumRegister(1))
return dag

return self._optimize1q._resynthesize_run(new_run)

@staticmethod
def _replace_subdag(dag, old_run, new_circ):
def _replace_subdag(dag, old_run, new_dag):
"""
Replaces a nonempty sequence `old_run` of `DAGNode`s, assumed to be a complete chain in
`dag`, with the circuit `new_circ`.
"""

new_dag = circuit_to_dag(new_circ)
node_map = dag.substitute_node_with_dag(old_run[0], new_dag)

for node in old_run[1:]:
Expand Down Expand Up @@ -220,9 +221,9 @@ def _step(self, dag):
dag,
(preceding_run or []) + run + (succeeding_run or []),
(
(new_preceding_run or QuantumCircuit(1)).data
+ (new_run or QuantumCircuit(1)).data
+ (new_succeeding_run or QuantumCircuit(1)).data
list(new_preceding_run.op_nodes())
+ list(new_run.op_nodes())
+ list(new_succeeding_run.op_nodes())
),
self._optimize1q._basis_gates,
qubit_indices[run[0].qargs[0]],
Expand Down
Loading

0 comments on commit e66a2ae

Please sign in to comment.