pytket.passesで遊んでみた

# はじめに
量子コンピュータカレンダーも22日目となり、ディープな話が様々繰り広げられていますが、今日は軽めの内容とさせて頂きたいと思いますｗ（浅くてスミマセンｗ）
なお、量子コンピュータカレンダー参加も３年目となります！
過去（2020/2021）のカレンダにて投稿した記事については、本稿末尾にてリンクさせて頂くので、そちらもご笑覧いただければ幸いです。

# やること
[Quantinuum](https://www.quantinuum.com/)が提供するpytket(tket)ですが、プラットフォームに依存しないSDKを提供し、
合わせてターゲット量子マシンへのコンパイラ／トランスパイラの機能も提供しています。

**このコンパイラ／トランスパイラの機能に興味を持ったのでちょっと試してみよう。**

というのが本稿の趣旨です。

> そういったコンパイル／トランスパイルならpytketを使わずともQiskitでも出来るじゃん。。

とかあるかもしれませんが、
純粋にpytketでどんな変換が出来るんだろう。を試してみるというスタンスなので、
あまり細かいことは気にせず進めてみたいと思います。

https://github.com/CQCL/pytket

# では、始めます
トランスパイラの機能は、`pytket.passes`パッケージで提供されているのですが、、
若干慣れが必要なので、まずは一番簡単な、Toffoliゲートの分解から始めたいと思います。
##  1）Toffoli（CCX）を分解
Toffoliゲート（$CCX$ゲート）ですが、複数量子系の完全系である$H,T,T^{\dagger},CNOT$を用いてこんな等価回路に分解可能であると知られています。[^wikipedia]

[^wikipedia]: https://en.wikipedia.org/wiki/Toffoli_gate

![CCX.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/1d0dee21-900d-247b-4361-afab2a0826bd.png)

この等価回路を`pytket.passes`パッケージを用いて作成してみたいと思います。[^Qiskit]

[^Qiskit]: なお、これくらいのトランスパイルであれば、Qiskitでも実装されています。
詳細は、[Qiskitチュートリアル：トランスパイラー パスとパス マネージャー](https://qiskit.org/documentation/locale/ja_JP/tutorials/circuits_advanced/04_transpiler_passes_and_passmanager.html) をご確認ください。

まずは、pipでモジュールを導入します。

```shell
!pip install pytket
```

次に、Toffoliを実装して、可視化します。
```python
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)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/1603d8ae-bf0e-f143-4f2c-6a6949092132.png)

なお、量子回路の表示スタイルは、このボタンで変更できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/9bf6f477-aa4e-f449-8ee9-68f17c4c4551.png)

では、回路を変換していきましょう。

```python
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)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/c2505fc2-3f95-63d0-0282-bd0af8890b45.png)

といった具合に、無事に、Toffoliを分解することができました。
ポイントは、
* ①　量子回路をpytketのSDKにて実装したら、その回路を`CompilationUnit`でラップし[^pass]
* ②　所望の変換（上記では`DecomposeMultiQubitsCX`）を`apply`する
* ③　`apply`後の`CompilationUnit`から`circuit`を取り出すと変換後の回路が得られる


[^pass]: [Compilationのサンプル](https://github.com/CQCL/pytket/blob/main/examples/compilation_example.ipynb)では、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できる様です。

といったところでしょうか。

<img width=700 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/88cb3b7d-77c6-8479-5c96-ed17177f4ee6.png"/>

## 2）冗長な回路の除去
下記のような回路を考えます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/302cb4a6-ee9e-b129-4f66-4549639ce23c.png"/>

$X$ゲートの後に、$X$ゲートがかかっているので、ブロッホ球で考えるとX軸を中心に180度回転し、もう一度180度回転するので、360度回転となり、結局何もしていない回路となります。

<img width=200px src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/a57f5e70-ef90-02b5-2b3f-b09cab3dd510.gif"/>


もう一つ、複数量子系で考えて見ます。
下記はどうでしょうか？これも、SWAPしてSWAPしているので、結局元の状態に戻ります。
よって、下記も無駄のある冗長な回路と見なすことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/22a4dc9d-a88c-eb97-e486-00a4b6c35011.png)

こういった無駄のある冗長な回路の排除を考えていきます。

では、始めます。
まずは、冗長な回路を作成してみましょう。

```python
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)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/981fb05f-4d71-2e4d-b9f9-48a2cd77dc47.png)

この回路はいかがでしょうか？
* SWAPはSWAPで無効化されるので、意味がない。
* 0,1ビット目の$X$ゲートも、後方の$X$ゲートで元の状態に戻る為、意味がない。
* 結局、意味のある処理は最後の$CX(0,2)$のCNOTだけ

ということで、前半の冗長な回路を除去していきます。

```python
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)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/29b3dd45-d7d3-b1c7-651f-e5b31e93ba14.png)

冗長な前半の回路が除去されて、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$と任意の単一量子ゲートを持つ回路への変換により、冗長な回路が除去されました。
構造的には先程と一緒ですが、こんなイメージです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/106c47f8-58cc-cafc-62b9-d3220fc969b1.png)

## 3）後方の単一ゲートを前方に持ってくる
いきなりですが、こういった回路を考えます。

```python
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)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/f080ddd3-7a38-55f3-14dc-a15c826f6599.png)

この２つのCNOT（$CX$）の背後にある、単一ゲートを前方に持ってくる様、書き換えをしてみたいと思います。この場合、単一ゲート（$X$ゲート３つ）は、前方に交換可能（Commute可能）ですので、単純に$X$ゲートが前に出てくる変換となります。

```python
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)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/7af48a3a-86f7-29a8-699d-1efdb88ff64a.png)

処理の構造としては先程と一緒ですので、模式図は省略します。

## 4）単一ゲートを前方へ移動し、冗長な処理を排除
最後に、上記の前方へ移動した、$X$ゲート3つは冗長であり無用なゲートを排除します。
* `CommuteThroughMultis`　：　単一ゲートを前方へ
* `RemoveRedundancies`　　：　冗長ゲートを排除[^tk]

[^tk]: 冗長性の排除に`CliffordSimp`を使っても良かったのですが、この処理だと単一ゲート操作が、TK1ゲートという独自ゲートに変換されてしまい見づらかったので、`RemoveRedundancies`を使うことにしました。

```python
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)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/f0cd72a5-abfa-b5a0-cabb-8200285b0bad.png)

単一ゲートが前方に交換し（Commuteし）、かつ、$XXX \rightarrow X$となり冗長性も排除できました。
この両方の処理を同時に実現するために、`SequencePass`を使っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/ecad648c-83af-e1f0-d5c7-085f2f2f518d.png)

# まとめ
`pytket.passes`パッケージを使って、回路の書き換えを色々やってみました。
下記の公式サンプルでも良かったのですが、単純な回路のほうが説明がわかりやすいかと考え、回路自体に意味は無い単純な回路で、各機能の役割を見てみました。

https://github.com/CQCL/pytket/blob/main/examples/compilation_example.ipynb

`pytket.passes`パッケージは他にも、後述のとおり、様々（膨大）に機能があるので、皆さんも色々試して頂ければと思います。

# おまけ①（過去のカレンダ投稿）
#### [2020年](https://qiita.com/advent-calendar/2020/quantum)

https://qiita.com/shnchr/items/5ffdf395c4841e5ec819

#### [2021年](https://qiita.com/advent-calendar/2021/quantum)
https://qiita.com/shnchr/items/c3355c1aed66f9411e65

# おまけ②（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()