Clifford演算子

Clifford は演算子を行列ではなくスタビライザ タブロー( X_0..X_{n-1} と Z_0..Z_{n-1} の共役による像)として保持 します。そのため合成と逆元がビット演算だけで厳密に計算でき、 2**n の 配列はどこにも現れません。

from blueqat import Circuit
from blueqat.clifford import Clifford, random_clifford

c = Clifford.from_circuit(Circuit(2).h[0].cx[0, 1])
c.to_circuit()          # ゲートへ戻す: h, s, sdg, cx, x, z
c.inverse()
c.then(other)           # c を先に、そのあと other

Cliffordゲート集合は i ・ x ・ y ・ z ・ h ・ s ・ sdg ・ sx ・ sxdg ・ cx ・ cy ・ cz ・ swap です。 それ以外( t や rx など)は黙って近似せずエラーにします。 大域位相は追跡しません(観測できず、ベンチマークで使うCliffford群は位相を 除いて定義されるためです)。

一様ランダムなClifford

random_clifford(2, seed=0)     # 2量子ビットの11520個から一様に

一様性は、シンプレクティック基底を共役ペアごとに作ることで得られます。 X_i の像はまだ使える非恒等パウリから一様に、 Z_i の像はそれと反交換 するものから一様に、残りは両方と交換するものから選びます。この選択肢の数を 掛け合わせるとちょうど |Sp(2n, 2)| になり、残りのパウリ因子は 2n 個の 独立な符号ビットが与えます。 seed は専用の generator を使うので、 グローバルな乱数には影響しません。

ランダム化ベンチマーキング

タブローが要る理由がこれです。ベンチマークの系列は、それ以前を全部打ち消す たった1つのClifffordで終わらなければなりません。系列を逆順に流し直すと 長さが約2倍になり、別のものを測ることになります。

from blueqat import Circuit
from blueqat.clifford import Clifford, random_clifford
from blueqat.noise import depolarizing

def rb_circuit(n, m, seed):
    total = Clifford.identity(n)
    circuit = Circuit(n)
    for i in range(m):
        c = random_clifford(n, seed=seed * 1000 + i)
        circuit += c.to_circuit()
        total = total.then(c)
    return circuit + total.inverse().to_circuit()

# 生存確率: 雑音がなければ厳密に1、雑音があると減衰する
rho = rb_circuit(1, 16, seed=0).run(noise=depolarizing(0.02))
survival = float(rho[0, 0].real)

survival を系列長 m に対してフィッティングする部分はベンチマークの 解析そのものなので、SDKではなく実験を行う側に置いてあります。