Skip to main content
IBM Quantum Platform

M3 を使用したSamplerプリミティブの読み出しエラー軽減

使用時間の見積もり:Heron r2 プロセッサーで1分未満(注:これはあくまでも見積もりです。 ランタイムは異なるかもしれない)。


背景

Estimatorプリミティブとは異なり、Samplerプリミティブにはエラー緩和のサポートが組み込まれていない。 Estimatorでサポートされているいくつかのメソッドは、期待値用に特別に設計されているため、Samplerプリミティブには適用できません。 例外は読み出しエラーの軽減で、これはサンプラー・プリミティブにも適用できる非常に効果的な方法である。

M3 Qiskitアドオンは、読み出しエラーを軽減する効率的な方法を実装している。 このチュートリアルでは、 M3 Qiskitアドオンを使用してSamplerプリミティブの読み出しエラーを軽減する方法を説明します。

リードアウトエラーとは何ですか?

測定の直前、量子ビット・レジスタの状態は計算基底状態の重ね合わせによって記述される。 計算基底状態の重ね合わせによって記述される、 または密度行列によって記述されます。 次に、量子ビット・レジスタを古典ビット・レジスタに変換する測定は、2つのステップで行われる。 まず、適切な量子測定が行われる。 つまり、量子ビット・レジスタの状態は の状態が単一の基底状態に投影されることを意味する。 11 00 の文字列によって特徴づけられる。 第二のステップは、この基底状態を特徴づけるビット列を読み出し を特徴づけるビット列を読み出し、古典的なコンピュータのメモリに書き込む。 これをステップ・ リードアウトと呼ぶ。 第2段階(読み出し)は、第1段階(基底状態への投影)よりも誤差が大きいことが判明した。 このことは、読み出しが微視的な量子状態を検出し、それを巨視的な領域まで増幅する必要があることを思い起こせば理解できる。 量子状態を検出し、それを巨視的な領域まで増幅する必要がある。 読み出し共振器は 読み出し共振器は(トランスモン)量子ビットに結合され、それによって非常に小さな周波数シフトを経験する。 マイクロ波パルス マイクロ波パルスが共振器で跳ね返され、共振器の特性がわずかに変化する。 特性が変化する。 反射されたパルスは増幅され、分析される。 これはデリケートな これはデリケートなプロセスであり、さまざまなエラーの可能性がある。

重要な点は、量子測定も読み出しも誤差の影響を受けるが、後者は読み出し誤差と呼ばれる支配的な誤差を生じるということである。 このチュートリアルでは読み出し誤差に焦点を当てる。

理論的背景

サンプリングされたビット列(古典メモリに格納されている)が、投影された量子状態を特徴付けるビット列と異なる場合、読み出しエラーが発生したと言う。 が異なる場合、読み出しエラーが発生したと言う。 これらの誤差は、標本ごとにランダムで無相関であることが観察されている。 読み出し誤差をノイズの多い古典的なチャンネルとしてモデル化することは有用であることが証明されている。 つまり iijj のビット列には、 jj の真値が と誤読される確率がある。 が ii と誤読される確率は一定である。

より正確には、ビット列の各対 (i,j)(i, j) に対して、(条件付き)確率が存在する。 Mi,j{M}_{i,j} であるとすると、 ii が読まれる確率は j.j. つまり

Mi,j=Pr(readout value is itrue value is j) for i,j(0,...,2n1),(1) {M}_{i,j} = \Pr(\text{readout value is } i | \text{true value is } j) \text{ for } i,j \in (0,...,2^n - 1), \tag{1}

ここで、 nn は読み出しレジスタのビット数である。 具体的に説明するために、 ii を10進整数と仮定する。 である10進整数であると仮定する。 2n×2n2^n \times 2^n 行列 M{M}割り当て行列と呼ぶ。 固定された真の値 jj に対して、すべてのノイジーな結果 ii の確率を合計すると、 11 が得られるはずである。つまり

i=02n1Mi,j=1 for all j \sum_{i=0}^{2^n - 1} {M}_{i,j} = 1 \text{ for all } j

(1)を満たす負のエントリーを持たない行列は、左確率的行列と呼ばれる。 左ストキャスティックと呼ぶ。 左ストキャスティック行列は、各列の和が 11 になることから、 列ストキャスティック行列とも呼ばれる。 Mi,j{M}_{i,j} 各要素の近似値を実験的に決定する。 各基底状態 j|j \rangle を繰り返し準備し、サンプリングされたビット列の出現頻度を計算する。 を計算することで、各要素の近似値を求める。

ある実験が、繰り返しサンプリングによって出力ビット列上の確率分布を推定することを含む場合、、 であれば、 M{M} を使って分布のレベルでの読み出し誤差を軽減することができる。 最初のステップは、対象の固定回路を何度も繰り返すことである、 サンプリングされたビット列のヒストグラムを作成する。 正規化されたヒストグラムは、以下のようなビット列の確率分布の測定値である。 2n2^n 可能なビット列を p~R2n{\tilde{p}} \in \mathbb{R}^{2^n} で表す。 をサンプリングする(推定)確率 p~i{{\tilde{p}}}_i は、すべての真のビットストリング に対する和に等しい。 ii をサンプリングする(推定確率) jj は、すべての真のビットストリング の合計に等しく、それぞれは次のように重み付けされる。 ii と間違われる確率で重み付けされる。 このステートメントを行列形式にすると

