倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

Go で 2-Qubit System (Multi-Qubit System)

Go で量子コンピューティングシリーズ(目次)。 今までは1つの qubit だけを扱っていましたが、量子コンピューティングの特筆すべき性質は複数の qubit が量子もつれを起こしている系から得られます。 今回は2つの qubit からなる 2-qubit 系を表す qubits2.TwoQubitSystem の簡単な使い方を見ていきます。

n 個の qubit からなる n-qubit 系を表す nqubits.MultiQubitSystem という型もあり、使い方は qubits.TwoQubitSystem 型と概ね同じです。 というより、nket, nbasis パッケージに定義されている、イミュータブルな状態や基底を表す「型」は共有していて、ket2, baisis2 パッケージには 2-qubit 系に特有の状態や基底のインスタンスを定義するためだけに使われています(この記事の後半で見る Split メソッド、Split 関数は qubitsTwoQubitSystem 特有のメソッド、関数です)。

Quantum Computing: A Gentle Introduction (Scientific and Engineering Computation)

Quantum Computing: A Gentle Introduction (Scientific and Engineering Computation)

ket2 パッケージと basis2 パッケージ

以前の記事『Go で Qubit』で見た qubit.Qubit に関する ket, basis パッケージ(それぞれイミュータブルな状態と基底が定義されたパッケージ)と同様に、2-qubit 系のイミュータブルな状態と基底に関連する型が定義された ket2, basis2 パッケージがあります。 使い方も概ね同じで、それぞれの New 関数で一般的な状態や基底を取得でき、またよく使われる状態や基底を取得する関数も定義されています。 2-qubit 系の測定などに使うので、それぞれ簡単に見ておきましょう。

ket2 パッケージは 2-qubit 系のイミュータブルな状態を返す関数が定義されています。 一般的な状態は

  { \displaystyle\begin{align*}
  |\psi\rangle = a|00\rangle + b|01\rangle + c|10\rangle + d|11\rangle
\end{align*}}

の形で、4つの複素数係数  { a,\,b,\,c,\,d } を与えることで指定できます:

  w2 := ket2.New(1, 1, 1, 1, false)
  fmt.Println(w2)
  // Output:
  // 0.5|00> + 0.5|01> + 0.5|10> + 0.5|11>

最後の bool 値は(1-qubit 系の場合と同じく)与えた係数が規格化されているかどうかです。 false (ゼロ値)なら規格化を行います*1。 多くの場合は定義済みの状態を使えば事足りると思います:

  zero  := ket2.Zero()   // |00>
  one   := ket2.One()    // |01>
  two   := ket2.Two()    // |10>
  three := ket2.Three()  // |11>

  phiPlus  := ket2.PhiPlus()  // |Φ+> = (|00> + |11>)/√2
  phiMinsu := ket2.PhiMinus() // |Φ-> = (|00> - |11>)/√2
  psiPlus  := ket2.PsiPlus()  // |Ψ+> = (|01> + |10>)/√2
  psiMinus := ket2.PsiMinus() // |Ψ+> = (|01> - |10>)/√2

basis2 パッケージは 2-qubit 系のイミュータブルな基底を返す関数が定義されています。 ここでは定義済みの基底のみを見ておきます。 basis2 パッケージには(2-qubit 系の)標準基底ベル基底の2つの基底が定義されています:

  std  := basis2.Standard()  // 標準基底:{|00>, |01>, |10>, |11>}
  bell := basis2.Bell()      // ベル基底:{|Φ+>, |Φ->, |Ψ+>, |Ψ->}

上記の状態や基底を表すオブジェクトにいくつかメソッドが定義されていますが、1-qubit 系のものと大体同じなのでここでは省略します。

qubits2.TwoQubitSystem 型

上記の2つの型を踏まえて、2-qubit 系を表す qubits2.TwoQubitSystem 型を見ていきます。 この記事では

  • インスタンスの取得方法
  • 2-qubit 系全体を測定するメソッド
  • 系内の1つの qubit を測定するメソッド

を見ていきます。 量子コンピューティングで重要な働きをするユニタリ変換に関しては後日。

