メインコンテンツまでスキップ

フェルミオン-量子ビットのマッピング

量子コンピュータで物理系のダイナミクスをシミュレーションするために、電子のハミルトニアンを量子ビットにマッピングする必要があります。通常、量子化学で使用されるフェルミオン系のハミルトニアンは、反交換子の生成と消滅演算子を使用して表現されます:第2量子化のもとではcic_i^{\dagger}, cic_iです。生成演算子と消滅演算子を量子ビットに働くパウリ演算子として書き直すことができるのならば、量子コンピュータでそれらを表現することができます。

ここでは、cic_i, cic_i^{\dagger}が反交換関係を満たします。

{ci,cj}=δij{ci,cj}=0{ci,cj}=0\begin{align} \{c_{i}, c_{j}^{\dagger}\} &= \delta_{ij}\\ \{c_{i}, c_{j}\} &= 0\\ \{c_{i}^{\dagger}, c_{j}^{\dagger}\} &= 0 \end{align}

ii, jjは演算子が働く自由度のラベルを示します。

フェルミオン波動関数は反対称性を示しますが、量子コンピュータでの量子ビットへのスピン軌道からの直接的なマッピングの時は、スピン軌道の電子の存在するとき1|1\rangle、存在しないとき0|0\rangleと表現され、反対称性は維持されません。この不一致は電子が識別不可能な粒子であるが、量子ビットは識別可能であるからです。フェルミオンの振る舞いを正しく表現するために、必要な反交換関係を保持するマッピング技術が複数開発されています。

このチュートリアルでは、OperFermionFermionOperatorからQURI PartsのOperatorにマッピングする方法について説明します。QURI Partsには3種類のマッピング手法があります:

  1. Jordan-Wignerマッピング
  2. Bravyi-Kitaevマッピング
  3. Symmetry-conserving Bravyi-Kitaevマッピング

前提条件

このチュートリアルで使用するQURI Partsモジュール:quri-parts-core, quri-parts-openfermion

以下のようにインストールできます:

!pip install "quri_parts[openfermion]"

概要

ここでデモンストレーションのためにフェルミ-ハバード・ハミルトニアンを設定します:

from openfermion import fermi_hubbard

n_site = 2
hamiltonian = fermi_hubbard(x_dimension=n_site, y_dimension=1, tunneling=1, coulomb=2)
print("Fermi-Hubbard Hamiltonian:")
print(hamiltonian)
#output
Fermi-Hubbard Hamiltonian:
2.0 [0^ 0 1^ 1] +
-1.0 [0^ 2] +
-1.0 [1^ 3] +
-1.0 [2^ 0] +
2.0 [2^ 2 3^ 3] +
-1.0 [3^ 1]

ii^はcic_i^{\dagger}jjは[・]でのcjc_jを示しています。例えば、[0^ 2]はc0c2c_0^{\dagger}c_2を指します。これは第2量子化の形式で書かれたハミルトニアンであることに注意してください。

QURI Partsでは、以下を生成するマッピングオブジェクトを提供しています:

  • OpenFermion演算子のmapper (operator mapper):

    • openfermion.ops.FermionOperator
    • openfermion.ops.InteractionOperator
    • openfermion.ops.MajoranaOperator

    をQURI PartsのOperatorにマッピングする関数

  • state mapper: 占有数の状態

    Ψ=cicjck000\begin{equation} | \Psi \rangle = c_i^{\dagger} c_j^{\dagger} \cdots c_k^{\dagger} | 00\cdots 0\rangle \end{equation}

    ComputationalBasisStateにマッピングする関数

  • inverse state mapper: ComputationalBasisStateを占有数の状態にマッピングする関数

例としてJordan-Wignerマッピングを考えます。QURI Partsでmapperを得るためのステップは以下のようになります。

  1. マッピングオブジェクトの作成
  2. 対応するプロパティにアクセスしてmapperを取得

4スピンの軌道を持つ系のJordan-Wignerマッピングを行うマッピングオブジェクトを初めに作成します。

