Skip to content

Commit

Permalink
fix: transpilation for targets with only Clifford + T gate basis
Browse files Browse the repository at this point in the history
  • Loading branch information
alxthm committed Dec 19, 2024
1 parent b10d7cc commit f022b63
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 38 deletions.
144 changes: 107 additions & 37 deletions qiskit_alice_bob_provider/plugins/state_preparation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##############################################################################


from copy import deepcopy
from typing import Any, Callable, Dict

import numpy as np
from qiskit.circuit import ControlFlowOp, Instruction, Qubit, Reset
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
from qiskit.circuit.library import Initialize
from qiskit.circuit.library import Initialize, RZGate
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.dagcircuit import DAGCircuit, DAGInNode, DAGOpNode
from qiskit.transpiler import (
Expand All @@ -30,6 +29,8 @@
TranspilerError,
)
from qiskit.transpiler.passes import (
BasisTranslator,
HighLevelSynthesis,
TrivialLayout,
UnitarySynthesis,
UnrollCustomDefinitions,
Expand All @@ -40,7 +41,6 @@
)
from qiskit.transpiler.preset_passmanagers.common import (
generate_embed_passmanager,
generate_translation_passmanager,
)
from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin

Expand All @@ -49,6 +49,29 @@ def _reset_prep() -> Instruction:
return Reset()


def _get_unitary_synthesis(config: PassManagerConfig):
return CustomUnitarySynthesis(
config.basis_gates,
approximation_degree=config.approximation_degree,
coupling_map=config.coupling_map,
backend_props=config.backend_properties,
plugin_config=config.unitary_synthesis_plugin_config,
method=config.unitary_synthesis_method,
target=config.target,
)


def _get_high_level_synthesis(config: PassManagerConfig):
return HighLevelSynthesis(
hls_config=config.hls_config,
coupling_map=config.coupling_map,
target=config.target,
use_qubit_indices=True,
equivalence_library=SessionEquivalenceLibrary,
basis_gates=config.basis_gates,
)


