メインコンテンツまでスキップ

回路トランスパイラ

様々な理由から、量子回路を意味的に等価な別の量子回路に変換したい場合があります。

例えば、特定のバックエンドが特定のゲートセットしかサポートしていない場合、ゲートセットを変換する必要があります。また、量子ビットが特定のトポロジーで実装されている場合、回路を実行可能にするために変換が必要になることがあります。意味的に等価な冗長表現をより簡潔な表現に変換することで、回路の実行時間、エラー率、量子ビットの数を削減できる可能性があります。

これらの動機は大きく2つに分類できます。

  1. バックエンド(ハードウェア)の適応
  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種類紹介します:GateKindDecomposerGateDecomposerです。

GateDecomposer

上記の通り、GateDecomposerは、ゲートが特定の条件を満たす場合にゲートを分解するトランスパイラです。QURI Partsでは、以下の2つの具体的な実装が提供されています。

  • SingleQubitUnitaryMatrix2RYRZTranspiler
  • TwoQubitUnitaryMatrixKAKTranspiler

名前が示すように、これらのゲート分解器は、ゲートがそれぞれ11量子ビットまたは22量子ビットに作用するユニタリ行列ゲートである場合にゲートを分解します。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ゲートに対してのみ効果を発揮するため、22量子ビットのユニタリ行列ゲートはトランスパイル中にそのまま残されることがわかります。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ページを参照することをお勧めします。

例として、アダマールゲートをRzR_zX\sqrt{X}ゲートの列にトランスパイルする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: Toffoli\text{Toffoli} \rightarrow (H\text{H}, T\text{T}, T\text{T}^{\dagger}, CNOT\text{CNOT})
  • トランスパイラ 2: H\text{H} \rightarrow (RZR_Z, X\sqrt{\text{X}})
  • トランスパイラ 3: T\text{T} \rightarrow RZR_Z
  • トランスパイラ 4: T\text{T}^{\dagger} \rightarrow RZR_Z

これらのトランスパイルは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

ここでは、SequentialTranspilerParallelDecomposerをネストして新しい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.transpilePauliDecomposeTranspilerPauli{X, Y, Z}
quri_parts.circuit.transpilePauliRotationDecomposeTranspilerPauliRotation{H, RX, RZ, CNOT}
quri_parts.circuit.transpileSingleQubitUnitaryMatrix2RYRZTranspilerUnitaryMatrix{RY, RZ}
quri_parts.circuit.transpileTwoQubitUnitaryMatrixKAKTranspilerUnitaryMatrix{H, S, RX, RY, RZ, CNOT}

Gate set conversion

モジュールトランスパイラターゲットゲート説明
quri_parts.circuit.transpileRZSetTranspiler{X, SqrtX, RZ, CNOT}Qiskit を介して IBM Quantum などの超伝導タイプの機器で使用されるゲートセット
quri_parts.circuit.transpileRotationSetTranspiler{RX, RY, RZ, CNOT}イオントラップタイプの中間ゲートセット
quri_parts.circuit.transpileCliffordRZSetTranspiler{H, X, Y, Z, S, SqrtX, SqrtXdag, SqrtY, SqrtYdag, Sdag, RZ, CZ, CNOT}クリフォード + RZ ゲートセット
quri_parts.quantinuum.circuit.transpileQuantinuumSetTranspiler{U1q, RZ, ZZ, RZZ}Quantinuum H1、H2の実機用ゲートセット
quri_parts.circuit.transpileIonQSetTranspiler{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.transpileCliffordApproximationTranspilerApproximate非クリフォード・ゲートを近似クリフォード・ゲート・シーケンスに置き換える
quri_parts.circuit.transpileIdentityInsertionTranspilerEquivalentゲートが作用していない量子ビットにIdentityゲートを追加する
quri_parts.circuit.transpileIdentityEliminationTranspilerEquivalent全ての恒等ゲートを削除する
quri_parts.qiskit.circuit.transpileQiskitTranspilerEquivalent (Numerical error)Qiskitの機能を使用して、バックエンドの適合、ゲートセットの変換、回路の簡略化を実行する
quri_parts.tket.circuit.transpileTketTranspilerEquivalent (Numerical error)Tketの機能を使用して、バックエンドの適合、ゲートセットの変換、回路の簡素化を実行する

回転ゲートをパラメータとする最も基本的な最適化パスは以下の通りです。

モジュールトランスパイラタイプ説明
quri_parts.circuit.transpileFuseRotationTranspilerEquivalent (Numerical error)同じ種類の連続した回転ゲートを結合する
quri_parts.circuit.transpileNormalizeRotationTranspilerEquivalent (Numerical error)回転ゲートの回転角度を指定された範囲に正規化する
quri_parts.circuit.transpileRX2NamedTranspilerEquivalent (Numerical error)RXゲートがパラメータを持たない名前付きゲートと等しい場合、RXゲートを変換する
quri_parts.circuit.transpileRY2NamedTranspilerEquivalent (Numerical error)RYゲートがパラメータを持たない名前付きゲートと等しい場合、RYゲートを変換する
quri_parts.circuit.transpileRZ2NamedTranspilerEquivalent (Numerical error)RZゲートがパラメータなしの名前付きゲートと等しい場合、RZゲートを変換する

独自のトランスパイラを定義

上で説明したように、SequentialTranspilerParallellDecomposerで連結されたトランスパイラーはそれ自体がCircuitTranspilerであり、他のトランスパイラーと同様に使用することができます。また、CircuitTranspilerのインターフェイスを持つ呼び出し可能なオブジェクトであれば、それがユーザー定義関数であれクラスであれ、トランスパイラとして動作することができます。

def transpiler(circuit: NonParametricQuantumCircuit) -> NonParametricQuantumCircuit:
...

オリジナルのトランスパイラをクラスとして定義する場合、CircuitTranspilerProtocolCircuitTranspilerのプロパティを満たし、継承可能な抽象基底クラスとして定義されます。

from quri_parts.circuit.transpile import CircuitTranspilerProtocol

class Transpiler(CircuitTranspilerProtocol):
def __call__(self, circuit: NonParametricQuantumCircuit) -> NonParametricQuantumCircuit:
...

GateDecomposerGateKindDecomposerは、回路内の特定の種類のゲートをあるゲートの列に変換するトランスパイラ(例えば、ゲートセットを変換するトランスパイラ)に使用できます。GateDecomposer を使用すると、ターゲットゲートの条件と、ターゲットゲートからゲートの列への変換のみを記述して、新しいトランスパイラを作成できます。GateKindDecomposerGateDecomposerと似ていますが、ターゲットゲート条件としてゲート名を必要とします。

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)]