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

Avoid intermediate DAGCircuit construction in 2q synthesis #12179

Merged
merged 1 commit into from
Apr 30, 2024
Merged
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
131 changes: 98 additions & 33 deletions qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from qiskit.synthesis.two_qubit.two_qubit_decompose import (
TwoQubitBasisDecomposer,
TwoQubitWeylDecomposition,
GATE_NAME_MAP,
)
from qiskit.quantum_info import Operator
from qiskit.circuit import ControlFlowOp, Gate, Parameter
Expand Down Expand Up @@ -293,7 +294,7 @@ def __init__(
natural_direction: bool | None = None,
synth_gates: list[str] | None = None,
method: str = "default",
min_qubits: int = None,
min_qubits: int = 0,
plugin_config: dict = None,
target: Target = None,
):
Expand Down Expand Up @@ -499,27 +500,55 @@ def _run_main_loop(
]
)

for node in dag.named_nodes(*self._synth_gates):
if self._min_qubits is not None and len(node.qargs) < self._min_qubits:
continue
synth_dag = None
unitary = node.op.to_matrix()
n_qubits = len(node.qargs)
if (plugin_method.max_qubits is not None and n_qubits > plugin_method.max_qubits) or (
plugin_method.min_qubits is not None and n_qubits < plugin_method.min_qubits
):
method, kwargs = default_method, default_kwargs
out_dag = dag.copy_empty_like()
for node in dag.topological_op_nodes():
if node.op.name == "unitary" and len(node.qargs) >= self._min_qubits:
synth_dag = None
unitary = node.op.to_matrix()
n_qubits = len(node.qargs)
if (
plugin_method.max_qubits is not None and n_qubits > plugin_method.max_qubits
) or (plugin_method.min_qubits is not None and n_qubits < plugin_method.min_qubits):
method, kwargs = default_method, default_kwargs
else:
method, kwargs = plugin_method, plugin_kwargs
if method.supports_coupling_map:
kwargs["coupling_map"] = (
self._coupling_map,
[qubit_indices[x] for x in node.qargs],
)
synth_dag = method.run(unitary, **kwargs)
if synth_dag is None:
out_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False)
continue
if isinstance(synth_dag, DAGCircuit):
qubit_map = dict(zip(synth_dag.qubits, node.qargs))
for node in synth_dag.topological_op_nodes():
out_dag.apply_operation_back(
node.op, (qubit_map[x] for x in node.qargs), check=False
)
out_dag.global_phase += synth_dag.global_phase
else:
node_list, global_phase, gate = synth_dag
qubits = node.qargs
for (
op_name,
params,
qargs,
) in node_list:
if op_name == "USER_GATE":
op = gate
else:
op = GATE_NAME_MAP[op_name](*params)
out_dag.apply_operation_back(
op,
(qubits[x] for x in qargs),
check=False,
)
out_dag.global_phase += global_phase
else:
method, kwargs = plugin_method, plugin_kwargs
if method.supports_coupling_map:
kwargs["coupling_map"] = (
self._coupling_map,
[qubit_indices[x] for x in node.qargs],
)
synth_dag = method.run(unitary, **kwargs)
if synth_dag is not None:
dag.substitute_node_with_dag(node, synth_dag)
return dag
out_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False)
return out_dag


def _build_gate_lengths(props=None, target=None):
Expand Down Expand Up @@ -893,6 +922,20 @@ def run(self, unitary, **options):
decomposers2q = [decomposer2q] if decomposer2q is not None else []
# choose the cheapest output among synthesized circuits
synth_circuits = []
# If we have a single TwoQubitBasisDecomposer skip dag creation as we don't need to
# store and can instead manually create the synthesized gates directly in the output dag
if len(decomposers2q) == 1 and isinstance(decomposers2q[0], TwoQubitBasisDecomposer):
preferred_direction = _preferred_direction(
decomposers2q[0],
qubits,
natural_direction,
coupling_map,
gate_lengths,
gate_errors,
)
return self._synth_su4_no_dag(
unitary, decomposers2q[0], preferred_direction, approximation_degree
)
for decomposer2q in decomposers2q:
preferred_direction = _preferred_direction(
decomposer2q, qubits, natural_direction, coupling_map, gate_lengths, gate_errors
Expand All @@ -919,6 +962,24 @@ def run(self, unitary, **options):
return synth_circuit
return circuit_to_dag(synth_circuit)

def _synth_su4_no_dag(self, unitary, decomposer2q, preferred_direction, approximation_degree):
approximate = not approximation_degree == 1.0
synth_circ = decomposer2q._inner_decomposer(unitary, approximate=approximate)
if not preferred_direction:
return (synth_circ, synth_circ.global_phase, decomposer2q.gate)

synth_direction = None
# if the gates in synthesis are in the opposite direction of the preferred direction
# resynthesize a new operator which is the original conjugated by swaps.
# this new operator is doubly mirrored from the original and is locally equivalent.
for op_name, _params, qubits in synth_circ:
if op_name in {"USER_GATE", "cx"}:
synth_direction = qubits
if synth_direction is not None and synth_direction != preferred_direction:
# TODO: Avoid using a dag to correct the synthesis direction
return self._reversed_synth_su4(unitary, decomposer2q, approximation_degree)
return (synth_circ, synth_circ.global_phase, decomposer2q.gate)

def _synth_su4(self, su4_mat, decomposer2q, preferred_direction, approximation_degree):
approximate = not approximation_degree == 1.0
synth_circ = decomposer2q(su4_mat, approximate=approximate, use_dag=True)
Expand All @@ -932,16 +993,20 @@ def _synth_su4(self, su4_mat, decomposer2q, preferred_direction, approximation_d
if inst.op.num_qubits == 2:
synth_direction = [synth_circ.find_bit(q).index for q in inst.qargs]
if synth_direction is not None and synth_direction != preferred_direction:
su4_mat_mm = su4_mat.copy()
su4_mat_mm[[1, 2]] = su4_mat_mm[[2, 1]]
su4_mat_mm[:, [1, 2]] = su4_mat_mm[:, [2, 1]]
synth_circ = decomposer2q(su4_mat_mm, approximate=approximate, use_dag=True)
out_dag = DAGCircuit()
out_dag.global_phase = synth_circ.global_phase
out_dag.add_qubits(list(reversed(synth_circ.qubits)))
flip_bits = out_dag.qubits[::-1]
for node in synth_circ.topological_op_nodes():
qubits = tuple(flip_bits[synth_circ.find_bit(x).index] for x in node.qargs)
out_dag.apply_operation_back(node.op, qubits, check=False)
return out_dag
return self._reversed_synth_su4(su4_mat, decomposer2q, approximation_degree)
return synth_circ

def _reversed_synth_su4(self, su4_mat, decomposer2q, approximation_degree):
approximate = not approximation_degree == 1.0
su4_mat_mm = su4_mat.copy()
su4_mat_mm[[1, 2]] = su4_mat_mm[[2, 1]]
su4_mat_mm[:, [1, 2]] = su4_mat_mm[:, [2, 1]]
synth_circ = decomposer2q(su4_mat_mm, approximate=approximate, use_dag=True)
out_dag = DAGCircuit()
out_dag.global_phase = synth_circ.global_phase
out_dag.add_qubits(list(reversed(synth_circ.qubits)))
flip_bits = out_dag.qubits[::-1]
for node in synth_circ.topological_op_nodes():
qubits = tuple(flip_bits[synth_circ.find_bit(x).index] for x in node.qargs)
out_dag.apply_operation_back(node.op, qubits, check=False)
return out_dag
Loading