銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

Q# で 1+1 を計算してみる

この前参加した勉強会で話題にでていたこの記事のコードをQ#で書いてみることにしました。

qiita.com

qiita.com

Q#の場合、現状シミュレーターしかないというのもありますが、あくまでQ#のQubitは論理量子ビットで、Q#で定義した論理量子ビットの操作から物理量子ビットへの変換はエラー訂正含めてよしなにやってくれる(はず)というのがあるので、Half Adderの回路を記述すればよいことになります。こちらのQ&Aも参考になりました。

quantumcomputing.stackexchange.com

2番目のQiitaの記事にもあるHalf Adderの回路をそのまま実装することにします。入力に2量子ビット、出力に2量子ビットを使うことにします。AND操作は二重制御NOT(CCNOT)操作を使います。

CCNOT operation (Microsoft.Quantum.Primitive) | Microsoft Docs

XORはCNOT操作を使います。

CNOT operation (Microsoft.Quantum.Primitive) | Microsoft Docs

というわけで、まずは入力に2量子ビット、出力に2量子ビットを確保した引数がくると想定したOperationは次のように書けます。

operation Add(input: Qubit[], output: Qubit[]) : ()
{
    body
    {
        CCNOT(input[0], input[1], output[0]);
        CNOT(input[0], output[1]);
        CNOT(input[1], output[1]);
    }
}

さて、この操作はQubitを渡してもらっているので、室温系から直接実行できません。室温系からは2要素のビット配列を渡してそれを足し算した結果を整数で受け取るようにしましょう。Operationの定義は次のようにかけるはずです。

operation ApplyAdd(input1: Bool, input2: Bool) : (Int)
{}

受け取ったビット配列から入力用のQubitを準備する必要があります。Qubitは0で初期化されるので、入力が1(ビットがtrue)の場合にX操作で反転させることにします。

X operation (Microsoft.Quantum.Primitive) | Microsoft Docs

というわけで、第一引数のビット配列に対応した状態に第2引数のQubit配列を変化させるOperationを用意します。

operation PrepareInput(input1: Bool, input2: Bool, input: Qubit[]) : ()
{
    body
    {
        if (input1)
        {
            X(input[0]);
        }
        if (input2)
        {
            X(input[1]);
        }
    }
}

以上を踏まえて、Q#全体のコードは次のようになります。

namespace Addition
{
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Primitive;

    operation ApplyAdd(input1: Bool, input2: Bool) : (Int)
    {
        body
        {
            mutable result = 0;
            using (qs = Qubit[4])
            {
                let input = qs[0..1];
                let output = qs[2..3];
                
                PrepareInput(input1, input2, input);

                Add(input, output);

                if (M(output[0]) == One)
                {
                    set result = result + 2;
                }
                if (M(output[1]) == One)
                {
                    set result = result + 1;
                }
                ResetAll(qs);
            }
            return result;
        }
    }

    operation PrepareInput(input1: Bool, input2: Bool, input: Qubit[]) : ()
    {
        body
        {
            if (input1)
            {
                X(input[0]);
            }
            if (input2)
            {
                X(input[1]);
            }
        }
    }

    operation Add(input: Qubit[], output: Qubit[]) : ()
    {
        body
        {
            CCNOT(input[0], input[1], output[0]);
            CNOT(input[0], output[1]);
            CNOT(input[1], output[1]);
        }
    }
}

usingで4量子ビットを用意し、入力用出力用の変数にそれぞれ割り当てます。配列の引数にRange型である0..1という記述をするとsliceされた配列を作ることができます。この例だと、indexが0と1の2要素配列となります。Add操作をしたとの結果の読み取りですが、M操作を利用し、出力の21を表すQubitが1ならば答えに2を足しています。測定結果はResult型のOneもしくはZeroであらわされます。20のQubitが1ならば結果に1を足した後、ResetAll操作でusingで用意したQubitをすべて初期状態にリセットします。usingを抜けるときは必ずQubitの状態を0に戻さないといけないのがQ#のルールです。

M operation (Microsoft.Quantum.Primitive) | Microsoft Docs

ResetAll operation (Microsoft.Quantum.Primitive) | Microsoft Docs

最後にApplyAdd操作を呼び出すC#コードをMainメソッド内に記述します。

using System.Threading.Tasks;
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.Simulators;

namespace Addition
{
    class Driver
    {
        static void Main(string[] args)
        {
            RunAsync().GetAwaiter().GetResult();
        }

        static async Task RunAsync()
        {
            using (var sim = new QuantumSimulator())
            {
                var res = await ApplyAdd.Run(sim, false, false);
                System.Console.WriteLine($"0+0= {res}");

                res = await ApplyAdd.Run(sim, false, true);
                System.Console.WriteLine($"0+1= {res}");

                res = await ApplyAdd.Run(sim, true, false);
                System.Console.WriteLine($"1+0= {res}");

                res = await ApplyAdd.Run(sim, true, true);
                System.Console.WriteLine($"1+1= {res}");
            }
            System.Console.WriteLine("Press any key to continue...");
            System.Console.ReadKey();
        }
    }
}

実行すると次のように出力されるはずです。これで計算ができました。

> dotnet run
0+0= 0
0+1= 1
1+0= 1
1+1= 2
Press any key to continue...

ちなみに量子回路の勉強のためにこうやって記述しましたが、Q#コード中で数値計算もできるので、普通に計算したい場合は例えばこんな風にQ#でかけます。量子ビットを扱わない処理はfunctionとして定義することができます。

function AddFunc(input1: Int, input2: Int) : (Int)
{
    return input1 + input2;
}