from quri_parts.openfermion.transforms import jordan_wigner
jw_mapping = jordan_wigner(n_spin_orbitals=2*n_site)

フェルミオン演算子をパウリ演算子にマッピング

operator_mapper = jw_mapping.of_operator_mapper
qubit_hamiltonian = operator_mapper(hamiltonian)
print(qubit_hamiltonian)
#output
(-0.5+0j)*Y0 Z1 Y2 + (-0.5+0j)*X0 Z1 X2 + (-0.5+0j)*Y1 Z2 Y3 + (-0.5+0j)*X1 Z2 X3 + (1+0j)*I + (-0.5+0j)*Z1 + (-0.5+0j)*Z0 + (0.5+0j)*Z0 Z1 + (-0.5+0j)*Z3 + (-0.5+0j)*Z2 + (0.5+0j)*Z2 Z3

占有数状態をComputationalBasisStateにマッピング

占有数状態0,3=c0c30000|0, 3\rangle = c_0^{\dagger} c_3^{\dagger}|0000\rangleがJordan-Wignerマッピングのもとでどのようにマッピングされるかをみてみましょう。

state_mapper = jw_mapping.state_mapper

occ_state = [0, 3]
cb_state = state_mapper(occ_state)

print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
print("ComputationalBasisState:", cb_state)
print("State preparation circuit:")
for g in cb_state.circuit.gates:
print(g)
#output
Occupation state: |0, 3>
ComputationalBasisState: ComputationalBasisState(qubit_count=4, bits=0b1001, phase=0π/2)
State preparation circuit:
QuantumGate(name='X', target_indices=(0,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(3,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())

ComputationalBasisStateを占有状態にマッピング

計算基底状態1011|1011\rangleがJordan-Wignerマッピングで何にマッピングされるかを見てみましょう。

from quri_parts.core.state import quantum_state

inv_state_mapper = jw_mapping.inv_state_mapper

cb_state = quantum_state(n_qubits=2*n_site, bits=0b1011)
occ_state = inv_state_mapper(cb_state)

print("ComputationalBasisState:", cb_state)
print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
#output
ComputationalBasisState: ComputationalBasisState(qubit_count=4, bits=0b1011, phase=0π/2)
Occupation state: |0, 1, 3>

Bravyi-Kitaevマッピングと対称性を保存するBravyi-Kitaevマッピングも同様に使用することができます。

インターフェース

ここでは、QURI Partsではマッピングを行う二つの柱、OpenFermionQubitMapperFactoryOpenFermionQubitMappingについて紹介します。

OpenFermionQubitMapping

OpenFermionQubitMappingは、変換または演算子を作用させたい状態の設定を保持するマッピングオブジェクトです。このチュートリアルでは、これを略して「マッピングオブジェクト」と呼びます。状態の設定は以下の情報を含みます:

  • NsN_s: スピン軌道の数
  • NeN_e: 電子の数
  • szs_z: 状態スピンのzz成分

演算子mapperと状態mapperと逆状態mapperはプロパティとして取り出せます。最後のセクションで作ったjw_mapping変数はOpenFermionQubitMappingオブジェクトです。次のサブセクションで紹介するOpenFermionQubitMapperFactoryによって生成されます。

OpenFermionQubitMapperFactory

OpenFermionQubitMapperFactoryOpenFermionQubitMappingを生成するオブジェクトです。QURI Partsでは、以下のOpenFermionQubitMapperFactoryが存在します。

# Jordan-Wigner
from quri_parts.openfermion.transforms import jordan_wigner

# Bravyi-Kitaev
from quri_parts.openfermion.transforms import bravyi_kitaev

# Symmetry conserving Bravyi-Kitaev
from quri_parts.openfermion.transforms import symmetry_conserving_bravyi_kitaev as scbk

OpenFermionQubitMapperFactoryは量子状態の情報を以下のメソッドに渡すことで各mapperを生成することができます:

  • get_of_operator_mapper
  • get_state_mapper
  • get_inv_state_mapper

加えて、与えられたスピン軌道数の系をマッピングした場合に必要な量子ビットの数、あるいはその逆についての情報も保持しています。以下の情報を取得することができます:

  • n_qubits_required:nnスピン軌道の系へのマッピングを行うのに必要な量子ビットの個数
  • n_spin_orbitals:マッピングされた計算基底状態がnn量子ビットを含む場合、系が含むスピン軌道の個数

Jordan-Wignerマッピング

手始めに、Jordan-Wignerマッピングについての簡単な概要について見てみましょう。Jordan-Wignerマッピングは以下のように与えられます。

cj=ZNsZj+112(XjiYj)cj=ZNsZj+112(Xj+iYj)\begin{align} c_j^{\dagger} &= Z_{N_s}\otimes\cdots\otimes Z_{j+1}\otimes\frac{1}{2}(X_j-iY_j)\\ c_j &= Z_{N_s}\otimes\cdots\otimes Z_{j+1}\otimes\frac{1}{2}(X_j+iY_j) \end{align}

XiX_iYiY_iZiZ_iはパウリ演算子です。Xj+iYjX_j+iY_jXjiYjX_j-iY_jは昇降演算子で以下を満たします。

12(Xj+iYj)1=012(XjiYj)0=1\begin{align} \frac{1}{2}(X_j+iY_j)|1\rangle&=|0\rangle\\ \frac{1}{2}(X_j-iY_j)|0\rangle&=|1\rangle \end{align}

等式内のZiZ_iはフェルミオンの波動関数の反対称性を表します。

Jordan-Wignerマッピングは、QURI Partsでは、jordan_wignerオブジェクトによって行われます。まずは、Jordan-Wignerマッピングを考慮した場合のスピン軌道数と量子ビット数の関係を見てみましょう。

from quri_parts.openfermion.transforms import jordan_wigner

n_spin_orbitals = 4
n_qubits_required = jordan_wigner.n_qubits_required(n_spin_orbitals=4)
print(
f"{n_qubits_required} qubits are required to perform Jordan-Wigner mapping for a {n_spin_orbitals}-spin-orbital system."
)

n_qubits = 4
n_spin_orbitals = jordan_wigner.n_spin_orbitals(n_qubits=4)
print(
f"{n_spin_orbitals} spin orbitals are present in a system if the Jordan-Wigner-mapped "
f"computational basis state contains {n_qubits} qubits."
)
#output
4 qubits are required to perform Jordan-Wigner mapping for a 4-spin-orbital system.
4 spin orbitals are present in a system if the Jordan-Wigner-mapped computational basis state contains 4 qubits.

では、Jordan-Wignerマッピングオブジェクトを生成しましょう。Jordan-Wignerマッピングでは、スピン軌道の個数のみがマッピングオブジェクトを作成するのに必要です。n_fermionsszが渡された場合には、自動的に無視されます。4スピン軌道を持つ系のJordan-Wignerマッピングオブジェクトを作成してみましょう。

n_spin_orbitals = 2 * n_site
jw_mapping = jordan_wigner(n_spin_orbitals)

次に、mapperを取り出してみましょう。

operator mapper

jw_operator_mapper = jw_mapping.of_operator_mapper
qubit_hamiltonian = jw_operator_mapper(hamiltonian)
print(qubit_hamiltonian)
#output
(-0.5+0j)*Y0 Z1 Y2 + (-0.5+0j)*X0 Z1 X2 + (-0.5+0j)*Y1 Z2 Y3 + (-0.5+0j)*X1 Z2 X3 + (1+0j)*I + (-0.5+0j)*Z1 + (-0.5+0j)*Z0 + (0.5+0j)*Z0 Z1 + (-0.5+0j)*Z3 + (-0.5+0j)*Z2 + (0.5+0j)*Z2 Z3

state mapper

占有状態0,3=c0c30000|0, 3\rangle = c_0^{\dagger} c_3^{\dagger}|0000\rangleがJordan-Wignerマッピングで何にマッピングされるかを見てみましょう。

jw_state_mapper = jw_mapping.state_mapper

occ_state = [0, 3]
cb_state = jw_state_mapper(occ_state)

print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
print("ComputationalBasisState:", cb_state)
print("State preparation circuit:")
for g in cb_state.circuit.gates:
print(g)
#output
Occupation state: |0, 3>
ComputationalBasisState: ComputationalBasisState(qubit_count=4, bits=0b1001, phase=0π/2)
State preparation circuit:
QuantumGate(name='X', target_indices=(0,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(3,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())

inverse state mapper

計算基底状態1011|1011\rangleがJordan-Wignerマッピングで何にマッピングされるかを見てみましょう。

from quri_parts.core.state import quantum_state

jw_inv_state_mapper = jw_mapping.inv_state_mapper

cb_state = quantum_state(n_qubits=2*n_site, bits=0b1011)
occ_state = jw_inv_state_mapper(cb_state)

print("ComputationalBasisState:", cb_state)
print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
#output
ComputationalBasisState: ComputationalBasisState(qubit_count=4, bits=0b1011, phase=0π/2)
Occupation state: |0, 1, 3>

mapperを得る別の方法

インターフェースのセクションで説明したように、マッピング・オブジェクトを作成せずに、jordan_wignerオブジェクトによってmapperを生成することもできます。それらは以下のように行えます:

jw_operator_mapper = jordan_wigner.get_of_operator_mapper(n_spin_orbitals=2*n_site)
jw_state_mapper = jordan_wigner.get_state_mapper(n_spin_orbitals=2*n_site)
jw_inv_state_mapper = jordan_wigner.get_inv_state_mapper(n_spin_orbitals=2*n_site)

Bravyi-Kitaevマッピング

Bravyi-Kitaevマッピングの概要についてみてみましょう。

Jordan-Wigner マッピングでは、1つの量子ビットを見ることで占有スピン軌道を決定でき、複数の量子ビットを見ることでパリティ(波動関数の逆対称性によって量子状態の符号が変化するかどうか)を決定できます。つまり、占有数は局所的に保存され、パリティ情報は非局所的に量子ビットに保存されます。Bravyi-Kitaevマッピングは、両方を非局所的にすることで、必要なパウリZ演算子の数を減らす方法です。

生成された量子状態の各量子ビットは、Jordan-Wigner変換による量子状態にBravyi-Kitaev変換行列を乗算することで得られます。

β2x=(β2x10/10β2x1)\beta_{2^x}= \left( \begin{matrix} \beta_{2^{x-1}} & 0/1 \\ 0 & \beta_{2^{x-1}} \end{matrix} \right)

ここでβ1=(1)\beta_1=(1)で、0/10/1は一番上の行だけ1が並び、他の行は0が並んでいることを意味します。この変換により、偶数番目の量子ビットはそれらに対応する占有スピン軌道の数を保存し、奇数番目の量子ビットは複数の占有スピン軌道の数のパリティを保存します。あるスピン軌道に対して生成・消滅演算子が作用したときに、いくつかの量子ビットを調べることで占有スピン軌道の数とパリティを導出することができます。

Bravyi-KitaevマッピングはQURI Parts のbravyi_kitaevオブジェクトで実行できます。まず、Bravyi-Kitaevマッピングを考慮した場合のスピン軌道数と量子ビット数の関係を見てみましょう。

from quri_parts.openfermion.transforms import bravyi_kitaev

n_spin_orbitals = 4
n_qubits_required = bravyi_kitaev.n_qubits_required(n_spin_orbitals=4)
print(
f"{n_qubits_required} qubits are required to perform Bravyi-Kitaev mapping for a {n_spin_orbitals}-spin-orbital system."
)

n_qubits = 4
n_spin_orbitals = bravyi_kitaev.n_spin_orbitals(n_qubits=4)
print(
f"{n_spin_orbitals} spin orbitals are present in a system if the Bravyi-Kitaev-mapped "
f"computational basis state contains {n_qubits} qubits."
)
#output
4 qubits are required to perform Bravyi-Kitaev mapping for a 4-spin-orbital system.
4 spin orbitals are present in a system if the Bravyi-Kitaev-mapped computational basis state contains 4 qubits.

それでは、Bravyi-Kitaevマッピングオブジェクトを生成してみましょう。Bravyi-Kitaevマッピングでは、マッピングオブジェクトの生成に必要なのはスピン軌道の数だけです。n_fermionsszが渡されても自動的に無視されます。4つのスピン軌道を持つ系に対して作成してみましょう。

n_spin_orbitals = 2 * n_site
bk_mapping = bravyi_kitaev(n_spin_orbitals)

mapperを取得してみましょう。

operator mapper

bk_operator_mapper = bk_mapping.of_operator_mapper
qubit_hamiltonian = bk_operator_mapper(hamiltonian)
print(qubit_hamiltonian)
#output
(-0.5+0j)*X0 Y1 Y2 + (0.5+0j)*Y0 Y1 X2 + (0.5+0j)*Z0 X1 Z3 + (-0.5+0j)*X1 Z2 + (1+0j)*I + (-0.5+0j)*Z0 Z1 + (-0.5+0j)*Z0 + (0.5+0j)*Z1 + (-0.5+0j)*Z1 Z2 Z3 + (-0.5+0j)*Z2 + (0.5+0j)*Z1 Z3

state mapper

Bravyi-Kitaevマッピングの元で占有状態 0,3=c0c30000|0, 3\rangle = c_0^{\dagger} c_3^{\dagger}|0000\rangle が何にマッピングされるかを見てみましょう。4量子ビットの場合、変換行列は

β4=(1111010000110001)\beta_{4}= \left( \begin{matrix} 1&1&1&1\\ 0&1&0&0 \\ 0&0&1&1\\ 0&0&0&1 \end{matrix} \right)

マッピングの後、(1001)T(1\,0\,0\,1)^\mathrm{T}β4\beta_{4}をかけた形、つまり(1001)T(1\,0\,0\,1)^\mathrm{T}("mod 2"をとることに注意)のような形をとります。

bk_state_mapper = bk_mapping.state_mapper

occ_state = [0, 3]
cb_state = bk_state_mapper(occ_state)

print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
print("ComputationalBasisState:", cb_state)
print("State preparation circuit:")
for g in cb_state.circuit.gates:
print(g)
#output
Occupation state: |0, 3>
ComputationalBasisState: ComputationalBasisState(qubit_count=4, bits=0b11, phase=0π/2)
State preparation circuit:
QuantumGate(name='X', target_indices=(0,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(1,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())

inverse state mapper

計算基底状態 0011|0011\rangle がBravyi-Kitaevマッピングで何にマッピングされるかを見てみましょう。

from quri_parts.core.state import quantum_state

bk_inv_state_mapper = bk_mapping.inv_state_mapper

cb_state = quantum_state(n_qubits=2*n_site, bits=0b11)
occ_state = bk_inv_state_mapper(cb_state)

print("ComputationalBasisState:", cb_state)
print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
#output
ComputationalBasisState: ComputationalBasisState(qubit_count=4, bits=0b11, phase=0π/2)
Occupation state: |0, 3>

mapperを得る他の方法

インターフェースのセクションで説明したように、マッピング・オブジェクトを作成しなくても、bravyi_kitaevオブジェクトによってmapperを生成することもできます。それらは以下のように生成されます。

bk_operator_mapper = bravyi_kitaev.get_of_operator_mapper(n_spin_orbitals=2*n_site)
bk_state_mapper = bravyi_kitaev.get_state_mapper(n_spin_orbitals=2*n_site)
bk_inv_state_mapper = bravyi_kitaev.get_inv_state_mapper(n_spin_orbitals=2*n_site)

Symmetry-Conserving Bravyi-Kitaevマッピング

Symmetry-Conserving Bravyi-Kitaev(SCBK)マッピングはQURI Partsのsymmetry_conserving_bravyi_kitaevオブジェクトで実行できます。まず、SCBKマッピングを考慮した場合のスピン軌道数と量子ビット数の関係を見てみましょう。

from quri_parts.openfermion.transforms import symmetry_conserving_bravyi_kitaev as scbk

n_spin_orbitals = 4
n_qubits_required = scbk.n_qubits_required(n_spin_orbitals=n_spin_orbitals)
print(
f"{n_qubits_required} qubits are required to perform SCBK mapping for a {n_spin_orbitals}-spin-orbital system."
)

n_qubits = 2
n_spin_orbitals = scbk.n_spin_orbitals(n_qubits=n_qubits)
print(
f"{n_spin_orbitals} spin orbitals are present in a system if the SCBK-mapped "
f"computational basis state contains {n_qubits} qubits."
)
#output
2 qubits are required to perform SCBK mapping for a 4-spin-orbital system.
4 spin orbitals are present in a system if the SCBK-mapped computational basis state contains 2 qubits.

では、SCBKマッピングオブジェクトを生成してみよう。SCBKマッピングでは、状態の構成に特に注意する必要があります。SCBKマッピングオブジェクトを生成するには、n_spin_orbitalsn_fermionsszが必要です。4つのスピン軌道、2つの電子、およびsz=0s_z = 0を持つ系でSCBKオブジェクトを作成してみましょう。

n_spin_orbitals = 2 * n_site
n_fermions = 2
sz = 0
scbk_mapping = scbk(n_spin_orbitals, n_fermions, sz)

mapperを取得してみましょう。

operator mapper

scbk_operator_mapper = scbk_mapping.of_operator_mapper
qubit_hamiltonian = scbk_operator_mapper(hamiltonian)
print(qubit_hamiltonian)
#output
-1.0*X0 + -1.0*X1 + 1.0*I + 1.0*Z0 Z1

state mapper

SCBKマッピングのもとで占有状態 0,3=c0c30000|0, 3\rangle = c_0^{\dagger} c_3^{\dagger}|0000\rangle が何にマッピングされるかを見てみましょう。

scbk_state_mapper = scbk_mapping.state_mapper

occ_state = [0, 3]
cb_state = scbk_state_mapper(occ_state)

print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
print("ComputationalBasisState:", cb_state)
print("State preparation circuit:")
for g in cb_state.circuit.gates:
print(g)
#output
Occupation state: |0, 3>
ComputationalBasisState: ComputationalBasisState(qubit_count=2, bits=0b1, phase=0π/2)
State preparation circuit:
QuantumGate(name='X', target_indices=(0,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())

inverse state mapper

SCBKマッピングのもとで計算基底状態 01|01\rangle が何にマッピングされるか見てみましょう。

from quri_parts.core.state import quantum_state

scbk_inv_state_mapper = scbk_mapping.inv_state_mapper

n_spin_orbitals = 2*n_site
n_qubits = scbk.n_qubits_required(n_spin_orbitals)
cb_state = quantum_state(n_qubits=n_qubits, bits=0b01)
occ_state = scbk_inv_state_mapper(cb_state)

print("ComputationalBasisState:", cb_state)
print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
#output
ComputationalBasisState: ComputationalBasisState(qubit_count=2, bits=0b1, phase=0π/2)
Occupation state: |0, 3>

mapperを得る別の方法

インターフェースのセクションで説明したように、マッピングオブジェクトを生成することなく、scbkオブジェクトによってmapperを生成することもできます。以下のように生成されます。

scbk_operator_mapper = scbk.get_of_operator_mapper(n_spin_orbitals=2*n_site, n_fermions=n_fermions, sz=sz)
scbk_state_mapper = scbk.get_state_mapper(n_spin_orbitals=2*n_site, n_fermions=n_fermions, sz=sz)
scbk_inv_state_mapper = scbk.get_inv_state_mapper(n_spin_orbitals=2*n_site, n_fermions=n_fermions, sz=sz)