p~=Mp,,(2) {\tilde{p}} = {M} {\vec{p}}, \tag{2},

ここで、 p{\vec{p}} は真の分布である。 言い換えれば、読み出しエラーは、ビット列に対する理想的な分布 に代入行列 を乗じる効果を持つ。 p{\vec{p}}、割り当て行列 M{M}。 観測された分布 p~{\tilde{p}}。 我々は p~{\tilde{p}}M{M} を測定したが、 p{\vec{p}} には直接アクセスできなかった。 の式(2)を解くことで、回路のビット列の真の分布を得ることができます。 p{\vec{p}} について式(2)を数値的に解くことで、回路のビット列の真の分布を得ることができます。

次に進む前に、この素朴なアプローチの重要な特徴をいくつか挙げておこう。

  • 実際には、式(2)は M{M} を反転しても解けない。線形代数 ソフトウェア・ライブラリの線形代数ルーチンは、より安定で、正確で、効率的な方法を採用している。
  • M{M} を推定する際には、読み出しエラーのみが発生すると仮定した。 特に 状態の準備や量子測定の誤差がなかったと仮定する。 あるいは、少なくともそれらが軽減されていると仮定する。 これが正しい仮定である限り、 M{M}。 読み出しエラーだけである。 しかし、 M{M} を使う場合は、そのような仮定はしない。 実際、興味深い回路は、例えばゲートエラーなど 例えばゲート・エラーなどである。 真の」分布 には、他の方法で軽減されなかったエラーの影響も含まれる。

この方法は、状況によっては有効だが、いくつかの制限がある。

M{M} を推定するために必要な空間と時間のリソースは、 nn において指数関数的に増大する:

  • M{M}p~{\tilde{p}} の推定は、サンプリングが有限であるため、統計的誤差の影響を受ける。 このノイズは望むだけ小さくすることができる このノイズは、より多くのショットを犠牲にすることで小さくすることができる。 M{M} の系統的誤差をもたらすタイムスケールまで)。 しかし、ミティゲーションを行う際に観測されるビット列に仮定を設けない場合 を推定するために必要なショット数は、 M{M} において少なくとも指数関数的に増加する。 nn は少なくとも指数関数的に増加する。
  • M{M}2n×2n2^n \times 2^n 行列である。 n>10n>10 の場合、 M{M} を保存するのに必要なメモリの量は、高性能なノートパソコンで使用できるメモリよりも多い。 強力なノートパソコンで利用可能なメモリよりも大きい。

さらに制限事項がある:

  • 回収された分布 p{\vec{p}} は、1つ以上の負の確率を持つ可能性がある。 またはそれ以上の負の確率を持つかもしれない(それでも合計は1になる)。 ひとつの解は Mpp~2||{M} {\vec{p}} - {\tilde{p}}||^2 を最小化することである。 p{\vec{p}} の各エントリーが負でないこと。 しかし、このような方法の実行時間は の実行時間は、式(2)を直接解くよりも桁違いに長くなる。
  • この緩和手続きは、ビット列上の確率分布のレベルで機能する。 レベルで動作する。 特に、観測された個々のビット列のエラーを訂正することはできない。 の誤りを訂正することはできない。

Qiskit M3 アドオン: より長いビット列への拡張

標準的な数値線形代数ルーチンを使って式(2)を解くのは、約10ビット以下のビット列に限られる。 M3 しかし、もっと長いビット列も扱える。 これを可能にする M3 の2つの重要な特性は以下の通りである:

  • ビットのコレクション間の3次以上の読み出し誤差の相関は無視できると仮定され、無視される。 は無視できると仮定され、無視される。 原理的には、より多くのショットを犠牲にすることになる、 より高い相関を推定することもできる。
  • M{M} を明示的に構築するのではなく、 を構築する際に収集したビット列のみを記録する、はるかに小さな有効行列を使用する。 p~{\tilde{p}} を構築する際に収集したビット列の確率のみを記録する、より小さな有効行列を使用する。

大まかな手順は以下の通り。

まず、 M{M} の簡略化された効果的な記述を構築するための構成ブロックを構築する。 次に、関心のある回路を繰り返し実行し、ビット列を収集する。 p~{\tilde{p}} と、ビルディング・ブロックの助けを借りて効果的な M{M} を構築するために使用します。

