Hello AHS: 最初のアナログハミルトンシミュレーションを実行する - Amazon Braket

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

Hello AHS: 最初のアナログハミルトンシミュレーションを実行する

このセクションでは、最初のアナログハミルトニアンシミュレーションの実行について説明します。

インタラクションスピンチェーン

多くの相互作用するパーティクルのシステムの正規例については、8 つのスピンのリング (それぞれが「上」∣↑⟩ および「下」∣↓⟩ 状態になる可能性があります) を考えてみましょう。このモデルシステムは、小規模ではありますが、自然に発生する磁気マテリアルの興味深い現象をすでにいくつか示しています。この例では、連続する回転が反対方向を向く、いわゆる抗強炭素順序を準備する方法を示します。

上下反転矢印を含む 8 つの円ノードを接続する図。

配置

1 つの中立アトムを使用して各スピンを待機し、「上」と「下」のスピン状態は、それぞれアトムの勾配 Rydberg 状態と地面状態にエンコードされます。まず、2 次元配置を作成します。上記のスピンリングは、次のコードでプログラムできます。

前提条件: Braket SDK を pip インストールする必要があります。(Braket がホストするノートブックインスタンスを使用している場合、この SDK にはノートブックがプリインストールされています)。プロットを再現するには、シェルコマンド を使用して matplotlib を個別にインストールする必要もありますpip install matplotlib

import numpy as np import matplotlib.pyplot as plt # required for plotting from braket.ahs.atom_arrangement import AtomArrangement a = 5.7e-6 # nearest-neighbor separation (in meters) register = AtomArrangement() register.add(np.array([0.5, 0.5 + 1/np.sqrt(2)]) * a) register.add(np.array([0.5 + 1/np.sqrt(2), 0.5]) * a) register.add(np.array([0.5 + 1/np.sqrt(2), - 0.5]) * a) register.add(np.array([0.5, - 0.5 - 1/np.sqrt(2)]) * a) register.add(np.array([-0.5, - 0.5 - 1/np.sqrt(2)]) * a) register.add(np.array([-0.5 - 1/np.sqrt(2), - 0.5]) * a) register.add(np.array([-0.5 - 1/np.sqrt(2), 0.5]) * a) register.add(np.array([-0.5, 0.5 + 1/np.sqrt(2)]) * a)

また、 を使用してプロットすることもできます。

fig, ax = plt.subplots(1, 1, figsize=(7,7)) xs, ys = [register.coordinate_list(dim) for dim in (0, 1)] ax.plot(xs, ys, 'r.', ms=15) for idx, (x, y) in enumerate(zip(xs, ys)): ax.text(x, y, f" {idx}", fontsize=12) plt.show() # this will show the plot below in an ipython or jupyter session
両方の軸の正の値と負の値に分散されたポイントを示す散布図。

インタラクション

抗強炭素フェーズを準備するには、隣接するスピン間の相互作用を誘発する必要があります。これには van der Waals インタラクションを使用します。これは、中立な原子デバイス ( のAquilaデバイスなど) によってネイティブに実装されますQuEra。スピン表現を使用すると、このインタラクションのハミルトニアン項を、すべてのスピンペア (j,k) の合計として表現できます。

この相互作用をすべてのスピンペア (j,k) の合計で表したハミルトニアン相互作用方程式。

