$$
\def\bra#1{\mathinner{\left\langle{#1}\right|}}
\def\ket#1{\mathinner{\left|{#1}\right\rangle}}
\def\braket#1#2{\mathinner{\left\langle{#1}\middle|#2\right\rangle}}
$$
動かしながら学ぶ量子コンピューター
のタコの本を手に入れたのでやってみた。
が本の説明通りにQ#上で表現できなかったので頑張ってみた。
基本のQPU演算(単一キュビット)
QPU演算の入出力は全てキュビットで表現され、多くのQPU演算は、対応する逆演算を持ちます。
逆演算を持つ場合、そのQPU演算は可逆である
と言われています。
「可逆である
」という事は、演算を行うときに情報が全く失われない、または、破棄されないことを意味します。
要は2回連続で同じ処理をすると元に戻りますよってことです。
Q#による可逆演算(Adjoint)
Q#では、可逆のQPU演算は以下のように作成し、呼び出すことができます。
ポイントは、<return type>
後に記載しているAdj
です。
operation <operation name>(<Args>: <Args type>) : <return type> is Adj {
...
}
Adj
を付与すると、このQPU演算は可逆
することを意味しQ#のコンパイラが可逆する演算を暗黙的に作成してくれて、以下のように呼び出すことが可能です。
use q = Qubit();
...
<operation name>(q); // 演算1
...
Adjoint <operation name>(q); // 演算1の情報をなかったことにする!
...
複雑な演算済みのキュビットに対して特定の演算部だけを無効化することができます。
しかしながら一部のQPU演算は不可逆です。
QPU命令:HAD(アダマール<Hadamard>
)
HAD演算は、$ \ket 0 $あるいは$ \ket 1 $のどちらかの状態に対して作用したときに、
大きさが等しい重ね合わせ
を生成する演算です。
この演算は可逆性です。
Q#では以下のようになります。
use q = Qubit();
H(q); // Hadamard
QPU命令:PHASE(θ)
この演算はキュビットの相対位相を直接操作し、
指定した角度だけ相対位相を変化させます。
この演算は可逆性です。
$ \ket 1 $ 状態に対応するキュビットのみに影響します。
Q#では、R1
がこの演算になるそうです。
use q = Qubit();
let theta = 180.0;
R1(theta, q); // |1>
QPU命令:NOT
この演算では、$ \ket 0 $を$ \ket 1$に、$ \ket 1$を$ \ket 0$に変換します。
重ね合わせ状態のキュビットに対しても対応しています。
この演算は可逆性です。
X(q);
QPU命令:READ
この演算はキュビットを読み出す演算です。
Q#では以下になります。
open Microsoft.Quantum.Canon;
open Microsoft.Quantum.Measurement; // for MultiM
@EntryPoint()
operation Main(): Unit {
use q = Qubit();
M(q); // 単一キュビットのREAD
use register = Qubit[3];
MultiM(register);// 複数のキュビットのREAD
...
}
多重キュビットテクニック
多重キュビット演算では、演算に複数のキュビットが必要になります。
単一キュビット演算のペア
多重キュビットの1キュビットに対して、単一演算を行うと別のキュビットにも影響があります。
以下のサンプルコードでは、多重キュビットの0番目のキュビットに対してPHASE、NOT演算を行った結果を表示しています。
@EntryPoint()
operation MultiQubitPair1() : Unit {
use qbyte = Qubit[3];
// 度からラジアン
let angle = 90.0 * PI() / 180.0;
// 全ての量子ビットに対してHadamard演算
ApplyToEach(H, qbyte);
// 単一キュビット演算)PHASE演算
R1(angle, qbyte[0]);
// PHASEの結果を表示
DumpMachine();
// |0⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |1⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |2⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |3⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |4⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |5⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |6⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |7⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
Message($"");
// 単一キュビット演算)NOT演算
X(qbyte[0]);
// NOTの結果を表示
DumpMachine();
// |0⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |1⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |2⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |3⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |4⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |5⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |6⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |7⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// 全ての量子ビット
ResetAll(qbyte);
}
PHASE演算では、0番目のキュビットの$ \ket 1 $に対して行うと1つ飛ばしで回転が加わります。
NOT演算では、0番目のキュビットに対して行うと隣同士の状態が入れ替わります。
操作対象のキュビットがずれると、操作対象のペアが変わります。
...
// 単一キュビット演算)PHASE演算
R1(angle, qbyte[1]);
// PHASEの結果を表示
DumpMachine();
// |0⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |1⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |2⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |3⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |4⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |5⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |6⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |7⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
...
// 単一キュビット演算)NOT演算
X(qbyte[1]);
// NOTの結果を表示
DumpMachine();
// |0⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |1⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |2⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |3⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |4⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |5⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |6⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |7⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
...
R1(angle, qbyte[2]); // qbitの|1>に対してPhaseする
DumpMachine();
// |0⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |1⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |2⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |3⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |4⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |5⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |6⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |7⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
Message($"");
X(qbyte[2]);
DumpMachine();
// |0⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |1⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |2⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |3⟩ 0.000000 + 0.353553 i == *** [ 0.125000 ] ↑ [ 1.57080 rad ]
// |4⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |5⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |6⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |7⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
CNOT
この演算は、制御キュビットとターゲットキュビットが必要になります。
制御キュビットがOneになると、ターゲットキュビットにNOT演算を行います。
この演算は可逆です。
Q#では、以下のようになります。
@EntryPoint()
operation ControlledNot(): Unit {
use qbyte = Qubit[2];
// qbyte[0] = ONE
X(qbyte[0]);
CNOT(qbyte[0], qbyte[1]);
let result = MultiM(qbyte);
Message($"{result}");
//[One,One,Zero]
ResetAll(qbyte);
...
上記のように最初から用意している演算を使用しても良いですが、せっかくQ#を使用しているので
Q#特有の機能を利用したCNOT演算を行おうと思います。
Q#による制御付き演算(Controlled)
以下のように演算を定義すると、制御付き演算として呼び出すことが可能になります。
// 条件付き演算呼び出し可能な定義
operation <operation name>(<Args>: <Args type>) : <return type> is Ctl {
...
}
...
@EntryPoint()
operation Main(): Unit {
...
// 条件付き演算の呼び出し
Controlled <operation name>([<controll qubit list>], (Args));
...
}
上記の例を参考にCNOTをサンプルコードを作成しました。
こちらも同じような結果が得られます。
// 可逆が可能な演算の為Adjも加える
operation NOT(q: Qubit): Unit is Adj + Ctl {
X(q);
}
@EntryPoint()
operation ControlledNot2(): Unit {
use qbyte = Qubit[2];
X(qbyte[0]);
// qbyte[0]がOneの時、NOT演算を行う
Controlled NOT([qbyte[0]], (qbyte[1]));
let result = MultiM(qbyte);
Message($"{result}");
// [One,One]
ResetAll(qbyte);
}
ベルペア
量子コンピューターでは、量子複製不可能(No-Cloning)定理によってコピーができません。
量子ビットの状態の複製はできませんが、
任意の量子ビットのランダムさを別の量子ビットに共有することができます。
この辺は、とても深いみたいなのでご自身で調べてください。
operation BellPaire(): Unit {
// a, b = Zero, Zero
use (a, b) = (Qubit(), Qubit());
// ベルペア
H(a);
CNOT(a, b); // Entabgle
let result = MultiM([a, b]);
Message($"{result}");
ResetAll([a, b]);
}
上記のサンプルコードでは、以下のケースになります。
- 量子ビットaがOneの時、量子ビットbがOneになります。
- 量子ビットaがZeroの時、量子ビットbがZeroになります。
つまり、aとbで得られる値は(Zero, Zero)もしは(One, One)のみになります。
CPHASE
この演算は、条件付きPHASE演算です。
制御キュビットがOneの時、ターゲットキュビットに対するPHASE演算を行います。
単一キュビット演算のペアで説明した影響する量子ビットの数が条件を付けると、半数になります。
operation CPhase(): Unit {
use qbyte = Qubit[3];
ApplyToEach(H, qbyte);
let control = qbyte[2..2];
let target = qbyte[0];
let angle = DegToRad(36.0);
Message($"controlled {control}");
Message($"target {target}");
// CPHASE
Controlled R1(control, (angle, target));
// |0⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |1⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |2⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |3⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |4⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |5⟩ 0.286031 + 0.207813 i == *** [ 0.125000 ] / [ 0.62832 rad ]
// |6⟩ 0.353553 + 0.000000 i == *** [ 0.125000 ] --- [ 0.00000 rad ]
// |7⟩ 0.286031 + 0.207813 i == *** [ 0.125000 ] / [ 0.62832 rad ]
DumpMachine();
ResetAll(qbyte);
}
位相キックバック
後で書く。。。もう。。。眠い
@EntryPoint()
operation PhaseKickBack(): Unit {
let (angle1, angle2) = (DegToRad(45.0), DegToRad(90.0));
use (register1, register2) = (Qubit[2], Qubit());
ApplyToEach(H, register1);
// |1>で初期化
X(register2);
Controlled R1(register1[0..0], (angle1, register2));
Controlled R1(register1[1..1], (angle2, register2));
DumpRegister((), register1);
// |0⟩ 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
// |1⟩ 0.353553 + 0.353553 i == ***** [ 0.250000 ] / [ 0.78540 rad ]
// |2⟩ 0.000000 + 0.500000 i == ***** [ 0.250000 ] ↑ [ 1.57080 rad ]
// |3⟩ -0.353553 + 0.353553 i == ***** [ 0.250000 ] \ [ 2.35619 rad ]
Message("======");
DumpRegister((), [register2]);
// |0⟩ 0.000000 + 0.000000 i == [ 0.000000 ]
// |1⟩ 1.000000 + 0.000000 i == ******************** [ 1.000000 ] --- [ 0.00000 rad ]
ResetAll(register1);
Reset(register2);
}