銀の光と碧い空

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

Q# の開発環境を準備する (.NET Core CLI + Visual Studio Code編)

最近 Q# のセッションをすることが増えたので、開発環境の準備をまとめておこうと思います。今回は、.NET Core と Visual Studio Codeを利用する環境なので、Windows, Linux, macOSで使えるはずです。11月4日時点の情報で、Quantum Development Kit 0.3の情報に更新しました。

Installing the Q# development environment for VS Code | Microsoft Docs

始めての場合はインストール手順を、0.2など以前のバージョンをインストール済みの場合はアップデート手順を参照してください。

 インストール手順

まずはじめに .NET Core SDK をインストールします。バージョンは2.0 より新しければサポートされますが、原則として最新版をインストールするのがよいでしょう。詳細は未検証なのですが、いくつかのコマンドが.NET Core 2.0の古いージョンで動作しないことがあるようです。.NET CoreのダウンロードページからWindows, Linux, macOSごとのリンクに移動しインストールします。SDKとRuntimeというものがありますが、SDKの方をインストールします。SDKはRuntimeを含んでいるのでRuntimeを追加でインストールする必要はありません。また、Runtimeだけだとソースコードがコンパイルできないので開発ができません。

www.microsoft.com

Windows、macOSはインストーラーがダウンロードできるようですが、Linuxの場合ディストリビューションごとのパッケージを提供しており、例えばCentOSならyumを使ってインストールできます。インストール後、dotnetコマンドにPATHが通っていることを確認してください*1

次に、Q#で開発するための.NET Coreのプロジェクトテンプレートをインストールします。WindowsならPowerShellもしくはコマンドプロンプト、Linux, macOSならbashなどお好きなシェルで実行します。Administratorもしくはrootユーザーではなく、実際に開発を行うユーザーで実行するほうがいいでしょう。

dotnet new -i "Microsoft.Quantum.ProjectTemplates::0.3.1810.2508-preview"

ちなみに、Q#で開発するための.NET Coreのプロジェクトテンプレートと回りくどい言い方をしているのは、実はこれがQuantum Developmenet Kitそのものではないからなのですが、今のところは細かいところは気にせずこれをインストールしてください。

ここから先はQ#の開発にVisual Studio Codeを利用する場合の手順になります。vimやemacsなどそれ以外のエディタで開発したい場合は不要なので、インストール後の確認手順に進んでください。今のところ、Visual Studio CodeでもQ#の開発にそこまで便利な支援がないため、vimやemacsに慣れていればそちらが使いやすいかもしれません。

まず、Visual Studio Codeをインストールします。次のページから、これもWindows, Linux, macOSごとのページに移動して指示にしたがってください。

code.visualstudio.com

StableもしくはInsider Buildのどちらでも問題ないはずですが、私はStableを使っています。

あわせて、gitのインストールもおすすめします。サンプルコードなどがGitHubリポジトリとして公開されているので、gitでクローンできるようにしておくと便利です。

Git - Downloads

最後に、Visual Studio Code用のMicrosoft Quantum Development Kit for Visual Studio Codeをインストールします。次のページに移動して、Installボタンをクリックしてもよいですし、

marketplace.visualstudio.com

Visual Studio Codeの拡張画面から検索してインストールしてもよいです。スクリーンショットはインストール済みなので、アンインストールするボタンになっています。

f:id:tanaka733:20181026200925p:plain

動作確認

これでQ#の開発環境は整いました。動作の確認ですが、次の3つのいずれか(もしくは全部)がおすすめです。

とりあえずサンプルコードを読みたい、もしくはQ# SDKの中身のコードを読みたいという場合は、Microsoft/QuantumというGitHubリポジトリをクローンします。ビルドのテストには、Samples以下のどれかのフォルダ(Teleportationが最初のドキュメントでも紹介されてます)に移動してビルドしてみましょう。

github.com

