Notebook_Header_01.png

Qiskit Runtime#

このLabは2024年Challenge Lab1 partⅡを基に作成しました。

この Challenge では Qiskit Runtime と 変分量子固有値ソルバー (Variational Quantum Eigensolver, VQE) を活用します。
量子ビットの組み合わせの期待値を計算するために Qiskit Runtime Estimator を使用し、 VQE アルゴリズムの実行を容易にするために Qiskit Runtime Sessions を使用します。
この Challenge は、実験物理学者で IBM の量子研究者である Nick Bronn が Coding with Qiskit Runtime video series 、特に Episode 05 Primitives & Sessions のために作成した例のコードを利用します。

import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt

from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.library import TwoLocal
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator

パウリ演算子を作る#

パウリ演算子は、特定の軸 (x, y, z) に沿ったスピンの測定に対応する量子力学的観測量を表す行列です。作ってみましょう:

pauli_op = SparsePauliOp(['ZII', 'IZI', 'IIZ'])
print(pauli_op.to_matrix())
[[ 3.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  1.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  1.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j -1.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j  0.+0.j  1.+0.j  0.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j -1.+0.j  0.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j -1.+0.j  0.+0.j]
 [ 0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j -3.+0.j]]

さまざまなパウリ演算子を使って、どのような組み合わせが得られるか、自由に実験してみてください!

VQEアルゴリズムのセットアップと実行#

次に、VQEアルゴリズムのセットアップを始めます。変分量子固有値ソルバー (VQE) は、量子と古典のハイブリッド・アルゴリズムであり、与えられた物理系の基底状態を見つけるために、量子と古典のテクニックを利用します。
VQE は量子化学や最適化問題でよく使われており、ノイズの多い近未来デバイスのハイブリッド・アルゴリズムとして有望視されています。

VQE は、「Ansatz」と呼ばれるパラメーター化された試行的な解を繰り返し更新するために、古典的な最適化アルゴリズムを用いることが特徴です。その目的は、パウリ項の線形結合として表される与えられたハミルトニアンの基底状態を解くことです。

VQE アルゴリズムを実行するには、3つのステップが必要です:

  1. ハミルトニアンと Ansatz (仮説) の設定 (問題の設定)

  2. Qiskit Runtime Estimator の実装

  3. 古典オプティマイザーの実行

Exercise: Ansatz回路の実装#

Exercise

私たちの最初のタスクは、私たちが比較する問題に対する Ansatz (試行的な解) を設定するこtです。

これには、 Qiskit の TwoLocal circuit を使用します。 これは変分量子アルゴリズムの試行波動関数や機械学習の分類回路を準備するための構築済み回路です。
TwoLocal circuit は回転層とエンタングルメント層が交互に配置されたパラメーター化された回路です。詳細は Qiskit's documentation を参照してください。

Ry 回転と Rz 回転を使用した3量子ビットの TwoLocal circuit を実装してください。   entanglement は full に設定し、 entanglement blocks は Cz にします。 reps=1insert_barriers=True に設定してください。

num_qubits = #Add your code here
rotation_blocks = #Add your code here
entanglement_blocks = #Add your code here
entanglement = #Add your code here

ansatz = #Add your code here


##### Don't change any code past this line #####
ansatz.decompose().draw('mpl')
Hide code cell content
num_qubits = 3 #Add your code here
rotation_blocks = ['ry', 'rz'] #Add your code here
entanglement_blocks = 'cz' #Add your code here
entanglement = 'full' #Add your code here

ansatz = TwoLocal(
    num_qubits,
    rotation_blocks,
    entanglement_blocks,
    entanglement, reps=1,
    insert_barriers=True
)#Add your code here


### Don't change any code past this line ###
ansatz.decompose().draw('mpl')
_images/5939c9c30b61e0c98e3f0b3bba6ae4fff6c727ecb6c45532c5a74ddc5aff217a.png

上の図から、 ansatz 回路はパラメーター \(\theta\) のベクトルによって定義され、その要素数は以下で与えられることがわかります。

num_params = ansatz.num_parameters
num_params
12

Exercise: ISA 回路へのトランスパイル#

Exercise

ここではバックエンドとして実際の 127qubit バックエンドを模した FakeSherbrooke を使用します。
FakeSherbrooke は Transpiler やその他のバックエンド向けの機能のテストに便利です。

Preset pass managers は transpile() 関数が使用するデフォルトのpass manager です。 transpile() は、transpile関数が量子回路を特定のバックエンドで実行するために最適化および変換する際に行うことを反映した、スタンドアロンの PassManager オブジェクトを構築するための便利でシンプルなメソッドを提供します。

