Skip to content

Commit

Permalink
Merge branch 'main' into move-equivalence
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss authored Jul 25, 2024
2 parents aa626b9 + aaaf107 commit 1aaf17a
Show file tree
Hide file tree
Showing 9 changed files with 376 additions and 345 deletions.
374 changes: 279 additions & 95 deletions crates/accelerate/src/two_qubit_decompose.rs

Large diffs are not rendered by default.

25 changes: 0 additions & 25 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

from qiskit import user_config
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumregister import Qubit
from qiskit.dagcircuit import DAGCircuit
from qiskit.providers.backend import Backend
from qiskit.providers.backend_compat import BackendV2Converter
Expand Down Expand Up @@ -339,10 +338,7 @@ def callback_func(**kwargs):
if translation_method is None and hasattr(backend, "get_translation_stage_plugin"):
translation_method = backend.get_translation_stage_plugin()

initial_layout = _parse_initial_layout(initial_layout)
approximation_degree = _parse_approximation_degree(approximation_degree)
output_name = _parse_output_name(output_name, circuits)

coupling_map = _parse_coupling_map(coupling_map)
_check_circuits_coupling_map(circuits, coupling_map, backend)

Expand Down Expand Up @@ -425,27 +421,6 @@ def _parse_coupling_map(coupling_map):
return coupling_map


def _parse_initial_layout(initial_layout):
# initial_layout could be None, or a list of ints, e.g. [0, 5, 14]
# or a list of tuples/None e.g. [qr[0], None, qr[1]] or a dict e.g. {qr[0]: 0}
if initial_layout is None or isinstance(initial_layout, Layout):
return initial_layout
if isinstance(initial_layout, dict):
return Layout(initial_layout)
initial_layout = list(initial_layout)
if all(phys is None or isinstance(phys, Qubit) for phys in initial_layout):
return Layout.from_qubit_list(initial_layout)
return initial_layout


def _parse_approximation_degree(approximation_degree):
if approximation_degree is None:
return None
if approximation_degree < 0.0 or approximation_degree > 1.0:
raise TranspilerError("Approximation degree must be in [0.0, 1.0]")
return approximation_degree


def _parse_output_name(output_name, circuits):
# naming and returning circuits
# output_name could be either a string or a list
Expand Down
89 changes: 0 additions & 89 deletions qiskit/synthesis/two_qubit/two_qubit_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -699,95 +699,6 @@ def traces(self, target):
return self._inner_decomposer.traces(target._inner_decomposition)


class TwoQubitDecomposeUpToDiagonal:
"""
Class to decompose two qubit unitaries into the product of a diagonal gate
and another unitary gate which can be represented by two CX gates instead of the
usual three. This can be used when neighboring gates commute with the diagonal to
potentially reduce overall CX count.
"""

def __init__(self):
sy = np.array([[0, -1j], [1j, 0]])
self.sysy = np.kron(sy, sy)

def _u4_to_su4(self, u4):
phase_factor = np.conj(np.linalg.det(u4) ** (-1 / u4.shape[0]))
su4 = u4 / phase_factor
return su4, cmath.phase(phase_factor)

def _gamma(self, mat):
"""
proposition II.1: this invariant characterizes when two operators in U(4),
say u, v, are equivalent up to single qubit gates:
u ≡ v -> Det(γ(u)) = Det(±(γ(v)))
"""
sumat, _ = self._u4_to_su4(mat)
sysy = self.sysy
return sumat @ sysy @ sumat.T @ sysy

def _cx0_test(self, mat):
# proposition III.1: zero cx sufficient
gamma = self._gamma(mat)
evals = np.linalg.eigvals(gamma)
return np.all(np.isclose(evals, np.ones(4)))

def _cx1_test(self, mat):
# proposition III.2: one cx sufficient
gamma = self._gamma(mat)
evals = np.linalg.eigvals(gamma)
uvals, ucnts = np.unique(np.round(evals, 10), return_counts=True)
return (
len(uvals) == 2
and all(ucnts == 2)
and all((np.isclose(x, 1j)) or np.isclose(x, -1j) for x in uvals)
)

def _cx2_test(self, mat):
# proposition III.3: two cx sufficient
gamma = self._gamma(mat)
return np.isclose(np.trace(gamma).imag, 0)

def _real_trace_transform(self, mat):
"""
Determine diagonal gate such that
U3 = D U2
Where U3 is a general two-qubit gate which takes 3 cnots, D is a
diagonal gate, and U2 is a gate which takes 2 cnots.
"""
a1 = (
-mat[1, 3] * mat[2, 0]
+ mat[1, 2] * mat[2, 1]
+ mat[1, 1] * mat[2, 2]
- mat[1, 0] * mat[2, 3]
)
a2 = (
mat[0, 3] * mat[3, 0]
- mat[0, 2] * mat[3, 1]
- mat[0, 1] * mat[3, 2]
+ mat[0, 0] * mat[3, 3]
)
theta = 0 # arbitrary
phi = 0 # arbitrary
psi = np.arctan2(a1.imag + a2.imag, a1.real - a2.real) - phi
diag = np.diag(np.exp(-1j * np.array([theta, phi, psi, -(theta + phi + psi)])))
return diag

