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

Add DAGCircuit output option to OneQubitEulerDecomposer #9188

Merged
merged 3 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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 @@ -240,8 +262,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 @@ -275,8 +297,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 @@ -288,10 +309,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 @@ -302,14 +322,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 @@ -371,167 +390,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]
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
# 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
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())
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
),
self._optimize1q._basis_gates,
qubit_indices[run[0].qargs[0]],
Expand Down
Loading