ここで、nj =jj∣↑⟩⟨↑∣ は、スピン j が「up」状態である場合にのみ 1 の値を、それ以外の場合は 0 の値を取る演算子です。強度は Vj,k=C6/(dj,k​6 で、C 6は固定係数、d j,kはスピン j と k の間のユークリッド距離です。この相互作用項の即時効果は、スピン j とスピン k の両方が「アップ」している状態がエネルギーを (V の量で) 増加させることですj,k。AHS プログラムの残りの部分を慎重に設計することで、この相互作用により、隣接するスピンが両方とも「アップ」状態になるのを防ぐことができます。これは、一般的に「Rydberg ブロック」と呼ばれる効果です。

運転フィールド

AHS プログラムの開始時、すべてのスピン (デフォルトでは) は「ダウン」状態で開始され、いわゆる強分解フェーズにあります。強炭素対策フェーズを準備するという目標に留意して、この状態から「アップ」状態が優先される多体状態にスピンをスムーズに移行する時間依存のコヒーレント駆動フィールドを指定します。対応するハミルトニアンは次のように記述できます。

ハミルトニアンドライブ関数の計算を表す数式。

ここで、""(t)、""(t)、Δ(t) は、すべてのスピンに均一に影響を与える駆動フィールドの時間依存、グローバル振幅 (Rabi 周波数)、フェーズ、およびデチューニングです。ここで、S−,k=k​k∣↓⟩⟨↑∣ と S+,k=(S−,k=k​k∣↑⟩⟨↓∣ はスピン k の降格演算子と降格演算子であり、nk=kk∣↑⟩⟨↑∣ は以前と同じ演算子です。走行フィールドの "" 部分は、すべてのスピンの "ダウン" 状態と "アップ" 状態を一貫して結合し、" 部分は "アップ" 状態のエネルギー報酬を制御します。

強分解フェーズから強分解フェーズへのスムーズな移行をプログラムするには、次のコードで駆動フィールドを指定します。

from braket.timings.time_series import TimeSeries from braket.ahs.driving_field import DrivingField # smooth transition from "down" to "up" state time_max = 4e-6 # seconds time_ramp = 1e-7 # seconds omega_max = 6300000.0 # rad / sec delta_start = -5 * omega_max delta_end = 5 * omega_max omega = TimeSeries() omega.put(0.0, 0.0) omega.put(time_ramp, omega_max) omega.put(time_max - time_ramp, omega_max) omega.put(time_max, 0.0) delta = TimeSeries() delta.put(0.0, delta_start) delta.put(time_ramp, delta_start) delta.put(time_max - time_ramp, delta_end) delta.put(time_max, delta_end) phi = TimeSeries().put(0.0, 0.0).put(time_max, 0.0) drive = DrivingField( amplitude=omega, phase=phi, detuning=delta )

次のスクリプトを使用して、運転フィールドの時系列を視覚化できます。

fig, axes = plt.subplots(3, 1, figsize=(12, 7), sharex=True) ax = axes[0] time_series = drive.amplitude.time_series ax.plot(time_series.times(), time_series.values(), '.-'); ax.grid() ax.set_ylabel('Omega [rad/s]') ax = axes[1] time_series = drive.detuning.time_series ax.plot(time_series.times(), time_series.values(), '.-'); ax.grid() ax.set_ylabel('Delta [rad/s]') ax = axes[2] time_series = drive.phase.time_series # Note: time series of phase is understood as a piecewise constant function ax.step(time_series.times(), time_series.values(), '.-', where='post'); ax.set_ylabel('phi [rad]') ax.grid() ax.set_xlabel('time [s]') plt.show() # this will show the plot below in an ipython or jupyter session
経時的な phi、delta、omega を示す 3 つのグラフ。上部のサブプロットは、6 rads/s のすぐ上までの増加を示し、0 に戻るまで 4 秒間留まります。中央のサブプロットは、関連する派生の線形成長を示し、下部のサブプロットはゼロに近い平坦な線を示します。

AHS プログラム

レジスタ、運転フィールド (および暗黙的な van der Waals インタラクション) は、アナログハミルトンシミュレーションプログラム を構成しますahs_program

from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation ahs_program = AnalogHamiltonianSimulation( register=register, hamiltonian=drive )

ローカルシミュレーターでの実行

この例は小さい (15 回未満のスピン) ため、AHS 互換 QPU で実行する前に、Braket SDK に付属するローカル AHS シミュレーターで実行できます。ローカルシミュレーターは Braket SDK で無料で利用できるため、コードを正しく実行できるようにするためのベストプラクティスです。

ここでは、ショットの数を高い値 (100 万個など) に設定できます。これは、ローカルシミュレーターが量子状態の時間進化を追跡し、最終状態からサンプルを引き出すためです。つまり、ショットの数を増やしながら、合計ランタイムをわずかに増やすだけです。

from braket.devices import LocalSimulator device = LocalSimulator("braket_ahs") result_simulator = device.run( ahs_program, shots=1_000_000 ).result() # takes about 5 seconds

シミュレーターの結果の分析

各スピンの状態 (「ダウン」の場合は「d」、「アップ」の場合は「u」、または空のサイトの場合は「e」) を推測する次の関数を使用してショット結果を集計し、ショット全体で各設定が発生した回数をカウントできます。

from collections import Counter def get_counts(result): """Aggregate state counts from AHS shot results A count of strings (of length = # of spins) are returned, where each character denotes the state of a spin (site): e: empty site u: up state spin d: down state spin Args: result (braket.tasks.analog_hamiltonian_simulation_quantum_task_result.AnalogHamiltonianSimulationQuantumTaskResult) Returns dict: number of times each state configuration is measured """ state_counts = Counter() states = ['e', 'u', 'd'] for shot in result.measurements: pre = shot.pre_sequence post = shot.post_sequence state_idx = np.array(pre) * (1 + np.array(post)) state = "".join(map(lambda s_idx: states[s_idx], state_idx)) state_counts.update((state,)) return dict(state_counts) counts_simulator = get_counts(result_simulator) # takes about 5 seconds print(counts_simulator)
{'udududud': 330944, 'dudududu': 329576, 'dududdud': 38033, ...}

以下はcounts、ショット全体で各状態設定が観測された回数をカウントするディクショナリです。また、次のコードで視覚化することもできます。

from collections import Counter def has_neighboring_up_states(state): if 'uu' in state: return True if state[0] == 'u' and state[-1] == 'u': return True return False def number_of_up_states(state): return Counter(state)['u'] def plot_counts(counts): non_blockaded = [] blockaded = [] for state, count in counts.items(): if not has_neighboring_up_states(state): collection = non_blockaded else: collection = blockaded collection.append((state, count, number_of_up_states(state))) blockaded.sort(key=lambda _: _[1], reverse=True) non_blockaded.sort(key=lambda _: _[1], reverse=True) for configurations, name in zip((non_blockaded, blockaded), ('no neighboring "up" states', 'some neighboring "up" states')): plt.figure(figsize=(14, 3)) plt.bar(range(len(configurations)), [item[1] for item in configurations]) plt.xticks(range(len(configurations))) plt.gca().set_xticklabels([item[0] for item in configurations], rotation=90) plt.ylabel('shots') plt.grid(axis='y') plt.title(f'{name} configurations') plt.show() plot_counts(counts_simulator)
隣接する「アップ」状態設定がない多数のショットを示す棒グラフ。
隣接する「up」状態設定の一部のショットを示す棒グラフ。状態は高い値から低い値に降順です。

プロットから、次の観測値を読み、強分解防止フェーズを正常に準備したことを確認できます。

  1. 一般に、ブロックされていない状態 (2 つの隣接するスピンが「アップ」状態ではない状態) は、少なくとも 1 つの隣接するスピンのペアが両方とも「アップ」状態である状態よりも一般的です。

  2. 一般的に、設定がブロックされない限り、「アップ」の引用が多い状態が優先されます。

  3. 最も一般的な状態は、実際には完全なアンチ強皮状態 "dudududu"と です"udududud"

  4. 2 番目によくある状態は、連続して 1、2、2 の分離で 3 つの「アップ」な興奮しか存在しない状態です。これは、van der Waals のインタラクションが、最近傍にも影響する (ただし、はるかに小さい) ことを示しています。

QuEra の Aquila QPU での実行

前提条件: Braket SDK のインストールに加えて、Amazon Braket を初めて使用する場合は、必要な開始手順を完了していることを確認してください。

注記

Braket がホストするノートブックインスタンスを使用している場合、Braket SDK にはインスタンスがプリインストールされています。

すべての依存関係がインストールされると、AquilaQPU に接続できます。

from braket.aws import AwsDevice aquila_qpu = AwsDevice("arn:aws:braket:us-east-1::device/qpu/quera/Aquila")

AHS プログラムをQuEraマシンに適したものにするには、AquilaQPU で許可されている精度レベルに準拠するようにすべての値を丸める必要があります。(これらの要件は、名前に「Resolution」が付いたデバイスパラメータによって管理されます。 ノートブックaquila_qpu.properties.dict()で を実行すると、それらを確認できます。 Aquila の機能と要件の詳細については、「Aquila の概要ノートブック」を参照してください。) これを行うには、 discretizeメソッドを呼び出します。

discretized_ahs_program = ahs_program.discretize(aquila_qpu)

これで、AquilaQPU でプログラム (現在 100 ショットのみを実行) を実行できます。

注記

プロセッサでこのプログラムを実行すると、コストAquilaが発生します。Amazon Braket SDK には、お客様がコスト制限を設定し、ほぼリアルタイムでコストを追跡できる Cost Tracker が含まれています。

task = aquila_qpu.run(discretized_ahs_program, shots=100) metadata = task.metadata() task_arn = metadata['quantumTaskArn'] task_status = metadata['status'] print(f"ARN: {task_arn}") print(f"status: {task_status}")
task ARN: arn:aws:braket:us-east-1:123456789012:quantum-task/12345678-90ab-cdef-1234-567890abcdef task status: CREATED

量子タスクの実行にかかる時間は大きく異なるため (可用性ウィンドウと QPU 使用率によって異なります)、量子タスク ARN を書き留めておくことをお勧めします。このため、後で次のコードスニペットでステータスを確認できます。

# Optionally, in a new python session from braket.aws import AwsQuantumTask SAVED_TASK_ARN = "arn:aws:braket:us-east-1:123456789012:quantum-task/12345678-90ab-cdef-1234-567890abcdef" task = AwsQuantumTask(arn=SAVED_TASK_ARN) metadata = task.metadata() task_arn = metadata['quantumTaskArn'] task_status = metadata['status'] print(f"ARN: {task_arn}") print(f"status: {task_status}")
*[Output]* task ARN: arn:aws:braket:us-east-1:123456789012:quantum-task/12345678-90ab-cdef-1234-567890abcdef task status: COMPLETED

ステータスが完了すると (Amazon Braket コンソールの量子タスクページから確認することもできます)、次の方法で結果をクエリできます。

result_aquila = task.result()

QPU 結果の分析

以前と同じget_counts関数を使用して、カウントを計算できます。

counts_aquila = get_counts(result_aquila) print(counts_aquila)
*[Output]* {'udududud': 24, 'dudududu': 17, 'dududdud': 3, ...}

と でプロットしますplot_counts

plot_counts(counts_aquila)
隣接する「アップ」状態の有無にかかわらず、ショットのレートを示す棒グラフ。

ごく一部のショットに空のサイト (「e」でマーク) があることに注意してください。これは、AquilaQPU の原子ごとの準備の不完全性が 1~2% であるためです。これとは別に、ショット数が少ないため、結果は予想される統計的変動内でシミュレーションと一致します。

次のステップ

これで、ローカル AHS シミュレーターと Aquila QPU を使用して Amazon Braket で最初の AHS ワークロードを実行できました。

Rydberg の物理、アナログハミルトニアンシミュレーション、および Aquila デバイスの詳細については、サンプルノートブックを参照してください。