より正確には

  • 単一量子ビットの割り当て行列は、それぞれの量子ビットについて推定される。 そのためには、次のことを繰り返す。 0...0|0 ... 0 \rangle を繰り返し準備する。 状態 1...1|1 ... 1 \rangle の準ビット・レジスタを繰り返し準備し、各準ビットが誤って読み取られる確率を記録する。 を記録する。

  • 次以上の相関は無視できると仮定し、無視する。

    その代わりに、 2×22 \times 2 の数 nn と、 の2量子ビット割り当て行列の数 を構築する。 割り当て行列の数 n(n1)/2n(n-1)/24×44 \times 4 2量子ビット割り当て行列の数 行列を構築する。 これらの1量子ビットと2量子ビットの割り当て行列は、後で使うために保存される。 使われる。

  • 回路を繰り返しサンプリングし、 p~{\tilde{p}} を構築した後、 の効果的な近似を構築する、 を構築するために回路を繰り返しサンプリングした後、 M{M} の効果的な近似を構築する。 p~{\tilde{p}} の有効近似を構築する。この有効行列 この有効行列は、前項目で説明した1量子ビット行列と2量子ビット行列を用いて構築される。 この行列の線形次元は最大でも、 の構築に使われたショット数のオーダーである。 p~{\tilde{p}} のオーダーである。 完全割り当て行列 M{M} の次元 2n2^n よりもはるかに小さい。

M3 の技術的な詳細については、 Scalable Mitigation of Measurement Errors on Quantum Computers を参照されたい。

M3 の量子アルゴリズムへの応用

M3 の読み出し緩和策を隠れシフト問題に適用する。 隠れシフト問題や、 隠れ部分群問題などの密接に関連する問題は、もともとフォールト・トレラントな設定で考えられたものである(より正確には、フォールト・トレラントなQPUが可能であると証明される前に!)。 しかし、それらは利用可能なプロセッサーでも研究されている。 127qubitの IBM® QPUで得られた隠れシフト問題の変形に対して得られたアルゴリズムの指数関数的スピードアップの例は、 この論文 ( arXiv バージョン )で見ることができる。

以下では、すべての演算はブール演算である。 すなわち、 a,bZ2={0,1}a, b \in \mathbb{Z}_2 = \{0, 1\} (加算)の場合、 a+ba + b (加算)は論理XOR関数である。 さらに、乗算 a×ba \times b (または aba b )は論理AND関数である。 x,y{0,1}nx, y \in \{0, 1\}^nx+yx + y はXORのビット単位の適用によって定義される。 ドット積 :Z2nZ2\cdot: {\mathbb{Z}_2^n} \rightarrow \mathbb{Z}_2 は次のように定義される。 xy=ixiyix \cdot y = \sum_i x_i y_i で定義される。

ハダマール演算子とフーリエ変換

量子アルゴリズムの実装では、ハダマード演算子をフーリエ変換として使うのが一般的だ。 計算基礎状態は古典的状態と呼ばれることもある。 これらは と一対一の関係にある。 nn -qubitのハダマード演算子は、ブール超立方体上のフーリエ変換と見なすことができる:

Hn=12nx,yZ2n(1)xyyx.H^{\otimes n} = \frac{1}{\sqrt{2^n}} \sum_{x,y \in {\mathbb{Z}_2^n}} (-1)^{x \cdot y} {|{y}\rangle}{\langle{x}|}.

固定ビット列 ss に対応する状態 s{|{s}\rangle} を考える。 HnH^{\otimes n} を適用し、 xs=δx,s{\langle {x}|{s}\rangle} = \delta_{x,s}s{|{s}\rangle} のフーリエ変換は次のように書ける。

Hns=12nyZ2n(1)syy. H^{\otimes n} {|{s}\rangle} = \frac{1}{\sqrt{2^n}} \sum_{y \in {\mathbb{Z}_2^n}} (-1)^{s \cdot y} {|{y}\rangle}.

ハダマードはそれ自身の逆数である、 HnHn=(HH)n=InH^{\otimes n} H^{\otimes n} = (H H)^{\otimes n} = I^{\otimes n}. したがって、逆フーリエ変換も同じ演算子であり、 HnH^{\otimes n}。 明示的には、次のようになる、

s=HnHns=Hn12nyZ2n(1)syy. {|{s}\rangle} = H^{\otimes n} H^{\otimes n} {|{s}\rangle} = H^{\otimes n} \frac{1}{\sqrt{2^n}} \sum_{y \in {\mathbb{Z}_2^n}} (-1)^{s \cdot y} {|{y}\rangle}.

隠れたシフト問題

隠しシフト問題の簡単な例を考える。 問題は、ある関数への入力に一定のずれがあることを特定することである。 ここで考える関数はドット積である。 これは この関数は、以下に示すような手法により、隠しシフト問題の量子的高速化を可能にする この関数は、以下に示すようなテクニックによって、隠しシフト問題の量子スピードアップを可能にする関数群の中で最も単純なものである。

x,yZ2mx,y \in {\mathbb{Z}_2^m} を長さ mm のビット列とする。 f:Z2m×Z2m{1,1}{f}: {\mathbb{Z}_2^m} \times {\mathbb{Z}_2^m} \rightarrow \{-1,1\} を次のように定義する。

f(x,y)=(1)xy. {f}(x, y) = (-1)^{x \cdot y}.