pass manager を定義してください。詳細は Qiskit documentation を参照してください。

backend_answer = #Add your code
optimization_level_answer = #Add your code
pm = generate_preset_pass_manager(backend=backend_answer,optimization_level=optimization_level_answer)
isa_circuit = # Add your code
Hide code cell content
backend_answer = FakeSherbrooke() #Add your code
optimization_level_answer = 2 #Add your code
pm = generate_preset_pass_manager(backend=backend_answer,optimization_level=optimization_level_answer)
isa_circuit = pm.run(ansatz) # Add your code

Qiskit Runtime の最新バージョンでは、バックエンドに渡されるすべての回路はバックエンドの制約に準拠しなければなりません。
このような回路は、ISA (Instruction Set Architecture) と呼ばれる、ハードウェアが理解し実行できる命令セットで記述されていると見なされます。

これらの制約はデバイスのネイティブな基本ゲート、量子ビットの接続性、パルスやその他の命令タイミング仕様などの要因によって定義されます。
ISA 回路を定義するために以下を実行します。

isa_circuit.draw('mpl', idle_wires=False,)
_images/324014584eaf18f71bc72ee3cbc440c69bdd5cbc408f36144a04de64a5cb30d4.png

ご覧のように、トランスパイル後の回路には、バックエンドのネイティブな基本ゲートしか含まれていません。 ISA回路の詳細については、こちらをご覧ください:

# Define our Hamiltonian
hamiltonian_isa = pauli_op.apply_layout(layout=isa_circuit.layout)

Exercise: コスト関数の定義#

Exercise

多くの古典的な最適化問題と同様に、VQE問題の解はスカラーのコスト関数の最小化として定式化することができます。 我々のVQEのコスト関数は単純で、エネルギーです!

Qiskit Runtime Estimator を使用してコスト関数を定義し、与えられたパラメーター化された状態とハミルトニアンに対するエネルギーを求めてください。

def cost_func(params, ansatz, hamiltonian, estimator, callback_dict):
    """Return estimate of energy from estimator

    Parameters:
        params (ndarray): Array of ansatz parameters
        ansatz (QuantumCircuit): Parameterized ansatz circuit
        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
        estimator (EstimatorV2): Estimator primitive instance

    Returns:
        float: Energy estimate
    """
    pub = #Add your code
    result = #Add your code
    energy = #Add your code

    callback_dict["iters"] += #Add your code
    callback_dict["prev_vector"] = #Add your code
    callback_dict["cost_history"].#Add your code


##### Don't change any code past this line #####
    print(energy)
    return energy, result
Hide code cell source
def cost_func(params, ansatz, hamiltonian, estimator, callback_dict):
    """Return estimate of energy from estimator

    Parameters:
        params (ndarray): Array of ansatz parameters
        ansatz (QuantumCircuit): Parameterized ansatz circuit
        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
        estimator (EstimatorV2): Estimator primitive instance

    Returns:
        float: Energy estimate
    """
    pub = (ansatz, [hamiltonian], [params]) #Add your code 'pub-like' object e.g. a list of circuits or tuples
    result = estimator.run([pub]).result() #Add your code
    energy = result[0].data.evs[0] #Add your code

    callback_dict["iters"] += 1 #Add your code
    callback_dict["prev_vector"] = params #Add your code
    callback_dict["cost_history"].append(energy) #Add your code


### Don't change any code past this line ###
    print(energy)
    return energy, result

コールバック関数は、ユーザーが反復アルゴリズム (VQE など) のステータスに関する追加情報を取得するための標準的な方法です。 しかし、これ以上のことも可能です。 ここでは、失敗のためにルーチンを再開したり、別の反復番号を返す必要がある場合に備えて、アルゴリズムの各反復における結果ベクトルを保存するために、変更可能なオブジェクト (辞書) を使用します。

callback_dict = {
    "prev_vector": None,
    "iters": 0,
    "cost_history": [],
}

古典オプティマイザーの使用#

ここで、コスト関数を最小化するために、任意の古典オプティマイザーを使用することができます。実際の量子ハードウェアでは、オプティマイザーの選択が重要です。すべてのオプティマイザーがノイズの多いコスト関数のランドスケープを同じように扱えるとは限らないからです。ここでは、 SciPy ルーチンを使用します。

ルーチンを開始するために、パラメーターの初期値としてランダムなセットを指定します:

x0 = 2 * np.pi * np.random.random(num_params)
x0
array([4.57320412, 3.73020365, 1.21451377, 4.75053118, 5.25328086,
       3.59546765, 5.47976166, 0.94069685, 3.91576757, 2.70726541,
       4.39118954, 2.79574758])