.NET Core CLIでビルドする場合は、シェル上でディレクトリを移動してdotnet runコマンドを実行します。Visual Studio Code上でビルドする場合は、ビルドするフォルダを含むフォルダを開いたあとに、Visual Studio Codeの統合シェルを開いてdotnet runコマンドを実行します。

cd Samples/Teleportation/
dotnet run

次に実際にプロジェクトを作成しコンパイルする動作を確認したい場合は、次のページに従って確認します。

docs.microsoft.com

Q#について手を動かしながら勉強したいという場合は、Microsoft/QuantumKatasというリポジトリをクローンします。詳細な手順はReadmeに書かれていますが、.NET CoreのUnit Testを使って問題が記述されています。テーマごとにフォルダがわかれていて、その中のTasks.qs に問題が書かれています。問題の条件を満たすようなQ#のコードを指定された場所に記述し、dotnet testを実行すると、正解していればその問題のUnit Testが成功するようになります。

github.com

アップデート手順

アップデートは、.NET Coreのプロジェクトテンプレート、Visual Studio Codeの拡張機能、既存のプロジェクトの更新の大きく3つが必要です。まず、プロジェクトテンプレートは最新版で上書きされるので、dotnet new -iコマンドを与えらしいバージョンを指定して実行すればよいです。次のコマンドを実行すれば、0.2のテンプレートがインストールされていても0.3に更新されます。

dotnet new -i "Microsoft.Quantum.ProjectTemplates::0.3.1810.2508-preview"

Visual Studio Code (Visual Studioの場合も同じ)の拡張機能は、拡張機能の管理メニューから更新しましょう。ここまでやれば、これから新規に作成するQ#のプロジェクトは新しいバージョンに対応したプロジェクトとして作成されます。

もし以前のバージョンで作成したプロジェクトを新しいバージョンに対応させたい場合は、まずRelease Notesを読みましょう。0.3の場合はここです。

QDK 0.3 Language Review and Migration Guide | Microsoft Docs

0.3の場合の手順は次のようになります。

  1. dotnet cleanで対象プロジェクトをクリーンする
  2. プロジェクトのcsprojファイル(XML形式)内のPackageReference要素のVersion属性を指定のバージョン(0.3の場合は0.3.1810.2508-preview)に変更する <PackageReference Include="Microsoft.Quantum.Canon" Version="0.3.1810.2508-preview" /> <PackageReference Include="Microsoft.Quantum.Development.Kit" Version="0.3.1810.2508-preview" />
  3. 文法の破壊的変更を自動で修正するため、dotnet msbuild /t:qsharpformatを実行する。コンパイラーの機能により、基本的には変更が必要なすべての部分が変更されるはず。
  4. 念のためビルドし、ビルドが成功することを確認する。

必要があれば動作確認の手順を再度実行しておきましょう。

*1:必須ではないが、通っていることを前提とする説明が多いため推奨

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;
}

Q# Coding Contestを題材にQuantum Oracleについてがんばって理解してみる

以前話題に出した Q# Coding ContestでQuantum Oracleについての問題があったのですが、Quantum Oracleとはなんなのか?というのを出てきた問題を題材に自分なりにまとめてみました。まず、Quantum Oracleについてはcoding contestのブログにて記事が出ています。

codeforces.com

このブログの記事の概要としては、古典的関数 {f:  \{0, 1 \}^n \rightarrow \{0, 1\}^m} を扱うことにあるようですが、要はn-bitのバイナリを受け取ってm-bitのバイナリを返すという意味で、古典関数をバイナリの入出力で表現したと考えればよさそうです。 で、これをQ#の中で考えるには、Qubitの操作として定義したいものの、nとmが異なる場合はそもそもそのまま扱えないし、たとえ同じだとしても随伴操作が作れなくてユニタリ変換にならないのでQubitの操作として定義することができないといっています。そこで、n Qubitからなる状態 x とm Qubitからなる状態 y を重ね合わせたものに対する操作 O を考えると、これはユニタリ変換になってて(というよりそうしていて)、次のように古典関数 f を定義できてうれしいといっています。

