回路トランスパイラ
様々な理由から、量子回路を意味的に等価な別の量子回路に変換したい場合があります。
例えば、特定のバックエンドが特定のゲートセットしかサポートしていない場合、ゲートセットを変換する必要があります。また、量子ビットが特定のトポロジーで実装されている場合、回路を実行可能にするために変換が必要になることがあります。意味的に等価な冗長表現をより簡潔な表現に変換することで、回路の実行時間、エラー率、量子ビットの数を削減できる可能性があります。
これらの動機は大きく2つに分類できます。
- バックエンド(ハードウェア)の適応
- 回路最適化
QURI Partsでは、このような目的のために様々な回路トランスパイラを用意しています。また、既存のトランスパイラを組み合わせて新しいトランスパイラを用意したり、ゼロからトランスパイラを実装することもできます。このチュートリアルでは、QURI Partsで回路トランスパイラを扱う方法を紹介します。
前提条件
このチュートリアルで使用するQURI Partsモジュール: quri-parts-circuit
, quri-parts-core
以下のようにインストールできます:
!pip install "quri-parts"
概要
例として、まず次のような回路を作り、RZセット・トランスパイラを適用してみましょう。RZセット・トランスパイラとは、回路をX、SqrtX、CNOT、RZゲートだけ を含むものに変換するトランスパイラです。これは次のように行います。
from quri_parts.circuit import QuantumCircuit
from quri_parts.circuit.transpile import RZSetTranspiler
from quri_parts.circuit.utils.circuit_drawer import draw_circuit
circuit = QuantumCircuit(3)
circuit.add_H_gate(2)
circuit.add_X_gate(0)
circuit.add_CNOT_gate(2, 1)
circuit.add_Z_gate(2)
print("original:")
draw_circuit(circuit)
transpiler = RZSetTranspiler()
transpiled_circuit = transpiler(circuit)
print("\ntranspiled:")
draw_circuit(transpiled_circuit)
#output
original:
___
| X |
--|1 |-----------------
|___|
___
|CX |
----------|2 |---------
|___|
___ | ___
| H | | | Z |
--|0 |-----●-----|3 |-
|___| |___|
transpiled:
___
| X |
--|3 |---------------------------------
|___|
___
|CX |
--------------------------|4 |---------
|___|
___ ___ ___ | ___
|RZ | |sqX| |RZ | | |RZ |
--|0 |---|1 |---|2 |-----●-----|5 |-
|___| |___| |___| |___|
ここでのRZSetTranspiler
は、複数の単純なトランスパイラから構成されるトランスパイラです。このチュートリアルの目的は、トランスパイラのインターフェイスを紹介し、カスタマイズされたトランスパイラを構築する方法を説明することです。
トランスパイラインターフェース
QURI PartsのトランスパイルはすべてCircuitTranspiler
であり、NonParametricQuantumCircuit
を別のNonParametricQuantumCircuit
に変換することができます。
from typing import Callable
from typing_extensions import TypeAlias
from quri_parts.circuit import NonParametricQuantumCircuit
CircuitTranspiler: TypeAlias = Callable[
[NonParametricQuantumCircuit], NonParametricQuantumCircuit
]
トランスパイラには、さまざまな種類のトランスパイルを実行するための複数のタイプがあります:
GateDecomposer
: ゲートがゲート分解器の設定した特定の条件を満たす場合に、ゲートを分解するトランスパイラ。GateKindDecomposer
: 特定のタイプのゲートに対してゲートを分解するトランスパイラ。つまり、ゲート名とターゲット・ゲート名が一致するかどうかをチェックするGateDecomposer
です。ParallelDecomposer
: ターゲット・ゲートが互いに排他的な複数のGateKindDecomposer
を合成するトランスパイラ。回路を1回繰り返し、ParallelDecomposer
で設定されたすべてのタイプのゲートを分解します。SequentialTranspiler
: 複数のトランスパイラを合成し、順番にトランスパイルを行うトランスパイラ。
Gate kind decomposerとgate decomposer
まず、ゲートを変換する基本的なトランスパイラを2種類紹介します:GateKindDecomposer
とGateDecomposer
です。
GateDecomposer
上記の通り、GateDecomposer
は、ゲートが特定の条件を満たす場合にゲートを分解するトランスパイラです。QURI Partsでは、以下の2つの具体的な実装が提供されています。
SingleQubitUnitaryMatrix2RYRZTranspiler
TwoQubitUnitaryMatrixKAKTranspiler
名前が示すように、これらのゲート分解器は、ゲートがそれぞれ量子ビットまたは量子ビットに作用するユニタリ行列ゲートである場合にゲートを分解します。SingleQubitUnitaryMatrix2RYRZTranspiler
の例を見てみましょう。
from quri_parts.circuit.transpile import SingleQubitUnitaryMatrix2RYRZTranspiler
from scipy.stats import unitary_group
single_qubit_matrix = unitary_group.rvs(2)
double_qubit_matrix = unitary_group.rvs(4)
circuit = QuantumCircuit(2)
circuit.add_UnitaryMatrix_gate([0], single_qubit_matrix)
circuit.add_UnitaryMatrix_gate([0, 1], double_qubit_matrix)
print("original circuit:")
draw_circuit(circuit)
transpiler = SingleQubitUnitaryMatrix2RYRZTranspiler()
transpiled_circuit = transpiler(circuit)
print("")
print("transpiled circuit:")
draw_circuit(transpiled_circuit)
#output
original circuit:
___ ___
|Mat| |Mat|
--|0 |---|1 |-
|___| | |
| |
| |
----------| |-
|___|
transpiled circuit:
___ ___ ___ ___
|RZ | |RY | |RZ | |Mat|
--|0 |---|1 |---|2 |---|3 |-
|___| |___| |___| | |
| |
| |
--------------------------| |-
|___|
上の例から、両方のゲートがUnitaryMatrix
型である一方で、GateDecomposer
SingleQubitUnitaryMatrix2RYRZTranspiler
は、 単一量子ビットに作用するUnitaryMatrix
ゲートに対してのみ効果を発揮するため、量子ビットのユニタリ行列ゲートはトランスパイル中にそのまま残されることがわかります。GateDecomposer
は、ゲートが変換されるかどうかをチェックするis_target_gate
を提供します:
print("Single qubit unitary gate should be converted:", transpiler.is_target_gate(circuit.gates[0]))
print("Double qubit unitary gate should be converted:", transpiler.is_target_gate(circuit.gates[1]))
#output
Single qubit unitary gate should be converted: True
Double qubit unitary gate should be converted: False
GateKindDecomposer
基本的なゲート・トランスパイラのもう1つのタイプは、GateKindDecomposer
です。これはGateDecomposer
のサブタイプで、ゲートの名前がトランスパイルしたいゲートの名前と一致するかどうかをチェックします。QuantumGate
の他の属性のチェックは行いません。QURI Partsでは、quri_parts.circuit.transpile.gate_kind_decomposer
モジュールで膨大な量のデコンポーザを提供しています。GateKindDecomposer
の一覧については、APIページを参照することをお勧めします。
例として、アダマールゲートをとゲートの列にトランスパイルするH2RZSqrtXTranspiler
を紹介します。
from quri_parts.circuit.transpile import H2RZSqrtXTranspiler
circuit = QuantumCircuit(2)
circuit.add_H_gate(0)
circuit.add_X_gate(1)
print("original circuit:")
draw_circuit(circuit)
transpiler = H2RZSqrtXTranspiler()
transpiled_circuit = transpiler(circuit)
print("")
print("transpiled circuit:")
draw_circuit(transpiled_circuit)
#output
original circuit:
___
| H |
--|0 |-
|___|
___
| X |
--|1 |-
|___|
transpiled circuit:
___ ___ ___
|RZ | |sqX| |RZ |
--|0 |---|1 |---|2 |-
|___| |___| |___|
___
| X |
--|3 |-----------------
|___|
逐次的なトランスパイラ
複数の変換を並べるだけで、複数のトランスパイラを適用できます。ここでは、1つのトフォリゲートを 使った回路を例にしています。ここでは、以下の一連のトランスパイルを行います。
- トランスパイラ 1: (, , , )
- トランスパイラ 2: (, )
- トランスパイラ 3:
- トランスパイラ 4:
これらのトランスパイルはQURI Partsがすでに提供しています。使い方を説明します:
from quri_parts.circuit.transpile import (
TOFFOLI2HTTdagCNOTTranspiler,
H2RZSqrtXTranspiler,
T2RZTranspiler,
Tdag2RZTranspiler,
)
circuit = QuantumCircuit(3)
circuit.add_TOFFOLI_gate(0, 1, 2)
print("original:")
draw_circuit(circuit, line_length=120)
circuit = TOFFOLI2HTTdagCNOTTranspiler()(circuit)
circuit = H2RZSqrtXTranspiler()(circuit)
circuit = T2RZTranspiler()(circuit)
circuit = Tdag2RZTranspiler()(circuit)
print("")
print("Sequential transpiled:")
draw_circuit(circuit, line_length=120)
#output
original:
----●---
|
|
|
----●---
|
_|_
|TOF|
--|0 |-
|___|
Sequential transpiled:
___
|RZ |
--------------------------------------------●-------------------------------●-------●-----|16 |-----●-----------
| | | |___| |
| ___ | _|_ ___ _|_
| |RZ | | |CX | |RZ | |CX |
----------------------------●---------------|---------------●-----|10 |-----|-----|15 |---|17 |---|18 |---------
| | | |___| | |___| |___| |___|
___ ___ ___ _|_ ___ _|_ ___ _|_ ___ _|_ ___ ___ ___ ___
|RZ | |sqX| |RZ | |CX | |RZ | |CX | |RZ | |CX | |RZ | |CX | |RZ | |RZ | |sqX| |RZ |
--|0 |---|1 |---|2 |---|3 |---|4 |---|5 |---|6 |---|7 |---|8 |---|9 |---|11 |---|12 |---|13 |---|14 |-
|___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___|
また、CircuitTranspiler
のインスタンスを生成時に渡すことで、SequentialTranspiler
を使用して、より簡単に記述することもできます。
from quri_parts.circuit.transpile import SequentialTranspiler
circuit = QuantumCircuit(3)
circuit.add_TOFFOLI_gate(0, 1, 2)
transpiler = SequentialTranspiler([
TOFFOLI2HTTdagCNOTTranspiler(),
H2RZSqrtXTranspiler(),
T2RZTranspiler(),
Tdag2RZTranspiler(),
])
circuit = transpiler(circuit)
draw_circuit(circuit, line_length=120)
#output
___
|RZ |
--------------------------------------------●-------------------------------●-------●-----|16 |-----●-----------
| | | |___| |
| ___ | _|_ ___ _|_
| |RZ | | |CX | |RZ | |CX |
----------------------------●---------------|---------------●-----|10 |-----|-----|15 |---|17 |---|18 |---------
| | | |___| | |___| |___| |___|
___ ___ ___ _|_ ___ _|_ ___ _|_ ___ _|_ ___ ___ ___ ___
|RZ | |sqX| |RZ | |CX | |RZ | |CX | |RZ | |CX | |RZ | |CX | |RZ | |RZ | |sqX| |RZ |
--|0 |---|1 |---|2 |---|3 |---|4 |---|5 |---|6 |---|7 |---|8 |---|9 |---|11 |---|12 |---|13 |---|14 |-
|___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___|
並行デコンポーザ
複数のタイプのゲートを一度にトランスパイルしたい場合がよくあります。逐次的なトランスパイラでもこの作業は可能ですが、複数の GateKindDecomposer
を1つのParallelDecomposer
にまとめた方が効率的です。GateKindDecomposer
は、ゲートの種類に基づいてゲートをトランスパイルするトランスパイラであることを再度強調しておきます。したがって、ParallelDecomposer
を構成するゲート変換は、互いに排他的なゲートに作用する関係になっているべきです。
トフォリ・ゲートをより小さなゲートにトランスパイルする前回の例を再確認します。前回の例では、4つのトランスパイラからなる逐次トランスパイラを使用しました。したがって、回路は4回反復されたことになります。しかし、トランスパイラ2、3、4を見ると、それらが作用するゲートは別個のものです。また、どの出力ゲート・セットも、検討中の他のトランスパイラによってさらにトランスパイルされることはありません。つまり、最後の3つのトランスパイラーを1つのParallelDecomposer
に統合することがより望ましいです。こうすることで、回路を2回繰り返すだけでトランスパイルを行うことができます。より簡単に説明すると、手順は次のようになります:
- ステップ 1:
TOFFOLI2HTTdagCNOTTranspiler
- ステップ 2: 以下を含む
ParallelDecomposer
:H2RZSqrtXTranspiler
T2RZTranspiler
Tdag2RZTranspiler
ここでは、SequentialTranspiler
とParallelDecomposer
をネストして新しいCircuitTranspiler
を作る方法を示します。
from quri_parts.circuit.transpile import ParallelDecomposer, SequentialTranspiler
circuit = QuantumCircuit(3)
circuit.add_TOFFOLI_gate(0, 1, 2)
print("original circuit:")
draw_circuit(circuit)
transpiler = SequentialTranspiler([
TOFFOLI2HTTdagCNOTTranspiler(),
ParallelDecomposer([
H2RZSqrtXTranspiler(),
T2RZTranspiler(),
Tdag2RZTranspiler(),
]),
])
circuit = transpiler(circuit)
print("\n")
print("transpiled circuit:")
draw_circuit(circuit, line_length=200)
#output
original circuit:
----●---
|
|
|
----●---
|
_|_
|TOF|
--|0 |-
|___|
transpiled circuit:
___
|RZ |
--------------------------------------------●-------------------------------●-------●-----|16 |-----●-----------
| | | |___| |
| ___ | _|_ ___ _|_
| |RZ | | |CX | |RZ | |CX |
----------------------------●---------------|---------------●-----|10 |-----|-----|15 |---|17 |---|18 |---------
| | | |___| | |___| |___| |___|
___ ___ ___ _|_ ___ _|_ ___ _|_ ___ _|_ ___ ___ ___ ___
|RZ | |sqX| |RZ | |CX | |RZ | |CX | |RZ | |CX | |RZ | |CX | |RZ | |RZ | |sqX| |RZ |
--|0 |---|1 |---|2 |---|3 |---|4 |---|5 |---|6 |---|7 |---|8 |---|9 |---|11 |---|12 |---|13 |---|14 |-
|___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___|
バックエンド適応のためのトランスパイラ
ゲートセット変換
回路を各バックエンドの実機で実行する場合、回路のゲートセットは多くの場合、数個のユニバーサルゲートに限定されます。また、QURI Partsにはマルチパウリゲートのような高レベルのゲート表現がありますが、ほとんどのバックエンドではサポートされていません。そのため、バックエンドで回路を実行する前に、回路をトランスパイルしてゲートセットを変換する必要があります。
SamplingBackend
の作成時や回路の変換時には、各バックエンドのデフォルトのトランスパイラが自動的に適用されますが、デフォルトのトランスパイラの代わりにユーザー指定のトランスパイラを使用することもできます。
Complex gate decomposition
モジュール | トランスパイラ | ターゲットゲート | デコンポーズドゲートセット |
---|---|---|---|
quri_parts.circuit.transpile | PauliDecomposeTranspiler | Pauli | {X, Y, Z} |
quri_parts.circuit.transpile | PauliRotationDecomposeTranspiler | PauliRotation | {H, RX, RZ, CNOT} |
quri_parts.circuit.transpile | SingleQubitUnitaryMatrix2RYRZTranspiler | UnitaryMatrix | {RY, RZ} |
quri_parts.circuit.transpile | TwoQubitUnitaryMatrixKAKTranspiler | UnitaryMatrix | {H, S, RX, RY, RZ, CNOT} |
Gate set conversion
モジュール | トランスパイラ | ターゲットゲート | 説明 |
---|---|---|---|
quri_parts.circuit.transpile | RZSetTranspiler | {X, SqrtX, RZ, CNOT} | Qiskit を介して IBM Quantum などの超伝導タイプの機器で使用されるゲートセット |
quri_parts.circuit.transpile | RotationSetTranspiler | {RX, RY, RZ, CNOT} | イオントラップタイプの中間ゲートセット |
quri_parts.circuit.transpile | CliffordRZSetTranspiler | {H, X, Y, Z, S, SqrtX, SqrtXdag, SqrtY, SqrtYdag, Sdag, RZ, CZ, CNOT} | クリフォード + RZ ゲートセット |
quri_parts.quantinuum.circuit.transpile | QuantinuumSetTranspiler | {U1q, RZ, ZZ, RZZ} | Quantinuum H1、H2の実機用ゲートセット |
quri_parts.circuit.transpile | IonQSetTranspiler | {GPi, GPi2, MS} | IonQの実機に合わせたゲートセット |
量子ビットマッピング
NISQ時代の実デバイスは、量子ビットのトポロジーによっても制約を受けます。ほとんどの場合、これらの制約はバックエンドが自動的に回路を変換することで満たされますが、バックエンドによる自動変換をオフにし、明示的に量子ビットのマッピングを与えたい場合もあります。
このような量子ビットのマッピングは、SamplingBackends
を作成する際に辞書で指定することができます(sampling backendsのチュートリアルの量子ビットマッピングを参照してください)が、与えられた回路に対して量子ビットのマッピングを行うQubitRemappingTranspiler
を作成することもできます。
from quri_parts.circuit import H, X, CNOT
from quri_parts.circuit.transpile import QubitRemappingTranspiler
circuit = QuantumCircuit(3)
circuit.extend([H(0), X(1), CNOT(1, 2)])
print("original:")
draw_circuit(circuit)
circuit = QubitRemappingTranspiler({0: 2, 1: 0, 2: 1})(circuit)
print("\ntranspiled:")
draw_circuit(circuit)
#outupt
original:
___
| H |
--|0 |---------
|___|
___
| X |
--|1 |-----●---
|___| |
_|_
|CX |
----------|2 |-
|___|
transpiled:
___
| X |
--|1 |-----●---
|___| |
_|_
|CX |
----------|2 |-
|___|
___
| H |
--|0 |---------
|___|
回路最適化のためのトランスパイラ
量子回路は、等価な作用を持つより簡潔な回路に変換することができます。実際のハードウェアでは、等価回路を特定の表現にすることで、エラーを減らしたり、実行時間を短縮したりすることができます。例えば、NISQ時代には2量子ビットゲートの数が誤り率に大きく影響することが多く、FTQC時代にはTゲート数が回路の実行時間に影響することがあります。こうした様々な基準に基づいて回路を最適化することも、トランスパイラに期待される役割の一つです。
QURI Partsでは、現在多くの最適化パスが非公開となっていますが、一部は利用可能であり、将来的にはさらに多くの最適化パスが公開される予定です。
モジュール | トランスパイラ | タイプ | 説明 |
---|---|---|---|
quri_parts.circuit.transpile | CliffordApproximationTranspiler | Approximate | 非クリフォード・ゲートを近似クリフォード・ゲート・シーケンスに置き換える |
quri_parts.circuit.transpile | IdentityInsertionTranspiler | Equivalent | ゲートが作用していない量子ビットにIdentityゲートを追加する |
quri_parts.circuit.transpile | IdentityEliminationTranspiler | Equivalent | 全ての恒等ゲートを削除する |
quri_parts.qiskit.circuit.transpile | QiskitTranspiler | Equivalent (Numerical error) | Qiskitの機能を使用して、バックエンドの適合、ゲートセットの変換、回路の簡略化を実行する |
quri_parts.tket.circuit.transpile | TketTranspiler | Equivalent (Numerical error) | Tketの機能を使用して、バックエンドの適合、ゲートセットの変換、回路の簡素化を実行する |
回転ゲートをパラメータとする最も基本的な最適化パスは以下の通りです。
モジュール | トランスパイラ | タイプ | 説明 |
---|---|---|---|
quri_parts.circuit.transpile | FuseRotationTranspiler | Equivalent (Numerical error) | 同じ種類の連続した回転ゲートを結合する |
quri_parts.circuit.transpile | NormalizeRotationTranspiler | Equivalent (Numerical error) | 回転ゲートの回転角度を指定された範囲に正規化する |
quri_parts.circuit.transpile | RX2NamedTranspiler | Equivalent (Numerical error) | RXゲートがパラメータを持たない名前付きゲートと等しい場合、RXゲートを変換する |
quri_parts.circuit.transpile | RY2NamedTranspiler | Equivalent (Numerical error) | RYゲートがパラメータを持たない名前付きゲートと等しい場合、RYゲートを変換する |
quri_parts.circuit.transpile | RZ2NamedTranspiler | Equivalent (Numerical error) | RZゲートがパラメータなしの名前付きゲートと等しい場合、RZゲートを変換する |
独自のトランスパイラを定義
上で説明したように、SequentialTranspiler
やParallellDecomposer
で連結されたトランスパイラーはそれ自体がCircuitTranspiler
であり、他のトランスパイラーと同様に使用することができます。また、CircuitTranspiler
のインターフェイスを持つ呼び出し可能なオブジェクトであれば、それがユーザー定義関数であれクラスであれ、トランスパイラとして動作することができます。
def transpiler(circuit: NonParametricQuantumCircuit) -> NonParametricQuantumCircuit:
...
オリジナルのトランスパイラをクラスとして定義する場合、CircuitTranspilerProtocol
はCircuitTranspiler
のプロパティを満たし、継承可能な抽象基底クラスとして定義されます。
from quri_parts.circuit.transpile import CircuitTranspilerProtocol
class Transpiler(CircuitTranspilerProtocol):
def __call__(self, circuit: NonParametricQuantumCircuit) -> NonParametricQuantumCircuit:
...
GateDecomposer
とGateKindDecomposer
は、回路内の特定の種類のゲートをあるゲートの列に変換するトランスパイラ(例えば、ゲートセットを変換するトランスパイラ)に使用できます。GateDecomposer
を使用すると、ターゲットゲートの条件と、ターゲットゲートからゲートの列への変換のみを記述して、新しいトランスパイラを作成できます。GateKindDecomposer
はGateDecomposer
と似ていますが、ターゲットゲート条件としてゲート名を必要とします。
from collections.abc import Sequence
from quri_parts.circuit import QuantumGate, gate_names
from quri_parts.circuit.transpile import GateDecomposer, GateKindDecomposer
class S0toTTranspiler(GateDecomposer):
def is_target_gate(self, gate: QuantumGate) -> bool:
return gate.target_indices[0] == 0 and gate.name == gate_names.S
def decompose(self, gate: QuantumGate) -> Sequence[QuantumGate]:
target = gate.target_indices[0]
return [gate.T(target), gate.T(target)]
class AnyStoTTranspiler(GateKindDecomposer):
def target_gate_names(self) -> Sequence[str]:
return [gate_names.S]
def decompose(self, gate: QuantumGate) -> Sequence[QuantumGate]:
target = gate.target_indices[0]
return [gate.T(target), gate.T(target)]