a,bZ2ma,b \in {\mathbb{Z}_2^m} を長さ mm の固定ビット列とする。 さらに、 g:Z2m×Z2m{1,1}g: {\mathbb{Z}_2^m} \times {\mathbb{Z}_2^m} \rightarrow \{-1,1\} を次のように定義する。

g(x,y)=f(x+a,y+b)=(1)(x+a)(y+b), g(x, y) = {f}(x+a, y+b) = (-1)^{(x+a) \cdot (y+b)},

ここで、 aabb は(隠し)パラメータである。 一方は ff を実装したもの、もう一方は gg を実装したものである。 私たちは、それらが上で定義された関数を計算することを知っていると仮定する。 aabb も知らない。ゲームは、隠されたビット列(シフト)を決定することである。 aabb を、 ffgg に問い合わせることによって決定することである、 O(2m)O(2m) aabb。たとえば、 gg に、ペアの一方の要素がすべてゼロで、もう一方の要素が 11 に設定された要素をちょうど1つ持つような文字列のペアをすべて問い合わせることができる。 各クエリで、 aa または bb のどちらかの要素を1つ学習する。 しかし、ブラックボックスを量子回路として実装すれば、次のことがわかる。 aa bb ff、。 gg

アルゴリズムの複雑さの文脈では、ブラックボックスをオラクルと呼ぶ。 不透明であることに加え、オラクルは入力を消費し、即座に出力を生成するという特性を持つ。 即座に出力を生成するという性質があり、それが組み込まれたアルゴリズム の複雑さバジェットに何も追加しない。 実際、今回のケースでは、 ff を実装したオラクルと gg を実装したオラクルが効率的であることがわかるだろう。

ff および gg 向けの量子回路

ffgg を量子回路として実装するためには、以下の材料が必要である。

単一量子ビット古典状態 x1,y1{|{x_1}\rangle}, {|{y_1}\rangle} に対して、 x1,y1Z2x_1,y_1 \in \mathbb{Z}_2ZZ ゲート CZ{CZ} は次のように書ける。

CZx1y1x1=(1)x1y1x1x1y1.{CZ} {|{x_1}\rangle}{|{y_1}\rangle}{x_1} = (-1)^{x_1 y_1} {|{x_1}\rangle}{x_1}{|{y_1}\rangle}.

mm の CZ ゲート、 (x1,y1)(x_1, y_1) のゲート、 (x2,y2)(x_2, y_2) のゲートなど、 (xm,ym)(x_m, y_m) を通して操作する。 この演算子を CZx,y{CZ}_{x,y} と呼ぶ。

Uf=CZx,yU_f = {CZ}_{x,y} は、 f=f(x,y){f} = {f}(x,y) の量子版である:

Ufxy=CZx,yxy=(1)xyxy.%\CZ_{x,y} {|#1\rangle}{z} = U_f {|{x}\rangle}{|{y}\rangle} = {CZ}_{x,y} {|{x}\rangle}{|{y}\rangle} = (-1)^{x \cdot y} {|{x}\rangle}{|{y}\rangle}.

ビット列のシフトも実装する必要がある。 ここでは、 xx レジスタ Xa1XamX^{a_1}\cdots X^{a_m} 上の演算子を XaX_a とし、同様に yy レジスタ Xb=Xb1XbmX_b = X^{b_1}\cdots X^{b_m}。 これらの演算子は、1ビットが 11 であればどこでも XX を適用し、 00 であればどこでもアイデンティティ II を適用する。 そして次のようになる。

XaXbxy=x+ay+b. X_a X_b {|{x}\rangle}{|{y}\rangle} = {|{x+a}\rangle}{|{y+b}\rangle}.

第二のブラックボックス gg は、次の式で与えられるユニタリー UgU_g によって実装される

Ug=XaXbCZx,yXaXb.%U_g {|{x}\rangle}{|{y}\rangle} = X_aX_b \CZ_{x,y} X_aX_b {|{x}\rangle}{|{y}\rangle}. U_g = X_aX_b {CZ}_{x,y} X_aX_b.

これを見るために、右から左へ演算子を状態 xy{|{x}\rangle}{|{y}\rangle} に適用する。 最初に

XaXbxy=x+ay+b. X_a X_b {|{x}\rangle}{|{y}\rangle} = {|{x+a}\rangle}{|{y+b}\rangle}.

それからだ、

CZx,yx+ay+b=(1)(x+a)(y+b)x+ay+b. {CZ}_{x,y} {|{x+a}\rangle}{|{y+b}\rangle} = (-1)^{(x+a)\cdot (y+b)} {|{x+a}\rangle}{|{y+b}\rangle}.

最後に、

XaXb(1)(x+a)(y+b)x+ay+b=(1)(x+a)(y+b)xy, X^a X^b (-1)^{(x+a)\cdot (y+b)} {|{x+a}\rangle}{|{y+b}\rangle} = (-1)^{(x+a)\cdot (y+b)} {|{x}\rangle}{|{y}\rangle},

これはまさに、 f(x+a,y+b)f(x+a, y+b) の量子版である。

隠されたシフトアルゴリズム

これで、隠れたシフト問題を解決するためのピースが揃った。 オールゼロの状態に初期化されたレジスタにハダマードを適用することから始める。

H2m=HmHm0m0m=122mx,yZ2m(1)xyxy.H^{\otimes 2m} = H^{\otimes m} \otimes H^{\otimes m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} = \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{x \cdot y} {|{x}\rangle}{|{y}\rangle}.

次に、オラクル gg

UgH2m0m0m=122mx,yZ2m(1)(x+a)(y+b)xyU_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} = \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{(x+a) \cdot (y+b)} {|{x}\rangle}{|{y}\rangle} 122mx,yZ2m(1)xy+xb+yaxy.\approx \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{x \cdot y + x \cdot b + y \cdot a} {|{x}\rangle}{|{y}\rangle}.