Qiskit PrimitivesV2の使用#

Exercise: Qiskit PrimitivesV2の使用#

Exercise

次に、新しい QiskitRuntimeService V2 primitivesを使ってみましょう: EstimatorV2SamplerV2 です。

新しい Estimator インターフェースでは、1つの回路と、その回路に対する複数の観測値やパラメーター値のセットを指定することができ、パラメーター値のセットや観測値に対するスイープを効率的に指定することができます。 以前は、結合するデータのサイズに合わせて、同じ回路を複数回指定する必要がありました。 また、引き続き、optimization_level と resilience_level を簡単なレベル調整として使用することができ、さらに V2 primitives では、個々のエラー緩和/抑制メソッドをオン/オフして、ニーズに合わせてカスタマイズできる柔軟性があります。

Sampler V2 は、量子回路の実行から量子レジスターをサンプリングするというコアタスクに集中するために簡素化されています。 プログラムによって定義されたタイプのサンプルを重みなしで返します。 また、出力データはプログラムで定義された出力レジスター名で区切られます。 この変更により、将来的には古典的な制御フローを持つ回路のサポートが可能になります。

また、Qiskitの1.0 の local testing mode を使用します。local testing mode (qiskit-ibm-runtime 0.22.0以降で利用可能) は、プログラムを微調整して実際の量子ハードウェアに送信する前に、プログラムの開発やテストに役立ちます。

あなたのタスク: local testing mode を使用してプログラムを検証した後、IBM Quantum システムで実行するためにバックエンド名を変更するだけです。

##### Select a Backend
##### Use FakeSherbrooke to simulate with noise that matches closer to the real experiment. This will run slower.
##### Use AerSimulator to simulate without noise to quickly iterate. This will run faster.

backend = FakeSherbrooke()
##### backend = AerSimulator()

##### Don't change any code past this line #####

##### Here we have updated the cost function to return only the energy to be compatible with recent scipy versions (>=1.10)
def cost_func_2(*args, **kwargs):
    energy, result = cost_func(*args, **kwargs)
    return energy

with Session(backend=backend) as session:
    estimator = Estimator(session=session)

    res = minimize(
        cost_func_2,
        x0,
        args=(isa_circuit, hamiltonian_isa, estimator, callback_dict),
        method="cobyla",
        options={'maxiter': 30})
### Select a Backend
## Use FakeSherbrooke to simulate with noise that matches closer to the real experiment. This will run slower.
## Use AerSimulator to simulate without noise to quickly iterate. This will run faster.

backend = FakeSherbrooke()
# backend = AerSimulator()

# ### Don't change any code past this line ###

# Here we have updated the cost function to return only the energy to be compatible with recent scipy versions (>=1.10)
def cost_func_2(*args, **kwargs):
    energy, result = cost_func(*args, **kwargs)
    return energy

with Session(backend=backend) as session:
    estimator = Estimator(session=session)

    res = minimize(
        cost_func_2,
        x0,
        args=(isa_circuit, hamiltonian_isa, estimator, callback_dict),
        method="cobyla",
        options={'maxiter': 30})
/Users/daimura/workspace/quantum/ibm-quantum-challenge-textbook/.venv/lib/python3.11/site-packages/qiskit_ibm_runtime/session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
  warnings.warn(
-0.81201171875
0.21240234375
-0.33837890625
-0.18994140625
-0.70361328125
-0.86328125
-0.841796875
-0.91552734375
-0.21533203125
-0.5869140625
-0.87158203125
-0.921875
-0.8759765625
-2.384765625
-2.8447265625
-2.73046875
-2.62353515625
-2.36376953125
-2.82373046875
-2.35302734375
-2.826171875
-2.32958984375
-2.84716796875
-2.80517578125
-2.60986328125
-2.849609375
-2.810546875
-2.83203125
-2.69677734375
-2.853515625

Tip

最適化が見られない場合は、maximiterを変更する もしCostが最小化されない場合は、 maximeter を上げて再度実行してください (100が適切)。

結果を見てみましょう:

fig, ax = plt.subplots()
plt.plot(range(callback_dict["iters"]), callback_dict["cost_history"])
plt.xlabel("Energy")
plt.ylabel("Cost")
plt.draw()
_images/7e918a1ffbb0d0653afb4890705376efaf6594a63b250cb6ca59ab8fa4d1a6a5.png

🎉 予想通り、我々の VQE アルゴリズムは、基底状態に到達するまでの反復においてエネルギーを最小化しました。 Qiskit 1.0 の新機能を使った VQE アルゴリズムの実装に成功しました!