Q# の Operation と Function について、あれどうだっけとググってしまう私のような方向けのメモです。
TL;DR
- 量子ビットを操作するのが Operations、古典コンピューターで実行可能なのが Functions。
- Operation と Function は第一級オブジェクトである。
- Operation と Function は部分適用がサポートされる。
ドキュメント
Q# techniques - operations and functions
概要
Operations
例1
operation BitFlip(target : Qubit) : Unit {
body (...) {
X(target);
}
}
- Operation の定義は
operation
キーワードから始まる。 - 続く
BitFlip
が Operation の名前 -
<operation name> <input tuple>:<output tuple>
で記述される-
<input tuple>
は<variable name>: <type>
で記述される - Q# は入力と出力が内部的にはそれぞれ 1 つで、すべて tuple として扱われる。ので、インフォーマルには "tuple-in, tuple-out な言語" といわれる
-
- 上記の場合は
- 入力が Qubit である target
- 出力はない。
Unit
は出力がないことを示す。(F# のUnit
と同じらしい)
-
body
は Operation 内の処理を記述するために必要な宣言
例2
operation Superdense(here : Qubit, there : Qubit) : (Result, Result) {
body (...) {
CNOT(there, here);
H(there);
let firstBit = M(there);
let secondBit = M(here);
return (firstBit, secondBit);
}
}
- 上記例の場合、出力は
Result
型が二つ含まれる Tuple となる - 出力がない場合のみ Variants (Controlled とか Adjoint とか) を定義できる。
例3
operation PrepareEntangledPair(here : Qubit, there : Qubit) : Unit {
body (...) {
H(here);
CNOT(here, there);
}
adjoint auto;
controlled auto;
controlled adjoint auto;
}
- 上記の
auto
は Variants をコンパイラが自動で生成することを意味する。 - コンパイラが自動生成できない場合や効率的な Variants を手動で生成したい場合は
auto
を使わないこともできる
Functions
例1
function Square(x : Double) : (Double) {
return x * x;
}
-
body
でくくる必要がないという差異を除けばあとは大体 Operation と一緒
例2
operation U(target : Qubit) : Unit {
body (...) {
let angle = RandomReal()
Rz(angle, target)
}
}
- 乱数を取得して、取得した乱数に基づいた回転を行うオペレーションだが、このオペレーションに対してはエルミート行列が定義できない
- ので、Adjoint は定義できない、コンパイラが処理できない
- 同様に Controlled も定義できない
Operation と Function は第一級オブジェクトである
例1
operation FirstClassExample(target : Qubit) : Unit {
body (...) {
let ourH = H;
ourH(target);
}
}
- 上記はアダマール変換を行う
H
をourH
という変数に代入している
例2
operation ApplyTwice(op : (Qubit => Unit), target : Qubit) : Unit {
body (...) {
op(target);
op(target);
}
}
- 上記ではオペレーションの引数として
op
という別のオペレーション (入力は Qubit、出力なし)と Target を受け取っている
例3
function TeleporationDecoderForMessage(hereBit : Result, thereBit : Result)
: (Qubit => Unit : Adjoint, Controlled)
{
if (hereBit == Zero && thereBit == Zero) {
return I;
} elif (hereBit == One && thereBit == Zero) {
return X;
} elif (hereBit == Zero && thereBit == One) {
return Z;
} else {
return Y;
}
}
- 出力に Cotnrolled や Adjoint などの Variant を指定することもできる
Fnction や Operation の部分適用
例1
Function や Operation は部分適用が可能
operation ApplyTwice(op : (Qubit => Unit), target : Qubit) : Unit {
body (...) {
op(target);
op(target);
}
}
operation PartialApplicationExample(op : (Qubit => Unit), target : Qubit) : Unit {
body (...) {
let twiceOp = ApplyTwice(op, _);
twiceOp(target);
}
}
-
_
は部分適用のうち、未適用部分を示す - 上記例の場合、
twiseOp
というでは一つ目の引数であるOperation はOp
で固定されるが、Target は未指定なのでtwiseOp
呼び出し時に指定する
例2
function SquareOperation(op : (Qubit => Unit)) : (Qubit => Unit) {
return ApplyTwice(op, _);
}
- 上記のように部分適用した Opearation を返すこともできる