はじめに
量子コンピュータカレンダーも22日目となり、ディープな話が様々繰り広げられていますが、今日は軽めの内容とさせて頂きたいと思いますw(浅くてスミマセンw)
なお、量子コンピュータカレンダー参加も3年目となります!
過去(2020/2021)のカレンダにて投稿した記事については、本稿末尾にてリンクさせて頂くので、そちらもご笑覧いただければ幸いです。
やること
Quantinuumが提供するpytket(tket)ですが、プラットフォームに依存しないSDKを提供し、
合わせてターゲット量子マシンへのコンパイラ/トランスパイラの機能も提供しています。
このコンパイラ/トランスパイラの機能に興味を持ったのでちょっと試してみよう。
というのが本稿の趣旨です。
そういったコンパイル/トランスパイルならpytketを使わずともQiskitでも出来るじゃん。。
とかあるかもしれませんが、
純粋にpytketでどんな変換が出来るんだろう。を試してみるというスタンスなので、
あまり細かいことは気にせず進めてみたいと思います。
では、始めます
トランスパイラの機能は、pytket.passes
パッケージで提供されているのですが、、
若干慣れが必要なので、まずは一番簡単な、Toffoliゲートの分解から始めたいと思います。
1)Toffoli(CCX)を分解
Toffoliゲート($CCX$ゲート)ですが、複数量子系の完全系である$H,T,T^{\dagger},CNOT$を用いてこんな等価回路に分解可能であると知られています。1
この等価回路をpytket.passes
パッケージを用いて作成してみたいと思います。2
まずは、pipでモジュールを導入します。
!pip install pytket
次に、Toffoliを実装して、可視化します。
from pytket.circuit import Circuit
from pytket.circuit.display import render_circuit_jupyter
# 3量子ビットを準備
circ_toff = Circuit(3)
# Toffoli(CCX)ゲートをかける
circ_toff.CCX(0,1,2)
# 可視化
render_circuit_jupyter(circ_toff)
なお、量子回路の表示スタイルは、このボタンで変更できます。
では、回路を変換していきましょう。
from pytket.predicates import CompilationUnit
from pytket.passes import DecomposeMultiQubitsCX
# ①CompilationUnitで回路を包む
unit_toff = CompilationUnit(circ_toff)
# ②DecomposeMultiQubitsCXを使って回路を展開
DecomposeMultiQubitsCX().apply(unit_toff)
# ③展開後の回路を取り出し
transformed_cric = unit_toff.circuit
# 可視化
render_circuit_jupyter(transformed_cric)
といった具合に、無事に、Toffoliを分解することができました。
ポイントは、
- ① 量子回路をpytketのSDKにて実装したら、その回路を
CompilationUnit
でラップし3 - ② 所望の変換(上記では
DecomposeMultiQubitsCX
)をapply
する - ③
apply
後のCompilationUnit
からcircuit
を取り出すと変換後の回路が得られる
といったところでしょうか。
2)冗長な回路の除去
下記のような回路を考えます。
$X$ゲートの後に、$X$ゲートがかかっているので、ブロッホ球で考えるとX軸を中心に180度回転し、もう一度180度回転するので、360度回転となり、結局何もしていない回路となります。
もう一つ、複数量子系で考えて見ます。
下記はどうでしょうか?これも、SWAPしてSWAPしているので、結局元の状態に戻ります。
よって、下記も無駄のある冗長な回路と見なすことができます。
こういった無駄のある冗長な回路の排除を考えていきます。
では、始めます。
まずは、冗長な回路を作成してみましょう。
from pytket.circuit import Circuit
from pytket.circuit.display import render_circuit_jupyter
# 3量子ビットを準備
circ_redundant = Circuit(3)
# 冗長な回路を作成
circ_redundant.X(0).X(1).SWAP(0,1).SWAP(1,2).SWAP(2,1).SWAP(1,0).X(0).X(1).CX(0,2)
# 可視化
render_circuit_jupyter(circ_redundant)
この回路はいかがでしょうか?
- SWAPはSWAPで無効化されるので、意味がない。
- 0,1ビット目の$X$ゲートも、後方の$X$ゲートで元の状態に戻る為、意味がない。
- 結局、意味のある処理は最後の$CX(0,2)$のCNOTだけ
ということで、前半の冗長な回路を除去していきます。
from pytket.predicates import CompilationUnit
from pytket.passes import CliffordSimp
# ① CompilationUnitで回路を包む
unit_redundant = CompilationUnit(circ_redundant)
# ② CliffordSimpを使って冗長な回路を除去
CliffordSimp().apply(unit_redundant)
# ③ 展開後の回路を取り出し
transformed_cric = unit_redundant.circuit
# ④ 可視化
render_circuit_jupyter(transformed_cric)
冗長な前半の回路が除去されて、CNOTだけが残りました。
CliffordSimp()
ですが、リファレンスを読むと
An optimisation pass that performs a number of rewrite rules for simplifying Clifford gate sequences, similar to Duncan & Fagan (https://arxiv.org/abs/1901.10114). Given a circuit with CXs and any single-qubit gates, produces a circuit with TK1, CX gates.
といったように、簡略化のための書き換えルールを適用し、$CX$と任意の単一量子ゲートを持つ回路への変換により、冗長な回路が除去されました。
構造的には先程と一緒ですが、こんなイメージです。
3)後方の単一ゲートを前方に持ってくる
いきなりですが、こういった回路を考えます。
from pytket.circuit import Circuit
from pytket.circuit.display import render_circuit_jupyter
# 3量子ビットを準備
circ_com = Circuit(3)
# 後方に単一ゲートが多数
circ_com.CX(0,1).CX(0,2).X(1).X(2).X(1).X(2).X(1).X(2)
# 可視化
render_circuit_jupyter(circ_com)
この2つのCNOT($CX$)の背後にある、単一ゲートを前方に持ってくる様、書き換えをしてみたいと思います。この場合、単一ゲート($X$ゲート3つ)は、前方に交換可能(Commute可能)ですので、単純に$X$ゲートが前に出てくる変換となります。
from pytket.predicates import CompilationUnit
from pytket.passes import CommuteThroughMultis
# ① CompilationUnitで回路を包む
unit_com = CompilationUnit(circ_com)
# ② CommuteThroughMultisで、単一ゲートを前方へ
CommuteThroughMultis().apply(unit_com)
# ③ 展開後の回路を取り出し
transformed_cric = unit_com.circuit
# ④ 可視化
render_circuit_jupyter(transformed_cric)
処理の構造としては先程と一緒ですので、模式図は省略します。
4)単一ゲートを前方へ移動し、冗長な処理を排除
最後に、上記の前方へ移動した、$X$ゲート3つは冗長であり無用なゲートを排除します。
-
CommuteThroughMultis
: 単一ゲートを前方へ -
RemoveRedundancies
: 冗長ゲートを排除4
from pytket.predicates import CompilationUnit
from pytket.passes import SequencePass,RemoveRedundancies,CommuteThroughMultis
from pytket.passes import *
# ① CompilationUnitで回路を包む
unit_com = CompilationUnit(circ_com)
# ② 複数の処理を同時に適用
SequencePass(
[
CommuteThroughMultis(), #CommuteThroughMultisで、単一ゲートを前方へ
RemoveRedundancies() #冗長性を排除
]
).apply(unit_com)
# ③ 展開後の回路を取り出し
transformed_cric = unit_com.circuit
# ④ 可視化
render_circuit_jupyter(transformed_cric)
単一ゲートが前方に交換し(Commuteし)、かつ、$XXX \rightarrow X$となり冗長性も排除できました。
この両方の処理を同時に実現するために、SequencePass
を使っています。
まとめ
pytket.passes
パッケージを使って、回路の書き換えを色々やってみました。
下記の公式サンプルでも良かったのですが、単純な回路のほうが説明がわかりやすいかと考え、回路自体に意味は無い単純な回路で、各機能の役割を見てみました。
pytket.passes
パッケージは他にも、後述のとおり、様々(膨大)に機能があるので、皆さんも色々試して頂ければと思います。
おまけ①(過去のカレンダ投稿)
2020年
2021年
おまけ②(pytket.passesのパッケージ)
本当に、色々機能があるので、どれが何につかえるのか?は徐々に勉強していきたいと思います。
今回使った機能には、★をつけてあります。
- pytket.passes.SequencePass() ★(4)
- pytket.passes.AASRouting()
- pytket.passes.CliffordSimp() ★(2)
- pytket.passes.CnXPairwiseDecomposition()
- pytket.passes.CommuteThroughMultis() ★(3,4)
- pytket.passes.ComposePhasePolyBoxes()
- pytket.passes.ContextSimp()
- pytket.passes.CustomPass()
- pytket.passes.CustomRoutingPass()
- pytket.passes.CXMappingPass()
- pytket.passes.DecomposeArbitrarilyControlledGates()
- pytket.passes.DecomposeBoxes()
- pytket.passes.DecomposeClassicalExp()
- pytket.passes.DecomposeMultiQubitsCX() ★(1)
- pytket.passes.DecomposeSingleQubitsTK1()
- pytket.passes.DecomposeSwapsToCircuit()
- pytket.passes.DecomposeSwapsToCXs()
- pytket.passes.DecomposeTK2()
- pytket.passes.DefaultMappingPass()
- pytket.passes.DelayMeasures()
- pytket.passes.EulerAngleReduction()
- pytket.passes.FlattenRegisters()
- pytket.passes.FullMappingPass()
- pytket.passes.FullPeepholeOptimise()
- pytket.passes.GlobalisePhasedX()
- pytket.passes.GuidedPauliSimp()
- pytket.passes.KAKDecomposition()
- pytket.passes.NaivePlacementPass()
- pytket.passes.NormaliseTK2()
- pytket.passes.OptimisePhaseGadgets()
- pytket.passes.PauliSimp()
- pytket.passes.PauliSquash()
- pytket.passes.PeepholeOptimise2Q()
- pytket.passes.PlacementPass()
- pytket.passes.RebaseCustom()
- pytket.passes.RebaseTket()
- pytket.passes.RemoveBarriers()
- pytket.passes.RemoveDiscarded()
- pytket.passes.RemoveRedundancies() ★(4)
- pytket.passes.RenameQubitsPass()
- pytket.passes.RoutingPass()
- pytket.passes.SimplifyInitial()
- pytket.passes.SimplifyMeasured()
- pytket.passes.SquashCustom()
- pytket.passes.SquashRzPhasedX()
- pytket.passes.SquashTK1()
- pytket.passes.SynthesiseHQS()
- pytket.passes.SynthesiseOQC()
- pytket.passes.SynthesiseTK()
- pytket.passes.SynthesiseTket()
- pytket.passes.SynthesiseUMD()
- pytket.passes.ThreeQubitSquash()
- pytket.passes.ZZPhaseToRz()
-
なお、これくらいのトランスパイルであれば、Qiskitでも実装されています。
詳細は、Qiskitチュートリアル:トランスパイラー パスとパス マネージャー をご確認ください。 ↩ -
Compilationのサンプルでは、In order to apply a pass to a circuit, we must first create a CompilationUnit from it. We can think of this as a 'bridge' between the circuit and the pass. The CompilationUnit is constructed from the circuit; the pass is applied to the CompilationUnit; and the transformed circuit is extracted from the CompilationUnit: との記載があったので、このクラスでラップするのがお作法の様に見えたため、こういった処理にしています。
一方で、CompilationUnit
でラップせずとも量子回路をそのままpassesにapplyできる様です。 ↩ -
冗長性の排除に
CliffordSimp
を使っても良かったのですが、この処理だと単一ゲート操作が、TK1ゲートという独自ゲートに変換されてしまい見づらかったので、RemoveRedundancies
を使うことにしました。 ↩