銀の光と碧い空

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

Q# プロジェクトの構造を調べてみる

ひとりAdvent Calendar 10日目です。

adventar.org

この資料などにも書いている内容なのですが、Quantum Development Kitで作成するQ#のプロジェクト構造についてまとめてみたいと思います。

www.slideshare.net

対象バージョンはVersion 0.3.1811.2802ですが、内容としては0.3.n全体で変更はないと思います。直近ではSDK本体の変更ではありませんが、VS Code拡張でevent streamパッケージの脆弱性に基づく対応でSDK全体が更新されています。

Quantum Development Kit preview release notes | Microsoft Docs

Q# のコンパイラはどのパスにインストールされているか

Q#で書かれたコードが実行されるということは、なにかしらのコンパイラーが動いていることが想像されます。.NET Coreのdotnetコマンドや、MSBuildコマンドを実行するバイナリのように、Q#をコンパイルするバイナリがインストールフォルダにインストールされている気もするかもしれませんが、インストール手順を見ると、.NET Coreのテンプレートを追加していることがわかります。

$ dotnet new -i "Microsoft.Quantum.ProjectTemplates::0.3.1811.2802-preview"

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

つまりこの手順ではコンパイラーに相当するものはまだマシンに追加されていません。Q#の開発は、次にQ#のプロジェクトを作るとすぐに始めることができます。というわけでプロジェクトを作成してみます。

$  dotnet new Console -lang Q# --output QSharp

このコマンドも.NET Coreでプロジェクトを作成する標準のコマンドです。作成されたプロジェクトファイル({プロジェクト名}.csproj}を見てみましょう。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Quantum.Canon" Version="0.3.1811.2802-preview" />
    <PackageReference Include="Microsoft.Quantum.Development.Kit" Version="0.3.1811.2802-preview" />
  </ItemGroup>

</Project>

<ItemGroup>を除けば、netcoreapp2.0 をターゲットフレームワークとする、プロジェクト単体で実行可能な.NET Core Consoleプロジェクトです。<ItemGroup>についても、NuGetからライブラリを追加する場合の標準的な書き方です。つまり、Q#のプロジェクトというのは、プロジェクトとしては.NET Coreのプロジェクトそのものであり、Q#のコンパイラーやそのほかのライブラリも含めNuGet経由でプロジェクトに追加しているという形態になっています。

www.nuget.org

www.nuget.org

そのため、Quantum Development Kitのアップグレードの際には、.NET Coreのプロジェクトテンプレートを更新することでこれから新規に作成するプロジェクトに追加するライブラリのバージョンを上げる作業のほかに、既存のプロジェクトで参照しているNuGetライブラリのバージョンを上げる作業が必要になります。

QDK 0.3 Language Review and Migration Guide | Microsoft Docs

Q#コードのコンパイルの仕組み

.qsファイルに書くQ#コードは、dotnet buildでコンパイルされ、dotnet runで実行することができます。プロジェクトが.NET Coreプロジェクトですし、C#コードからQ#のoperationを参照できるので、ILなどにコンパイルされているような気がしますが、実はC#コードにコンパイル*1されています。

dotnet buildしたあとに、プロジェクトフォルダの下にobj/qsharp/Operations.g.csというファイルが生成されているのがわかります。

f:id:tanaka733:20181208220404p:plain

空のOperation一つのQ#コードだと次のようなC#コードが生成されています。このコードを見ると、C#から実行するときにoperationの名前(この場合はHelloQ)がついたクラスのRunメソッドを実行することでQ#コードが実行されることや、Q#コードを実行する場所に渡すためのDriverはIOperationFactoryを実装していることがわかります。

#pragma warning disable 1591
using System;
using Microsoft.Quantum.Core;
using Microsoft.Quantum.Primitive;
using Microsoft.Quantum.Simulation.Core;