インスタンスの取得方法
一般的な 2-qubit 系を表す qubits2.TwoQubitSystem オブジェクトは

  • qubits2.New 関数
  • qubits2.NewWith 関数

のいずれかで生成できます。 qubits2.New 関数は係数を与えて生成する関数で、引数は(上で見た)イミュータブルな状態を生成する ket2.New 関数と同じです。 qubits2.NewWith 関数は引数にイミュータブルな状態を与えます:

  // qubits2.New 関数
  qbts0 := qubits2.New(1, 1, 1, 1, false)

  // qubits2.NewWith 関数
  s := ket2.New(1, 1, 1, 1, false)  // イミュータブルな状態
  qbts1 := qubits2.NewWith(s)

また、qubit.Qubit の場合と同じく、ket2 パッケージに定義済みの状態で TwoQubitSystem オブジェクトを生成する関数が定義されています:

  qbts0 := qubits2.NewPhiPlus()  // |Φ+> を初期状態としてインスタンスを生成

  // 各呼び出しで別のインスタンスが返される
  qbts1 := qubits2.NewPhiPlus()
  if qbts0 == qbts1 { log.Fatal() }

これまた qubit.Qubit の場合と同じく(ket2 パッケージの PhiPlus() 関数などとは異なり)、各呼び出しで別のインスタンスが返されます。

2-qubit 系全体を測定するメソッド
qubit.Qubit オブジェクトが Observe メソッドによって、標準基底やアダマール基底を使った量子力学的測定を行えたように、qubits2.TwoQubitSystem オブジェクトも Observe メソッドによって量子力学的測定を行うことができます。 ただし、この際に用いる基底は上記の basis2 パッケージで定義されている 2-qubit 系の基底となります:

  rand.Seed(time.Now().UnixNano())

  // 同じ TwoQubitSystem オブジェクトを同じ基底で繰り返し測定する
  qbts := NewPhiPlus()  // |Φ+> = (|00> + |11>)/√2
  result := qbts.Observe(basis2.Standard())  // 五分五分の確率で |00> または |11>

  for i := 0; i < 10000; i++ {
    s := qbts.Observe(basis2.Standard())  // 同じ基底で測定を繰り返すと...
    if s != result { log.Fatal(i) }       // 常に同じ状態が返される。
  }

  // 新しい TwoQubitSystem オブジェクトを同じ基底で測定するのを繰り返す
  results := make(map[nket.NState]int)
  for i := 0; i < 10000; i++ {
    qbts := NewPhiPlus()  // 毎回新しいオブジェクト
    r := qbts.Observe(basis2.Standard())  // 五分五分の確率で |00> または |11>
    results[r]++
  }
  fmt.Println(results)
  // 出力例:
  // map[|11>:4961 |00>:5039]

Observe メソッドは測定後の状態を返しますが、これは引数の基底に含まれるオブジェクトと同じものが返されるので ==, != で等値評価が行えます*2

毎回標準基底やベル基底を Observe メソッドの引数に渡すのは少し面倒なので、この2つの基底を使った測定にはそれぞれ

  • ObserveInStandardBasis()
  • ObserveInBellBasis()

の2つのメソッドが定義されています。

系内の1つの qubit を測定するメソッド
qubits2.TwoQubitSystem 型には、2-qubit 系の全体の状態を測定する代わりに1つの qubit を測定するメソッド ObserveQubit メソッドが定義されています。 このメソッドを直接使ってもいいのですが、以下では代わりに系内の1つの qubit を取り出してその状態を測定する方法を見ていきます。 この取り出した qubit は qubit.Qubit 型のオブジェクトなので、『Go で Qubit』で見たのと同じ使い方ができます。

系内の qubit を、番号を指定して取得するには Qubit メソッドを使います:

  qbts := qubits2.NewPhiPlus()

  qbt0 := qbts.Qubit(0)  // 0番目の qubit
  qbt1 := qbts.Qubit(1)  // 1番目の qubit

もしくは Split メソッドでまとめて取得できます*3

  qbts := qubits2.NewPhiPlus()

  // Split メソッド
  qbt0, qbt1 := qbts.Split()