def __call__(self, mat):
"""do the decomposition"""
su4, phase = self._u4_to_su4(mat)
real_map = self._real_trace_transform(su4)
mapped_su4 = real_map @ su4
if not self._cx2_test(mapped_su4):
warnings.warn("Unitary decomposition up to diagonal may use an additionl CX gate.")
circ = two_qubit_cnot_decompose(mapped_su4)
circ.global_phase += phase
return real_map.conj(), circ


# This weird duplicated lazy structure is for backwards compatibility; Qiskit has historically
# always made ``two_qubit_cnot_decompose`` available publicly immediately on import, but it's quite
# expensive to construct, and we want to defer the object's creation until it's actually used. We
Expand Down
6 changes: 4 additions & 2 deletions qiskit/synthesis/unitary/qsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from qiskit.circuit.library.generalized_gates.uc_pauli_rot import UCPauliRotGate, _EPS
from qiskit.circuit.library.generalized_gates.ucry import UCRYGate
from qiskit.circuit.library.generalized_gates.ucrz import UCRZGate
from qiskit._accelerate.two_qubit_decompose import two_qubit_decompose_up_to_diagonal


def qs_decomposition(
Expand Down Expand Up @@ -253,7 +254,7 @@ def _apply_a2(circ):
from qiskit.quantum_info import Operator
from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate

decomposer = two_qubit_decompose.TwoQubitDecomposeUpToDiagonal()
decomposer = two_qubit_decompose_up_to_diagonal
ccirc = transpile(circ, basis_gates=["u", "cx", "qsd2q"], optimization_level=0)
ind2q = []
# collect 2q instrs
Expand All @@ -275,7 +276,8 @@ def _apply_a2(circ):
instr2 = ccirc.data[ind2]
mat2 = Operator(instr2.operation).data
# rollover
dmat, qc2cx = decomposer(mat1)
dmat, qc2cx_data = decomposer(mat1)
qc2cx = QuantumCircuit._from_circuit_data(qc2cx_data)
ccirc.data[ind1] = instr1.replace(operation=qc2cx.to_gate())
mat2 = mat2 @ dmat
ccirc.data[ind2] = instr2.replace(UnitaryGate(mat2))
Expand Down
3 changes: 3 additions & 0 deletions qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
CZGate,
RXXGate,
RZXGate,
RZZGate,
ECRGate,
RXGate,
SXGate,
Expand Down Expand Up @@ -781,6 +782,8 @@ def _replace_parameterized_gate(op):
op = RXXGate(pi / 2)
elif isinstance(op, RZXGate) and isinstance(op.params[0], Parameter):
op = RZXGate(pi / 4)
elif isinstance(op, RZZGate) and isinstance(op.params[0], Parameter):
op = RZZGate(pi / 2)
return op

try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@

from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
from qiskit.circuit.quantumregister import Qubit
from qiskit.providers.backend_compat import BackendV2Converter
from qiskit.transpiler.coupling import CouplingMap
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.instruction_durations import InstructionDurations
from qiskit.transpiler.layout import Layout
from qiskit.transpiler.passmanager_config import PassManagerConfig
from qiskit.transpiler.target import Target, target_to_backend_properties
from qiskit.transpiler.timing_constraints import TimingConstraints
Expand Down Expand Up @@ -319,6 +321,10 @@ def generate_preset_pass_manager(
if backend_properties is None:
backend_properties = target_to_backend_properties(target)

# Parse non-target dependent pm options
initial_layout = _parse_initial_layout(initial_layout)
approximation_degree = _parse_approximation_degree(approximation_degree)

pm_options = {
"target": target,
"basis_gates": basis_gates,
Expand Down Expand Up @@ -470,3 +476,24 @@ def _parse_timing_constraints(backend, timing_constraints):
elif backend is not None:
timing_constraints = backend.target.timing_constraints()
return timing_constraints


def _parse_initial_layout(initial_layout):
# initial_layout could be None, or a list of ints, e.g. [0, 5, 14]
# or a list of tuples/None e.g. [qr[0], None, qr[1]] or a dict e.g. {qr[0]: 0}
if initial_layout is None or isinstance(initial_layout, Layout):
return initial_layout
if isinstance(initial_layout, dict):
return Layout(initial_layout)
initial_layout = list(initial_layout)
if all(phys is None or isinstance(phys, Qubit) for phys in initial_layout):
return Layout.from_qubit_list(initial_layout)
return initial_layout


def _parse_approximation_degree(approximation_degree):
if approximation_degree is None:
return None
if approximation_degree < 0.0 or approximation_degree > 1.0:
raise TranspilerError("Approximation degree must be in [0.0, 1.0]")
return approximation_degree
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ upgrade_transpiler:
If a `backend` input of type :class:`.BackendV1` is provided, it will be
converted to :class:`.BackendV2` to expose its :class:`.Target`. This change does
not require any user action.
fixes:
- |
A series of input-handling inconsistencies between :func:`.transpile` and :func:`.generate_preset_pass_manager`
have been fixed. These inconsistencies would lead to different transpilation outputs for the same inputs,
or :func:`.generate_preset_pass_manager` failing for certain input combinations accepted by :func:`.transpile`.
Loading

0 comments on commit 1aaf17a

Please sign in to comment.