Q#チュートリアルを触ってみた


### 動機

- [日本初のIBM製「ゲート型商用量子コンピュータ」が新川崎で稼働。アメリカ、ドイツに次いで世界で3番目](https://pc.watch.impress.co.jp/docs/news/1340386.html)
- 以前Q#を触っていたことを思い出し、その紹介をしようと思った

## Q#とは？

- 2017年12月にマイクロソフトが公開した、量子プログラミング言語のひとつ
- 30理論量子ビットのローカル量子シュミレーターを使用して、ローカル環境でデバッグ実行可能
    - マイクロソフトのクラウドである「Microsoft Azure」を利用することで、さらに大きい40以上の論理量子ビットをシュミレートすることが可能

## 量子プログラミングとは？

> 量子プログラミング言語（りょうしプログラミングげんご、英: Quantum programming language）とは量子アルゴリズムの表現を実現するプログラミング言語の総称である。

## 量子アルゴリズムとは？

### 古典コンピュータ

- 「ビット」の値（＝0 **又は** 1）を書き換えながら計算を行う

### 量子コンピュータ

- 「量子アニーリング方式」と「量子ゲート方式」の2種類がある
    - 量子アニーリング方式の方が実用化が進んでいる
    - Q#は量子ゲート方式を扱う
- 「量子ビット」の値（＝0 と 1 の**重ね合わせの状態**）を書き換えながら計算を行う
- 原理的には、古典コンピュータ（ビット）で計算できることはすべて、量子コンピュータ（量子ビット）で計算できるとされている

この量子ビットを用いて計算を行うアルゴリズムのことを「量子アルゴリズム」という。

## 量子ビットの特性

- 「キュービット」ともいう
- 実際に値を観測をするまで「0であり1でもある」状態であり、観測すると一定の割合で「0」もしくは「1」という結果が得られる

### ブラ-ケット記法

ブラ-ケット記法を用いると、1量子ビットは次のように表現される。

　　$ \alpha |0\rangle +\beta |1\rangle $　（$ \alpha ,\beta $ は $ |\alpha |^{2}+|\beta |^{2}=1 $ の関係を満たす複素数）

上記の記法で古典ビットを表現すると、$ \alpha ,\beta $ はどちらかが0でもう一方が1になる。

# チュートリアルを試す

https://docs.microsoft.com/ja-jp/azure/quantum/tutorial-qdk-quantum-random-number-generator?tabs=tabid-qsharp

## プロジェクトの作成

### dotnet-sdkのインストール

環境：WSL2（Ubuntu18.04 ディストリビューション）

https://qiita.com/tabizou/items/f47983d1d327e6c5d5e1#ms%E3%81%AEkey%E3%81%A8product-repository%E3%82%92%E7%99%BB%E9%8C%B2%E3%81%99%E3%82%8B

こちらの記事を参考にさせていただいた。

### Q#プログラムの作成

1. VSCodeにQ#用拡張機能を追加
- https://docs.microsoft.com/ja-jp/azure/quantum/install-command-line-qdk?tabs=tabid-vscode
2. `Command + Shift + P` で `Q#: Create new project...` を選択

## Hello world!

上記手順で以下のような構成のプロジェクトフォルダが作成される

```
./
└ helloWorld/
  ├ bin/
  ├ obj/
  ├ Drivers.cs
  ├ helloWorld.csproj
  └ Operations.qs
```

`Operations.qs` には次のようなプログラムが用意される。

```qs:Operations.qs
namespace helloWorld
{
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Primitive;

    operation HelloQ () : Unit {
        Message("Hello quantum world!");
    }
}
```

実行

```shell
$ cd helloWorld
$ dotnet run
Hello quantum world!
```

## 反転

一定の確率で 0 もしくは 1 になる量子ビットに対し、その確率を入れ替える操作を行う

```qs
namespace reverseState {
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Intrinsic;

    @EntryPoint()
    operation Main() : Unit {
        use q = Qubit(); // 量子ビットをアロケート。初期状態では100%の確率で 0 になる。
        let result1 = M(q); // 量子ビットの状態を測定する

        X(q); // 量子ビットの状態を反転させる
        let result2 = M(q);

        X(q); // さらに量子ビットの状態を反転させる
        let result3 = M(q);

        Message($"{result1} ==[X]==> {result2} ==[X]==> {result3}");
    }
}
```

実行

```shell
$ dotnet run
Zero ==[X]==> One ==[X]==> Zero
```

### ブロッホ球

https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%AD%E3%83%83%E3%83%9B%E7%90%83

`X()`が何をしているかというと、ブロッホ球でいうX軸を中心に回転させる操作を行うことにより、「0である」状態と「1である」状態の割合を変化させています。このメソッドを通すことで180度の回転が行われ、「100%の確率で0になる」状態から「100%の確率で1になる」状態に変化させています。

また同様に `Y()` と `Z()` もあり、それぞれ回転軸が異なります。「100%の確率で0になる」状態の量子ビットについて、同じくY軸回転を行った場合は同様の結果になり、Z軸回転を行った場合は何も変化しません。

## 重ね合わせの状態

先ほどの `X()` を用いることにより、ある量子ビットを0か1を指定して初期化することができます。

```qs
operation SetQubitState(q: Qubit, expect: Result): Unit {
    if M(q) != expect {
        X(q);
    }
}
```

アダマールゲート（`H()`）を通すことで、量子ビットを「重ね合わせの状態」にすることができます。
この状態の量子ビットは、50%ずつの確率で0か1を観測します。

```qs
namespace hadamard {
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Intrinsic;

    operation SetQubitState(q: Qubit, expect: Result): Unit {
        if M(q) != expect {
            X(q);
        }
    }
    
    @EntryPoint()
    operation Main(count: Int) : Unit {
        use q = Qubit();

        mutable countZero = 0;
        for _i in 1..count {
            SetQubitState(q, Zero);

            H(q); // 重ね合わせの状態にする

            if M(q) == Zero {
                set countZero += 1;
            }
        }

        Message($"{count} times measured.");
        Message($"Zero: {countZero}, One: {count - countZero}");
    }
}
```

実行

```shell
$ dotnet run --count 1000
1000 times measured.
Zero: 498, One: 502
```

※結果は実行の度に変わりますが、概ね半々の割合で0と1が観測されます。

アダマールゲートを簡単に説明すると、`X()` や `Y()` が180度回転させていたのに対し、`H()` は90度だけ回転させるので、ちょうど0と1の中間の状態になります。

https://docs.microsoft.com/ja-jp/azure/quantum/tutorial-qdk-quantum-random-number-generator?tabs=tabid-qsharp#visualizing-the-code-with-the-bloch-sphere

## 乱数ジェネレータ

次のプログラムでは、重ね合わせの状態を用いて乱数を生成します。

```qs
operation RandomQuantumBit() : Result {
    use q = Qubit();
    H(q);
    return MResetZ(q);
}
```

このメソッドは、量子ビットの重ね合わせの状態の性質を利用して、ランダムに0か1を返します。

```qs
namespace random {
    open Microsoft.Quantum.Convert;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Measurement;
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Intrinsic;

    operation RandomQuantumBit() : Result {
        use q = Qubit();
        H(q);
        return MResetZ(q);
    }

    operation SampleRandomNumberInRange(max : Int) : Int {
        mutable bits = new Result[0];
        for idxBit in 1..BitSizeI(max) {
            set bits += [RandomQuantumBit()]; // Array of 'Zero' or 'One'
        }
        let sample = ResultArrayAsInt(bits);
        return sample > max
               ? SampleRandomNumberInRange(max)
               | sample;
    }

    @EntryPoint()
    operation Main(max: Int) : Int {
        Message($"Generate a random number between 0 and {max}: ");
        return SampleRandomNumberInRange(max);
    }
}
```

こちらがプログラム全体になっており、0もしくは1をランダムに要素に持つ配列を生成し、それを2進数に見立てて10進数に変換し出力しています。

## 量子もつれ状態（エンタングル状態）

こちらは、量子もつれの状態を作るプログラムになります。ある二つの量子ビットにおいて、片方を観測すると、もう片方の観測結果が常に一致するという結果になります。

```qs
namespace cnot {
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Intrinsic;

    operation SetQubitState(q: Qubit, expect: Result): Unit {
        if M(q) != expect {
            X(q);
        }
    }
    
    @EntryPoint()
    operation Main(count : Int, initial : Result): Unit {
        use (q0, q1) = (Qubit(), Qubit());

        mutable countZero = 0;
        mutable countEqual = 0;
        for i in 1..count {
            SetQubitState(q0, initial);
            SetQubitState(q1, Zero);

            H(q0);
            CNOT(q0, q1); // 第一引数がOneであれば、第二引数の量子ビットにXゲートが適用される。

            let result = M(q0);
            if result == Zero {
                set countZero += 1;
            }
            if M(q1) == result {
                set countEqual += 1;
            }
        }

        Message($"zero: {countZero}, one: {count-countZero}, equal: {countEqual}");
    }
}
```

実行

```shell
$ dotnet run --count 1000 --initial Zero
zero: 502, one: 498, equal: 1000
```

これは言い換えると、片方の量子ビットの結果がもう片方に影響を及ぼしているということになります。

### CNOTゲート

`CNOT(q0, q1)` は、第一引数のビットが 1 だった場合に、第二引数のビットに対しXゲートを適用します。
表に表すと、次のような状態になっています。

| q0（重ね合わせ状態） | q1（0で初期化） | → | q0' | q1' |
| --- | --- | --- | --- | --- |
| 0 | 0 | → | 0 | 0 |
| 1 | 0 | → | 1 | 1 |

q0は重ね合わせ状態のため、観測するまで0か1かわからない状態です。一方、q1は0で初期化されています。
この状態でCNOTゲートに通すことで、q0、q1どちらを先に観測しても、常に他方の結果はそれを同じになるという状態を作成できました。
また、CNOTゲートを適用し50%の確率で (0, 0) もしくは (1, 1) が観測される状態をベル状態といいます。

これは、一方の量子ビットの状態が他方の量子ビットに影響を及ぼしているようにみえる奇妙な状態で、それぞれを数光年の距離に離したとしてもこの結果は変わりません。
しかし測定結果はランダムであり、一方の量子ビットの観測結果は、もう一方の量子ビットの操作の影響を受け続けるというわけではありません。なのでこの量子もつれを利用しての通信をすることはできません。これは[量子複製不可能定理](https://ja.wikipedia.org/wiki/%E9%87%8F%E5%AD%90%E8%A4%87%E8%A3%BD%E4%B8%8D%E5%8F%AF%E8%83%BD%E5%AE%9A%E7%90%86)といわれています。

## おわりに

知っていたら"強そう"という理由で始めてみた量子プログラミングですが、理解するまでが結構大変で、またまだ実用的なプログラムを作るには知識も経験も足りていませんが、気が向いたら少しづつ学習を続けていこうかなと思います。