倭算数理研究所

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

Go で Qubit

Go で量子コンピューティングシリーズ(目次)。 Google Go で量子計算・量子コンピュータの簡単なシミュレータを書いていきます(『Quantum Computing: A Gentle Introduction』の練習問題を解くサポートで)。 今回は準備として 1 qubit 系に関連する型をいくつか見ていきます。 今後の量子計算に関連する部分はコードを見ていくつもりですが、今回扱うものはインスタンス化とメソッドの使い方だけ見ていきます。 1 qubit 系なので量子もつれはなく、特に難しいことも面白いこともないかと思います。

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

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

ket.State 型

まずは固定された量子状態を表す ket.State 型。 この型はイミュータブル、つまり一度インスタンス化されればフィールドの値が変化しません。

任意の状態を得たい場合は、 { a|0\rangle +b|1\rangle } { a,\,b } を与えてインスタンス化する ket.New 関数を使います。 最後の引数の bool 値は、第1, 2 引数の複素係数が規格化されているかどうかを指定します:

  import "github.com/waman/qwave/system/qubit/ket"

  s0 := ket.New(1, 0, true)  // = |0>  :既に規格化されている
  s := ket.New(3, 4, false)  // = (3|0> + 4|1>)/5  :規格化が必要

第3引数の bool 値は、ゼロ値 false の場合に規格化するようにしてあります。 第3引数に true を指定しつつ、規格化されていない係数を渡した場合の動作は保証されていません(確率計算などが正しい結果を返さないなど)。

ket パッケージにはいくつかの定義済みの状態があります(以下、import 文は省略):

  zero  := ket.Zero()   // |0>
  one   := ket.One()    // |1>
  plus  := ket.Plus()   // |+> = (|0> + |1>)/√2
  minus := ket.Minus()  // |-> = (|0> - |1>)/√2

また、ket.State 型にはいくつかのメソッドが定義されています:

  delta := 1e-10  // 許容誤差

  // EqualState メソッド (|<x|y>| = 1 ?)
  s := ket.New(1, 1, false)
  fmt.Println(s.EqualState(ket.Plus(), delta))  // true

  // IsOrthogonalTo メソッド (<x|y> = 0 ?)
  fmt.Println(ket.Zero().IsOrthogonalTo(ket.One(), 1e-10))  // true

  // Probablility メソッド (= |<x|y>|^2)
  p := ket.Zero().Probability(ket.Minus())
  fmt.Println(math.Abs(p - 0.5) <= delta)  // true

メソッドの意味は名前から明らかだと思います。 状態を指定する係数  { a,\,b }浮動小数点数なので、EqualState, IsOrthogonalTo メソッドの第2引数で許容される誤差を指定する必要があります。

basis.Basis 型

basis.Basis 型は量子状態の基底を表します。 1 qubit の量子状態の基底なので、2つの基底ベクトル(状態)のみからなります。

basis.Basis オブジェクトは basis.New 関数によって生成します:

  import "github.com/waman/qwave/system/qubit/basis"

  delta := 1e-10
  b := basis.New(ket.New(3, 4, false), ket.New(4, -3, false), delta)

delta は ket.State 型の IsOrthogonalTo メソッドにあった許容誤差と同じものです。 2つの状態がこの範囲内で直交していなければパニックを起こします。

basis.Basis 型についても、定義済みのものがあります:

  standard := basis.Standard()  // 標準規定: {|0>, |1>}
  hadamard := basis.Hadamard()  // アダマール基底: {|+>, |->}

基底に含まれる状態は、First, Second メソッドで取得できます:

  b := basis.Standard()
  fmt.Println(b.First() == ket.Zero())  // true
  fmt.Println(b.Second() == ket.One())  // true

qubit.Qubit 型

qubit.Qubit 型は1つの qubit を表します。 qubit.Qubit オブジェクトは ket.State オブジェクトとは異なり、測定(Observe メソッドの呼び出し)によって内部状態が変化します(しない場合もありますが)。

Go のコードとしては、qubit.Qubit 型は ket.State 型や basis.Basis 型と異なり、インターフェースとして定義されています*1

qubit.Qubit オブジェクトは qubit.New 関数もしくは qubit.NewWith 関数で生成します:

  import "github.com/waman/qwave/system/qubit"
  import "github.com/waman/qwave/system/qubit/ket"

  qbt0 := qubit.New(3, 4, false)  // = (3|0> + 4|1>)/5
  qbt1 := qubit.NewWith(ket.Zero())