最後の行では、一定のグローバル位相係数 (1)ab(-1)^{a \cdot b} を省略した、 を省略し、ある位相までの等式を \approx で表す。 次に、オラクル ff を適用すると、 (1)xy(-1)^{x \cdot y} の別のファクターが導入され、すでに存在するファクターがキャンセルされる。 が存在する。 そして、こうなる:

UfUgH2m0m0m122mx,yZ2m(1)xb+yaxy.U_f U_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} \approx \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{x \cdot b + y \cdot a} {|{x}\rangle}{|{y}\rangle}.

最後のステップは、逆フーリエ変換( H2m=HmHmH^{\otimes 2m} = H^{\otimes m} \otimes H^{\otimes m} )を適用することである、 その結果

H2mUfUgH2m0m0mba.H^{\otimes 2m} U_f U_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} \approx {|{b}\rangle}{|{a}\rangle}.

回路は完成した。 ノイズがない場合、量子レジスタをサンプリングすると、以下のようになる。 ビット列 b,ab, a を確率 11 で返す。

ブール内積は、いわゆるベント関数の一例である。 ここではベント関数を定義しない。 単に "入力のある線形部分空間に対する出力の依存性を利用しようとする攻撃に対して最大限の抵抗力を持つ 出力が入力のある線形部分空間に依存することを利用しようとする攻撃に対して最大限の耐性を持つ。" この引用は、論文「 Quantum algorithms for highly non-linear Boolean functions 」からのものである。 いくつかのクラスの屈曲関数に対する効率的な隠れシフトアルゴリズムを与えている。 このチュートリアルのアルゴリズムはセクション 3.1。

より一般的なケースでは、隠れたシフトを見つけるための回路 sZns \in \mathbb{Z}^n

HnUf~HnUgHn0n=s. H^{\otimes n} U_{\tilde{f}} H^{\otimes n} U_g H^{\otimes n} {|{0}\rangle}^{\otimes n} = {|{s}\rangle}.

一般的なケースでは、 ffgg は単一変数の関数である。 内積の例は、 f(x,y)f(z)f(x, y) \to f(z) とするとこのような形になる、 zzxxyy の連結に等しく、 ss は と の連結に等しい。 aabb の連結に等しい。 一般的なケースでは、2つのオラクルが必要である:1つは gg、もう1つは f~\tilde{f}、 ここで、後者は屈曲関数 ff双対として知られる関数である。 内積関数は自己双対的な性質を持つ f~=f\tilde{f}=f

内積の隠しシフトの回路では、一般的な場合の回路に現れるハダマードの中間層を省略した。 を省略した。 一般的なケースでは このレイヤーは必要だが、省略することで深さを少し節約した。 ab{|{a}\rangle}{|{b}\rangle} の代わりに ba{|{b}\rangle}{|{a}\rangle}


要件

このチュートリアルを始める前に、以下のものがインストールされていることを確認してください:

  • Qiskit SDK v2.1 またはそれ以降、 可視化サポート付き
  • Qiskit Runtime v0.41 またはそれ以降 (pip install qiskit-ibm-runtime)
  • M3 Qiskit addon v3.0 (pip install mthree)

セットアップ

from collections.abc import Iterator, Sequence
from random import Random
from qiskit.circuit import (
    CircuitInstruction,
    QuantumCircuit,
    QuantumRegister,
    Qubit,
)
from qiskit.circuit.library import CZGate, HGate, XGate
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
import timeit
import matplotlib.pyplot as plt
from qiskit_ibm_runtime import SamplerV2 as Sampler
import mthree

ステップ1:古典的な入力を量子問題にマッピングする

まず、隠しシフト問題を実装するための関数を QuantumCircuit として書く。