{ \displaystyle
O(|x \rangle \otimes |y \rangle) = |x \rangle \otimes |y \oplus f(x) \rangle
}

つまり、出力 |y> も入力 |x> とあわせて入力として扱い、出力 |y> と古典関数f の排他的論理和をとったものと入力 |x> とのテンソル積が欲しいものになる、ということのようです。これだけだと、自分はわかったようなわからんような感じだったので、とりあえずCoding Contestの問題を見てみます。

codeforces.com

入力 |x> に対し、kが指定されたときに、k番目の要素を返す古典関数fに対するQuantum Oracleに関する問題です。ここでは、入力がn Qubitの|x>、出力が1 Qubitなので、Q#のOperationの第一引数がn個のQubit配列、第二引数が単一のQubitでこれが出力となります。 解答としては、|x>に古典関数fを施したものは結局k番目の要素を返すのでx[k]にほかならず、これとyでCNOTをとったものが答えになっています。

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

    operation Solve (x : Qubit[], y : Qubit, k : Int) : ()
    {
        body
        {
            CNOT(x[k], y);
        }
    }
}

次の問題は、入力|x> を全部足した値を2を法とした場合の剰余をもとめる問題です。

codeforces.com

全部足した値の2を法とした剰余というのが、結局XORであり、入力 |x> の各要素を順番に*1 出力 |y> とCNOTをとればよいことになります。ここは一応、前の問題がヒントになっています。

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

    operation Solve (x : Qubit[], y : Qubit, k : Int) : ()
    {
        body
        {
            for (i in 0..Length(x)-1) 
            {
                CNOT(x[i], y);
            }
        }
    }
}

ここからは本戦の問題です。この問題は直前の問題と並べるとよく似ていることに気づきます。

codeforces.com

違いは、bit配列 b が追加されていて、bと |x> との内積の和の2を法とした場合の剰余になっています。bと |x> との内積に対して、2を法とするということで、結局bit配列であるbの要素が1のときだけ入力|x>のXORをとっていればよいことになり、答えは次のようになっています。

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

    operation Solve (x : Qubit[], y : Qubit, k : Int) : ()
    {
        body
        {
            for (i in 0..Length(x)-1) 
            {
                if (b[i] == 1) 
                {
                    CNOT(x[i], y);
                }
            }
        }
    }
}

次がQuantum Oracleに関する最後の問題です。今度は b * x + (1 - b) * (1 - x) という形の内積になっています。

codeforces.com

内積の前半 b*x については直前の問題と同じなので、bit配列bの要素の値が1のときはCNOTをとればいいことになります。後半については (1-b) というのは結局bの否定なのでbが0のときに計算する必要があり(1-x) というのはx の否定になります。そのため、bが0のときは入力|x>の要素の否定=PauliX操作の結果をCNOTすればいいことになります。注意しないといけないのが、Quantum Oracleとなる操作はユニタリ操作でないといけないので、入力|x>の状態を変えてはいけません。そのためPauliX操作を行ったものは同じ操作をしてもとに戻しておく必要があります。

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

    operation Solve (x : Qubit[], y : Qubit, k : Int) : ()
    {
        body
        {
            for (i in 0..Length(x)-1) 
            {
                if (b[i] == 1) 
                {
                    CNOT(x[i], y);
                }
                else 
                {
                    // do a 0-controlled NOT
                    X(x[i]);
                    CNOT(x[i], y);
                    X(x[i]);
                }
            }
        }
    }
}

Coding Contestの問題と答えがあるので、照らし合わせてなんとかという感じでまとめました。もうちょっと具体的な問題とアルゴリズムに対するQuantum Oracleがあればよりわかるようになるかもしれません...

*1:順番自体はどうでもよく、とにかく全要素を1回ずつ適用すればよい