NewWith 関数は ket.State オブジェクトを指定して qubit を作成します。 New 関数の引数は ket.New 関数で ket.State オブジェクトを生成する際のものと同じです。

qubit パッケージには特定の状態を持つ qubit.Qubit オブジェクトを生成する関数もあります:

  zero  := qubit.NewZero()
  one   := qubit.NewOne()
  plus  := qubit.NewPlus()
  minus := qubit.NewMinus()

これらは定義済み状態を返す ket.Zero() 関数などとは異なり、同じ関数でも各呼び出しで異なるオブジェクトを返します(そのため New が付いています):

  fmt.Println(qubit.NewZero() != qubit.NewZero())  // true

qubit.Qubit 型には Observe メソッドが定義されており、量子力学的測定を行います。 引数には測定を行う基底(basis.Basis オブジェクト)を渡し*2、返り値として測定後の qubit の状態(ket.State オブジェクト)を返します:

  qbt := qubit.NewPlus()

  // Observe メソッド
  result := qbt.Observe(basis.Standard())

  fmt.Println(result == ket.Zero() || result == ket.One())  // true

Observe メソッドの返り値の ket.State オブジェクトは、引数の基底に含まれる状態のどちらかと同じオブジェクトなので、 == 演算子で評価することができます(EqualState メソッドを使ってもかまいませんが)。 basis.Standard() や basis.Hadamard() は ket パッケージの定義済み状態を返す関数 ket.Zero(), ket.Plus() によって得られるオブジェクトで構成されているので、上記のコードのように == 演算子を使えます。

量子力学的測定を模倣しているので、一度ある基底で測定すれば、その後に同じ基底で測定しても常に同じ結果(状態)が得られるだけです:

  qbt := qubit.NewPlus()
  result := qbt.Observe(basis.Standard())  // 一度測定

  for i := 0; i < 1000; i++ {
    fmt.Println(qbt.Observe(basis.Standard()) == result)  // 全て true
  }

別の基底で測定すれば、対応する確率にしたがって基底のどちらかの状態が得られます:

  // |+> を生成して標準基底 {|0>, |1>} で測定する(100000回)
  result := make(map[*ket.State]int)

  for i := 0; i < 100000; i++ {
    s := qubit.NewPlus().Observe(basis.Standard())
    if s == ket.Zero() {
      result[ket.Zero()]++
    }else{
      result[ket.One()]++
    }
  }

  fmt.Println(result)
  // (出力例)
  // Output:
  // map[|0>:50082 |1>:49918]

今の場合は五分五分の確率で  { |0\rangle } もしくは  { |1\rangle } が得られます。 内部では rand パッケージの乱数生成をそのまま使っているので、必要なら事前に(main 関数の最初か init 関数内で)乱数の種を設定してください。

標準基底もしくはアダマール基底で測定を行う場合は、それぞれ ObserveInStandardBasis, ObserveInHadamardBasis メソッドも使えます。 長いメソッド名ですが、Observe メソッドに basis.Basis オブジェクトを指定するよりも少しだけ短く書けます :-)

今回は量子状態、基底、qubit を表す型を見てきました。 次回はこれらを使って量子鍵配送 (quantum key distribution) を実装してみます。

【修正】

  • ket.Zero や basis.Standard を getter メソッドではなく変数へのアクセスに変更しました。 不変 (immutable) じゃなくなるので少々不安ですが。
  • ket.Zero() や basis.Standard() など、定義済みの状態や基底にアクセスする場合に getter 関数を使うように戻しました。
  • ket.New 関数や qubit.New 関数が係数を自動的には規格化しないように変更しました。 係数が規格化されているかどうかを指定する bool を第3引数に指定する必要があります。
  • Qubit 型に ObserveInStandardBasis, ObserveInHadamardBasis メソッドを新たに定義しました。

github.com

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

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

*1:ket.State, basis.Basis は構造体として定義されています。

*2:伝統的な量子力学ではオブザーバブル(物理量・観測可能量)によって測定を行いますが、エルミート演算子は固有状態が完全系をなすので、この完全系を用いて測定を行っていることに対応します。 また、この場合固有値が測定値となりますが、量子コンピューティングでは固有値はあまり重要ではないので、完全系をなす基底だけを明示して測定を行うようにしています。