[assembly: Microsoft.Quantum.QsCompiler.Attributes.CallableDeclaration("{\"Kind\":{\"Case\":\"Operation\"},\"QualifiedName\":{\"Namespace\":\"QSharp\",\"Name\":\"HelloQ\"},\"SourceFile\":\"C:/Users/TanakaTakayoshi/source/BellTest/QSharp/Operations.qs\",\"Position\":{\"Item1\":5,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":11},\"Item2\":{\"Line\":1,\"Column\":17}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UnitType\"},\"ReturnType\":{\"Case\":\"UnitType\"},\"SupportedFunctors\":[]},\"Documentation\":[]}")]
[assembly: Microsoft.Quantum.QsCompiler.Attributes.SpecializationDeclaration("{\"Kind\":{\"Case\":\"QsBody\"},\"Parent\":{\"Namespace\":\"QSharp\",\"Name\":\"HelloQ\"},\"SourceFile\":\"C:/Users/TanakaTakayoshi/source/BellTest/QSharp/Operations.qs\",\"Position\":{\"Item1\":5,\"Item2\":4},\"HeaderRange\":{\"Item1\":{\"Line\":1,\"Column\":11},\"Item2\":{\"Line\":1,\"Column\":17}},\"Documentation\":[]}")]
#line hidden
namespace QSharp
{
    public class HelloQ : Operation<QVoid, QVoid>, ICallable
    {
        public HelloQ(IOperationFactory m) : base(m)
        {
        }

        String ICallable.Name => "HelloQ";
        String ICallable.FullName => "QSharp.HelloQ";
        protected ICallable<String, QVoid> Message
        {
            get;
            set;
        }

        public override Func<QVoid, QVoid> Body => (__in) =>
        {
#line 7 "C:\\Users\\TanakaTakayoshi\\source\\BellTest\\QSharp\\Operations.qs"
            Message.Apply("Hello quantum world!");
#line hidden
            return QVoid.Instance;
        }

        ;
        public override void Init()
        {
            this.Message = this.Factory.Get<ICallable<String, QVoid>>(typeof(Microsoft.Quantum.Primitive.Message));
        }

        public override IApplyData __dataIn(QVoid data) => data;
        public override IApplyData __dataOut(QVoid data) => data;
        public static System.Threading.Tasks.Task<QVoid> Run(IOperationFactory __m__)
        {
            return __m__.Run<HelloQ, QVoid, QVoid>(QVoid.Instance);
        }
    }
}

また、qsc-command.txtqsc-ok.txtといういかにもそれっぽい名前のファイルが生成されており、前者がコンパイルした際の入力ファイル、参照ライブラリ、実行コマンドを記録しており、後者が結果を記録しているように見えます。

:: files ::
Operations.qs
:: qsim ::
:: references ::
C:\Users\User\.nuget\packages\microsoft.quantum.canon\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Canon.dll
C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Primitives.dll
C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.QsCompilerCommon.dll
C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Simulation.Common.dll
C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Simulation.Core.dll
C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.dll
C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Simulation.Simulators.dll
:: command ::
dotnet "C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\build\../tools/qsc/qsc.dll" build --format MsBuild  --input "Operations.qs" --references "C:\Users\User\.nuget\packages\microsoft.quantum.canon\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Canon.dll" "C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Primitives.dll" "C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.QsCompilerCommon.dll" "C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Simulation.Common.dll" "C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Simulation.Core.dll" "C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.dll" "C:\Users\User\.nuget\packages\microsoft.quantum.development.kit\0.3.1811.2802-preview\lib\netstandard2.0\Microsoft.Quantum.Simulation.Simulators.dll" --output obj\qsharp\src\
qsc completed succesfully!

dotnet コマンドでビルドしているので、おそらくqsc.dllというバイナリの中に以下の記事で紹介されているようなMSBuild Taskが定義されていて、dotnet buildによりトリガーされて実行されているのではないかと想像されます。qsc.dllを含んだQuantum Development Kitのコードは今のところ公開されていないので、今調べることができるのはここまでということになります。

natemcmaster.com

まとめ

この記事で書いた内容は特に仕様とかで決まっているものではないので、今後のリリースで変更される可能性は十分にあります。ただ今のところ、.NET Coreで用意されているテンプレートやMSBuild Taskの拡張など標準的な技術で提供されているのは興味深いです。Q#の言語紹介にも書いてある通り、ドメイン特化していて、古典的なコンピューターから実行されるサブルーチン的な処理を行うための言語とあるので、C#をはじめとした言語から実行される仕組みというのは、当面変わらないだろうと予想しています。

docs.microsoft.com

*1:トランスパイルのほうがより正確かもしれませんが、今回はコンパイルという表現に統一しておきます