さて、これを踏まえて個々の qubit の測定するコードを見ていきましょう。 2-qubit の頻繁に使われる量子もつれ状態

  { \displaystyle\begin{align*}
  |\Phi+\rangle = \tfrac{1}{\sqrt{2}}\left(|00\rangle + |11\rangle\right)
\end{align*}}

を考えます。 この系は、0番目の qubit を標準状態で測定すると五分五分の確率で  {|0\rangle } もしくは  { |1\rangle } が得られますが、その後で1番目の qubit を標準状態で測定すると0番目の qubit を測定した結果と同じ状態が必ず得られます:

  rand.Seed(time.Now().UnixNano())

  for i := 0; i < 10000; i++ {
    qbt0, qbt1 := qubits2.NewPhiPlus().Split()  // |Φ+> = (|00> + |11>)/√2 の各 qubit

    s0 := qbt0.ObserveInStandardBasis()  // 五分五分の確率で |0> または |1>
    s1 := qbt1.ObserveInStandardBasis()  // s0 と同じ状態が得られる 
    if s0 != s1 { log.Fatal() }

もちろん、最初に1番目の qubit を測定し、その後に0番目の qubit を測定しても同じ結果が得られます(どちらも標準基底で測定)。 個々の qubit は単独の qubit と同じく、一度ある基底で測定されれば同じ基底で測定し続ける限り必ず同じ状態が観測されます(返されます):

  rand.Seed(time.Now().UnixNano())

  qbt0, qbt1 := NewPhiPlus().Split()
  s0 := qbt0.ObserveInStandardBasis()  // |0> もしくは |1>
  s1 := qbt1.ObserveInStandardBasis()  // |0> もしくは |1> (s0 と同じ)

  for i := 0; i < 10000; i++ {
    if qbt0.ObserveInStandardBasis() != s0 { log.Fatal() }  // 常に s0 と同じ
    if qbt1.ObserveInStandardBasis() != s1 { log.Fatal() }  // 常に s1 と同じ
  }

また、量子もつれ状態は測定が行われた後は解消されるので、測定が行われた後の qubit を他の基底で測定しても、もう一方の qubit の状態には影響を与えません:

  rand.Seed(time.Now().UnixNano())

  for i := 0; i < 10000; i++ {
    qbt0, qbt1 := NewPhiPlus().Split()  // |Φ+> = (|00> + |11>)/√2 の各 qubit

    s0 := qbt0.ObserveInStandardBasis()  // 五分五分の確率で |0> もしくは |1>
    qbt0.ObserveInHadamardBasis()  // 上記どちらの場合でも五分五分の確率で |+> もしくは |->

    s1 := qbt1.ObserveInStandardBasis()  // 必ず s0 と同じ状態が返され、
    if s0 != s1 { log.Fatal() }          // qbt0 をアダマール基底で測定した結果とは無関係
  }

まぁ、系の挙動はともかく、関数・メソッドの使い方は特に難しくはないと思います。

ここでは  { |\Phi+\rangle } を使った系の測定を見てきたので、1つの qubit を測定すれば他の qubit の状態も定まってしまいますが、一般の量子もつれ状態では必ずしもそうならないので注意。 詳しくは量子力学を学んでください(数式の計算自体は、記法に慣れれば単なるベクトルの計算です)。

次回はこれらの API を使って量子もつれを利用した量子鍵配送 Ekert91 プロトコルを見ていく予定。

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

*1:規格化されていない係数と true を渡した場合の挙動は規定していません。

*2:n-qubit 系の nqubits.MultiQubitSystem の Observe メソッドでは同じオブジェクトになるかどうかは(基底オブジェクトの)実装に依るので、イミュータブルな状態オブジェクトの Equals メソッドを使う方が無難です。 これは n-qubit 系の基底ベクトルを全て保持すると、n が大きい場合にメモリ消費量が多くなる恐れがあるためです。

*3:n-qubit 系を表す nqubits.MultiQubitSystem 型には Qubit メソッドはありますが、Split メソッドは定義されていません。