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

Fix/cs gate transpilation #58

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
VENV=venv
PIP?=pip

MODULES=qiskit_alice_bob_provider tests
BUILD_DIR=dist
Expand All @@ -24,7 +25,7 @@ $(VENV):
$(PYTHON) -m venv $(VENV)

install: $(VENV)
. $(ACTIVATE) && pip install -e .[dev]
. $(ACTIVATE) && $(PIP) install -e .[dev]

clear:
rm -rf $(VENV)
Expand Down
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
2 changes: 2 additions & 0 deletions qiskit_alice_bob_provider/processor/logical_cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
't',
'tdg',
'h',
's',
'sdg',
]


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
Loading