def apply_hadamards(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:
    """Apply a Hadamard gate to every qubit."""
    for q in qubits:
        yield CircuitInstruction(HGate(), [q], [])


def apply_shift(
    qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
    """Apply X gates where the bits of the shift are equal to 1."""
    for i, q in zip(range(shift.bit_length()), qubits):
        if shift >> i & 1:
            yield CircuitInstruction(XGate(), [q], [])


def oracle_f(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:
    """Apply the f oracle."""
    for i in range(0, len(qubits) - 1, 2):
        yield CircuitInstruction(CZGate(), [qubits[i], qubits[i + 1]])


def oracle_g(
    qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
    """Apply the g oracle."""
    yield from apply_shift(qubits, shift)
    yield from oracle_f(qubits)
    yield from apply_shift(qubits, shift)


def determine_hidden_shift(
    qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
    """Determine the hidden shift."""
    yield from apply_hadamards(qubits)
    yield from oracle_g(qubits, shift)
    # We omit this layer in exchange for post processing
    # yield from apply_hadamards(qubits)
    yield from oracle_f(qubits)
    yield from apply_hadamards(qubits)


def run_hidden_shift_circuit(n_qubits, rng):
    hidden_shift = rng.getrandbits(n_qubits)

    qubits = QuantumRegister(n_qubits, name="q")
    circuit = QuantumCircuit.from_instructions(
        determine_hidden_shift(qubits, hidden_shift), qubits=qubits
    )
    circuit.measure_all()
    # Format the hidden shift as a string.
    hidden_shift_string = format(hidden_shift, f"0{n_qubits}b")
    return (circuit, hidden_shift, hidden_shift_string)


def display_circuit(circuit):
    return circuit.remove_final_measurements(inplace=False).draw(
        "mpl", idle_wires=False, scale=0.5, fold=-1
    )

小さな例から始めよう:

n_qubits = 6
random_seed = 12345
rng = Random(random_seed)
circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(
    n_qubits, rng
)

print(f"Hidden shift string {hidden_shift_string}")

display_circuit(circuit)

Output:

Hidden shift string 011010
Output of the previous code cell

ステップ2:量子ハードウェア実行のための回路最適化

job_tags = [
    f"shift {hidden_shift_string}",
    f"n_qubits {n_qubits}",
    f"seed = {random_seed}",
]
job_tags

Output:

['shift 011010', 'n_qubits 6', 'seed = 12345']
# Uncomment this to run the circuits on a quantum computer on IBMCloud.
service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=100
)

# from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2
# backend = FakeMelbourneV2()
# backend.refresh(service)

print(f"Using backend {backend.name}")


def get_isa_circuit(circuit, backend):
    pass_manager = generate_preset_pass_manager(
        optimization_level=3, backend=backend, seed_transpiler=1234
    )
    isa_circuit = pass_manager.run(circuit)
    return isa_circuit


isa_circuit = get_isa_circuit(circuit, backend)
display_circuit(isa_circuit)

Output:

Using backend ibm_kingston
Output of the previous code cell

ステップ3: Qiskit primitives を使用して回路を実行する

# submit job for solving the hidden shift problem using the Sampler primitive
NUM_SHOTS = 50_000


def run_sampler(backend, isa_circuit, num_shots):
    sampler = Sampler(mode=backend)
    sampler.options.environment.job_tags
    pubs = [(isa_circuit, None, NUM_SHOTS)]
    job = sampler.run(pubs)
    return job


def setup_mthree_mitigation(isa_circuit, backend):
    # retrieve the final qubit mapping so mthree knows which qubits to calibrate
    qubit_mapping = mthree.utils.final_measurement_mapping(isa_circuit)

    # submit jobs for readout error calibration
    mit = mthree.M3Mitigation(backend)
    mit.cals_from_system(qubit_mapping, rep_delay=None)

    return mit, qubit_mapping
job = run_sampler(backend, isa_circuit, NUM_SHOTS)
mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)

ステップ4:後処理を行い、結果を従来の形式で返す

上記の理論的な議論では、入力 abab に対し、出力 baba を期待することを決定した。 さらに複雑なのは、より単純な(トランスパイル前の)回路を作るために、必要なCZゲートを隣接する量子ビットのペアの間に挿入したことである。 を挿入したことである。 これは、ビット列 aabba1b1a2b2a1 b1 a2 b2 \ldots のようにインターリーブすることになる。 出力文字列 baba も同様にインターリーブされます: b1a1b2a2b1 a1 b2 a2 \ldots。以下の関数 unscramble は、入力文字列と出力文字列を直接比較できるように、出力文字列を b1a1b2a2b1 a1 b2 a2 \ldots から a1b1a2b2a1 b1 a2 b2 \ldots に変換する。

# retrieve bitstring counts
def get_bitstring_counts(job):
    result = job.result()
    pub_result = result[0]
    counts = pub_result.data.meas.get_counts()
    return counts, pub_result
counts, pub_result = get_bitstring_counts(job)

2つのビット列間のハミング距離は、ビットが異なるインデックスの数である。

def hamming_distance(s1, s2):
    weight = 0
    for c1, c2 in zip(s1, s2):
        (c1, c2) = (int(c1), int(c2))
        if (c1 == 1 and c2 == 1) or (c1 == 0 and c2 == 0):
            weight += 1

    return weight
# Replace string of form a1b1a2b2... with b1a1b2a1...
# That is, reverse order of successive pairs of bits.
def unscramble(bitstring):
    ps = [bitstring[i : i + 2][::-1] for i in range(0, len(bitstring), 2)]
    return "".join(ps)


def find_hidden_shift_bitstring(counts, hidden_shift_string):
    # convert counts to probabilities
    probs = {
        unscramble(bitstring): count / NUM_SHOTS
        for bitstring, count in counts.items()
    }

    # Retrieve the most probable bitstring.
    most_probable = max(probs, key=lambda x: probs[x])

    print(f"Expected hidden shift string: {hidden_shift_string}")
    if most_probable == hidden_shift_string:
        print("Most probable bitstring matches hidden shift 😊.")
    else:
        print("Most probable bitstring didn't match hidden shift ☹️.")
    print("Top 10 bitstrings and their probabilities:")
    display(
        {
            k: (v, hamming_distance(hidden_shift_string, k))
            for k, v in sorted(
                probs.items(), key=lambda x: x[1], reverse=True
            )[:10]
        }
    )

    return probs, most_probable
probs, most_probable = find_hidden_shift_bitstring(
    counts, hidden_shift_string
)

Output:

Expected hidden shift string: 011010
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their probabilities:
{'011010': (0.9743, 6),
 '001010': (0.00812, 5),
 '010010': (0.0063, 5),
 '011000': (0.00554, 5),
 '011011': (0.00492, 5),
 '011110': (0.00044, 5),
 '001000': (0.00012, 4),
 '010000': (8e-05, 4),
 '001011': (6e-05, 4),
 '000010': (6e-05, 4)}

M3 で読み出しエラーを軽減する前に、最も確率の高いビット列の確率を記録しておこう。

max_probability_before_M3 = probs[most_probable]
max_probability_before_M3

Output:

0.9743

次に、 M3 によって学習された読み出し補正をカウント値に適用する。 この関数は準確率分布 apply_corrections を返します。 これは 11 に等しくなるオブジェクト float のリストです。ただし、一部の値は負である可能性があります。

def perform_mitigation(mit, counts, qubit_mapping):
    # mitigate readout error
    quasis = mit.apply_correction(counts, qubit_mapping)

    # print results
    most_probable_after_m3 = unscramble(max(quasis, key=lambda x: quasis[x]))

    is_hidden_shift_identified = most_probable_after_m3 == hidden_shift_string
    if is_hidden_shift_identified:
        print("Most probable bitstring matches hidden shift 😊.")
    else:
        print("Most probable bitstring didn't match hidden shift ☹️.")
    print("Top 10 bitstrings and their quasi-probabilities:")
    topten = {
        unscramble(k): f"{v:.2e}"
        for k, v in sorted(quasis.items(), key=lambda x: x[1], reverse=True)[
            :10
        ]
    }
    max_probability_after_M3 = float(topten[most_probable_after_m3])
    display(topten)

    return max_probability_after_M3, is_hidden_shift_identified
print(f"Expected hidden shift string: {hidden_shift_string}")
max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(
    mit, counts, qubit_mapping
)

Output:

Expected hidden shift string: 011010
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their quasi-probabilities:
{'011010': '1.01e+00',
 '001010': '8.75e-04',
 '001000': '7.38e-05',
 '010000': '4.51e-05',
 '111000': '2.18e-05',
 '001011': '1.74e-05',
 '000010': '6.42e-06',
 '011001': '-7.18e-06',
 '011000': '-4.53e-04',
 '010010': '-1.28e-03'}

M3 補正を適用する前と後の、隠されたシフト文字列の特定を比較する

def compare_before_and_after_M3(
    max_probability_before_M3,
    max_probability_after_M3,
    is_hidden_shift_identified,
):
    is_probability_improved = (
        max_probability_after_M3 > max_probability_before_M3
    )
    print(f"Most probable probability before M3: {max_probability_before_M3}")
    print(f"Most probable probability after M3: {max_probability_after_M3}")
    if is_hidden_shift_identified and is_probability_improved:
        print("Readout error mitigation effective! 😊")
    else:
        print("Readout error mitigation not effective. ☹️")
compare_before_and_after_M3(
    max_probability_before_M3,
    max_probability_after_M3,
    is_hidden_shift_identified,
)

Output:

Most probable probability before M3: 0.9743
Most probable probability after M3: 1.01
Readout error mitigation effective! 😊

M3 が要求するCPU時間がショット数に比例して増加する様子をプロットする

# Collect samples for numbers of shots varying from 5000 to 25000.
shots_range = range(5000, NUM_SHOTS + 1, 2500)
times = []
for shots in shots_range:
    print(f"Applying M3 correction to {shots} shots...")
    t0 = timeit.default_timer()
    _ = mit.apply_correction(
        pub_result.data.meas.slice_shots(range(shots)).get_counts(),
        qubit_mapping,
    )
    t1 = timeit.default_timer()
    print(f"\tDone in {t1 - t0} seconds.")
    times.append(t1 - t0)

fig, ax = plt.subplots()
ax.plot(shots_range, times, "o--")
ax.set_xlabel("Shots")
ax.set_ylabel("Time (s)")
ax.set_title("Time to apply M3 correction")

Output:

Applying M3 correction to 5000 shots...
	Done in 0.003321983851492405 seconds.
Applying M3 correction to 7500 shots...
	Done in 0.004425413906574249 seconds.
Applying M3 correction to 10000 shots...
	Done in 0.006366567220538855 seconds.
Applying M3 correction to 12500 shots...
	Done in 0.0071477219462394714 seconds.
Applying M3 correction to 15000 shots...
	Done in 0.00860048783943057 seconds.
Applying M3 correction to 17500 shots...
	Done in 0.010026784148067236 seconds.
Applying M3 correction to 20000 shots...
	Done in 0.011459112167358398 seconds.
Applying M3 correction to 22500 shots...
	Done in 0.012727141845971346 seconds.
Applying M3 correction to 25000 shots...
	Done in 0.01406092382967472 seconds.
Applying M3 correction to 27500 shots...
	Done in 0.01546052098274231 seconds.
Applying M3 correction to 30000 shots...
	Done in 0.016769016161561012 seconds.
Applying M3 correction to 32500 shots...
	Done in 0.019537431187927723 seconds.
Applying M3 correction to 35000 shots...
	Done in 0.019739801064133644 seconds.
Applying M3 correction to 37500 shots...
	Done in 0.021093040239065886 seconds.
Applying M3 correction to 40000 shots...
	Done in 0.022840639110654593 seconds.
Applying M3 correction to 42500 shots...
	Done in 0.023974396288394928 seconds.
Applying M3 correction to 45000 shots...
	Done in 0.026412792038172483 seconds.
Applying M3 correction to 47500 shots...
	Done in 0.026364430785179138 seconds.
Applying M3 correction to 50000 shots...
	Done in 0.02820305060595274 seconds.
Text(0.5, 1.0, 'Time to apply M3 correction')
Output of the previous code cell

プロットの解釈

上のプロットは、 M3 補正を適用するのに必要な時間が、ショット数に対してリニアにスケールすることを示している。


スケールアップ

n_qubits = 80
rng = Random(12345)
circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(
    n_qubits, rng
)

print(f"Hidden shift string {hidden_shift_string}")

Output:

Hidden shift string 00000010100110101011101110010001010000110011101001101010101001111001100110000111
isa_circuit = get_isa_circuit(circuit, backend)
job = run_sampler(backend, isa_circuit, NUM_SHOTS)
mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)
counts, pub_result = get_bitstring_counts(job)
probs, most_probable = find_hidden_shift_bitstring(
    counts, hidden_shift_string
)

Output:

Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their probabilities:
{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': (0.50402,
  80),
 '00000010100110101011101110010001010000110011100001101010101001111001100110000111': (0.0396,
  79),
 '00000010100110101011101110010001010000110011101001101010101001111001100100000111': (0.0323,
  79),
 '00000010100110101011101110010001010000110011101001101010101001101001100110000111': (0.01936,
  79),
 '00000010100110101011101110010011010000110011101001101010101001111001100110000111': (0.01432,
  79),
 '00000010100110101011101110010001010000110011101001101010101001011001100110000111': (0.0101,
  79),
 '00000010100110101011101110010001010000110011101001101010101001110001100110000111': (0.00924,
  79),
 '00000010100110101011101110010001010000010011101001101010101001111001100110000111': (0.00908,
  79),
 '00000010100110101011100110010001010000110011101001101010101001111001100110000111': (0.00888,
  79),
 '00000010100110101011101110010001010000110011101001100010101001111001100110000111': (0.0082,
  79)}

正しい隠しシフト文字列が見つかったことがわかる。 さらに、次にありそうな9つのビット列は、たった1つの位置で間違っている。

最も可能性の高い確率を記録する:

max_probability_before_M3 = probs[most_probable]
max_probability_before_M3

Output:

0.50402
print(f"Expected hidden shift string: {hidden_shift_string}")
max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(
    mit, counts, qubit_mapping
)

Output:

Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their quasi-probabilities:
{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': '9.85e-01',
 '00000010100110101011101110010001010000110011100001101010101001111001100110000111': '6.84e-03',
 '00000010100110101011100110010001010000110011101001101010101001111001100110000111': '3.87e-03',
 '00000010100110101011101110010011010000110011101001101010101001111001100110000111': '3.42e-03',
 '00000010100110101011101110010001010000110011101001101010101001111001100100000111': '3.30e-03',
 '00000010100110101011101110010001010000110011101001101010101001110001100110000111': '3.28e-03',
 '00000010100010101011101110010001010000110011101001101010101001111001100110000111': '2.62e-03',
 '00000010100110101011101110010001010000110011101001101010101001101001100110000111': '2.43e-03',
 '00000010100110101011101110010000010000110011101001101010101001111001100110000111': '1.73e-03',
 '00000010100110101011101110010001010000110011101001101010101001111001000110000111': '1.63e-03'}
compare_before_and_after_M3(
    max_probability_before_M3,
    max_probability_after_M3,
    is_hidden_shift_identified,
)

Output:

Most probable probability before M3: 0.54348
Most probable probability after M3: 0.99
Readout error mitigation effective! 😊

その結果、読み出しエラーがエラーの主な原因であり、 M3 の緩和が効果的であった。

このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。