class EnsurePreparationPass(TransformationPass):
"""
A transpilation pass that ensures there is a state preparation at the
Expand Down Expand Up @@ -305,6 +328,7 @@ def pass_manager(
custom_pm.append(IntToLabelInitializePass())
custom_pm.append(BreakDownInitializePass())

# Can probably be removed (replaced by HighLevelSynthesis?)
custom_pm.append(
UnrollCustomDefinitions(
equivalence_library=SessionEquivalenceLibrary,
Expand All @@ -313,41 +337,87 @@ def pass_manager(
)
)

default_pm = generate_translation_passmanager(
target=pass_manager_config.target,
basis_gates=pass_manager_config.basis_gates,
method='translator',
approximation_degree=pass_manager_config.approximation_degree,
coupling_map=pass_manager_config.coupling_map,
backend_props=pass_manager_config.backend_properties,
unitary_synthesis_method=(
pass_manager_config.unitary_synthesis_method
),
unitary_synthesis_plugin_config=(
pass_manager_config.unitary_synthesis_plugin_config
# Replace passes from qiskit generate_translation_passmanager() with
# passes that work for us.
# By default, qiskit returns
# [UnitarySynthesis, HighLevelSynthesis, BasisTranslator]
# In our case, we need
# 1. to replace UnitarySynthesis with our CustomUnitarySynthesis class
# 2. to adapt the passes to handle the "Clifford + T gate basis" case

if 'rz' in pass_manager_config.target:
# In this case, no need to modify the BasisTranslator pass and to
# force the SK synthesis after.
need_synthesis = False
basis_translator_target = pass_manager_config.target
else:
# In this case, our basis target consists of Clifford + T gate,
# i.e. {cx, h, s, t}. It does not contain unitary rotations
# (e.g. rz).
#
# This is a universal set of gates, but it requires synthesis
# (e.g. with SK algorithm), for transpilation to succeed. This
# synthesis (that transforms rotations into discrete gates of our
# basis target) is done during the CustomUnitarySynthesis pass.
#
# Unfortunately, qiskit BasisTranslator pass does not handle
# synthesis. It tries to match existing gates in the circuit to
# gates in the target basis, but only for exact equivalence (doing
# a graph search).
#
# Therefore, as a workaround, when the target basis does not
# support unitary rotations natively, we
# - add the 'rz' to the BasisTranslator target basis set, to
# trick the algorithm into thinking that rotations are
# supported by the target.
# - add a 2nd CustomUnitarySynthesis pass after that, to get
# rid of any 'rz' pass generated in the pass above.
#
# Example :
# > Input circuit ('cs')
# q_0: ──■──
# ┌─┴─┐
# q_1: ┤ S ├
# └───┘
#
# > output of BasisTranslator, with 'rz' in target :
# ┌─────────┐
# q_0: ┤ Rz(π/4) ├──■────────────────■─────────────
# └─────────┘┌─┴─┐┌──────────┐┌─┴─┐┌─────────┐
# q_1: ───────────┤ X ├┤ Rz(-π/4) ├┤ X ├┤ Rz(π/4) ├
# └───┘└──────────┘└───┘└─────────┘
#
# > output of BasisTranslator, without 'rz' in target :
# TranspilerError: "Unable to translate the operations..."
#
# > output of the 2nd CustomUnitarySynthesis :
# ┌───┐
# q_0: ┤ T ├──■───────────■───────
# └───┘┌─┴─┐┌─────┐┌─┴─┐┌───┐
# q_1: ─────┤ X ├┤ Tdg ├┤ X ├┤ T ├
# └───┘└─────┘└───┘└───┘
need_synthesis = True

# Ideally, we would use the `target_basis` argument of
# BasisTranslator to specify we want to add the "rz" gate.
# Unfortunately, in the .run method, this argument is ignored in
# favor of the target.target_basis when the target is defined.
# Therefore, we create a copy of the current target and modify
# its set of supported instructions just for this pass.
basis_translator_target = deepcopy(pass_manager_config.target)
basis_translator_target.add_instruction(RZGate(0))

custom_pm.append(_get_unitary_synthesis(pass_manager_config))
custom_pm.append(_get_high_level_synthesis(pass_manager_config))
custom_pm.append(
BasisTranslator(
SessionEquivalenceLibrary,
pass_manager_config.basis_gates,
basis_translator_target,
),
hls_config=pass_manager_config.hls_config,
)
# pylint: disable=protected-access
for task in default_pm._tasks:
for subtask in task:
# Substitue the default UnitarySynthesis with our
# custom implementation.
if isinstance(subtask, UnitarySynthesis):
custom_pm.append(
# pylint: disable=line-too-long
CustomUnitarySynthesis(
basis_gates=pass_manager_config.basis_gates,
approximation_degree=pass_manager_config.approximation_degree, # noqa: E501
coupling_map=pass_manager_config.coupling_map,
backend_props=pass_manager_config.backend_properties, # noqa: E501
method=pass_manager_config.unitary_synthesis_method, # noqa: E501
plugin_config=pass_manager_config.unitary_synthesis_plugin_config, # noqa: E501
target=pass_manager_config.target,
)
)
continue
custom_pm.append(subtask)
if need_synthesis:
custom_pm.append(_get_unitary_synthesis(pass_manager_config))

custom_pm += generate_embed_passmanager(
pass_manager_config.coupling_map
Expand Down
73 changes: 72 additions & 1 deletion tests/local/test_backend.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import sys
from typing import List

import numpy as np
import pytest
from qiskit import QuantumCircuit, transpile
from qiskit.circuit.library import Initialize
from qiskit.circuit import Instruction
from qiskit.circuit.library import Initialize, get_standard_gate_name_mapping

from qiskit_alice_bob_provider import AliceBobLocalProvider
from qiskit_alice_bob_provider.local.backend import ProcessorSimulator
from qiskit_alice_bob_provider.processor.logical_cat import LogicalCatProcessor
from qiskit_alice_bob_provider.processor.physical_cat import (
Expand Down Expand Up @@ -116,6 +119,74 @@ def test_synthesize_cz() -> None:
assert len(transpiled.get_instructions('h')) == 2


def test_all_gates():
"""Test transpilation for all basis gates"""
provider = AliceBobLocalProvider()
backend = provider.get_backend('EMU:40Q:LOGICAL_TARGET')

qiskit_gates = get_standard_gate_name_mapping()
skip_gates = [
# Trying to access this gates from the circuit attributes returns an
# error "'QuantumCircuit' object has no attribute '...'"
'c3sx',
'cu1',
'cu3',
'xx_minus_yy',
'xx_plus_yy',
'u1',
'u2',
'u3',
# This one is not a gate, just a float.
'global_phase',
]
if sys.platform == 'darwin':
# For some reason, on macOS we have numerical instabilities with the
# Solovay Kitaev synthesis, with specific angles.
# For instance, a simple 1Q circuit with a RZ(5pi/4) gate fails to
# transpile with our logical backends, and typically raises :
# ValueError('Input matrix is not orthogonal.')
# As a result, the synthesis currently fails for the gates below (this
# needs to be fixed).
skip_gates += [
'cry',
'rccx',
'rcccx',
]

def create_circuit_with_gate(instruction: Instruction):
if instruction.params:
# Most parameters are angles -> use pi/5.
# Except for the delay instruction, which expects an integer for
# param 't' -> use 10.
params = [
10 if p.name == 't' else np.pi / 5 for p in instruction.params
]
else:
params = []
circuit = QuantumCircuit(
instruction.num_qubits, instruction.num_clbits
)
args = (
params
+ list(range(instruction.num_qubits))
+ list(range(instruction.num_clbits))
)
# apply the gate
getattr(circuit, instruction.name)(*args)
return circuit

errors = []
for name, i in qiskit_gates.items():
if name in skip_gates:
continue
try:
circ = create_circuit_with_gate(i)
_ = transpile(circ, backend=backend)
except Exception as e: # pylint: disable=broad-exception-caught
errors.append((name, e))
assert not errors


def test_do_nothing_on_mx() -> None:
backend = ProcessorSimulator(
SimpleAllToAllProcessor(), translation_stage_plugin='sk_synthesis'
Expand Down

0 comments on commit f022b63

Please sign in to comment.