シェーディングされたライトコーンを用いた確率的誤差相殺
所要時間の目安:Heronプロセッサで10分(注:これはあくまで目安です。 (実行時間は状況によって異なる場合があります。)
学習成果
このチュートリアルを学習した後、ユーザーは以下の点を理解できるようになります:
- 確率的誤差相殺(PEC)とは何か、そしてなぜそのサンプリングオーバーヘッド が、回路に作用する総ノイズとともに指数関数的に増加するのか
- シェーディングされたライトコーン(SLC)が、各ノイズ項のターゲット観測量への寄与をどのように制限し、重要な部分に緩和リソースを集中できるようにするのか――そして、純粋に幾何学的なライトコーンがすでに提供するバイアスゼロのオーバーヘッド低減に加え、このシェーディングによって、制限された残留バイアスと引き換えに、さらに低いサンプリングオーバーヘッドを実現できる仕組みについて
- とを用いて
NoiseLearnerV3レイヤーノイズを学習し、とExecutorプリミティブを通じてsamplomaticアンチノイズを注入する方法 - ハードウェア上で期待値を推定するために、PECおよびPEC+SLCをTREXおよびポストセレクションと組み合わせる方法
前提条件
このチュートリアルを進める前に、以下のトピックについてあらかじめ理解しておいていただくことをお勧めします:
- Qiskit のパターンワークフロー
- Estimatorプリミティブを用いた観測変数の期待値の計算
- 「推定器プリミティブとエラー軽減オプションの組み合わせ」 で取り上げられている、パウリ・トゥワーリングやTREXなどのエラー軽減手法
背景
このチュートリアルでは、shaded lightcone(SLC)アドオンを使用してエラーを軽減する方法について解説します。 このアドオンは、 確率的誤差相殺(PEC)手法を発展させたものであり、ユーザーが回路内の個々の層のノイズを学習し、単一量子ビットゲートや後処理技術を適用することで、そのノイズを相殺するものです。 他の手法と比較して、PECはバイアスが軽減された結果に対するより堅牢な境界を提供しますが、QPUの処理時間という点ではオーバーヘッドが大きくなる傾向があります。 PECの実施中、ノイズによる期待値の減衰を補正するために、平均結果は という倍率で再スケーリングされる。ここで、 は、回路の層 における、学習されたノイズ誤差率(Pauli )である。 この再スケーリングにより、分散は 倍に増加し、その結果、QPU上で必要な回路実行回数も 倍になる。これを「サンプリングコスト」または「サンプリングオーバーヘッド」と呼ぶ。 は指数関数的に増加するため、PECは多くの場合、浅い回路やクビット数の少ない回路に限定される。 「ノイズの多い量子プロセッサにおける疎なパウリ・リンドブラッドモデルを用いた確率的誤差キャンセル」 で、PECについてさらに詳しく学びましょう。
軽減する必要のないエラーを特定できれば、このサンプリングコストを指数関数的に削減できる。 この方向への第一歩として、局所性を考慮したエラー緩和手法の実装が挙げられる。この手法では、高速に計算可能な従来の「ライトコーン」を用いて、回路全体における観測量の誤差に対する感度を制限することでPECのオーバーヘッドを低減し、一部の問題においてPECの適用範囲をより大規模なものへと拡大する。 この光錐の外側にある誤差は、測定結果に影響を及ぼすことはできないため、誤差相殺の手順から除外することができる。 この除外により、追加のバイアスを生じさせることなく、サンプリングのオーバーヘッドを、場合によっては大幅に低減することができる。 特に、固定深さの回路の局所観測量 を測定する場合、回路内の量子ビット数を拡大しても、必要なサンプリングオーバーヘッドは最終的に頭打ちとなる(『 量子回路の局所性とエラー緩和 』の図 2b を参照)。
シェーデッド・ライトコーン(SLC)はさらに一歩進んで、古典シミュレーションを用いて、回路全体にわたる誤差に対する感度をより厳密に制限しています。 これにより、QPUの処理時間を多少犠牲にしてCPUの処理時間を確保し、バイアスの再正規化に必要なサンプリングのオーバーヘッドを低減します。 厳格なカットオフ値を設ける代わりに、回路内の各潜在的な誤差には、その誤差に対する観測量の期待値の影響を受けやすさの上限を示す、段階的な「シェード」が割り当てられる。 (後述するバックプロパゲーションによる境界値については、この感受性は回路の初期状態にも依存します。一般的なワークフローや本チュートリアルでは、初期状態はすべてゼロの状態となります。) この精緻な特性解析により、PECをより効率的かつ的を絞って適用し、ばらつきを低減できると同時に、ユーザーは観測可能な推定値におけるバイアスを制御的に調整することが可能となる。 詳細については、 「古典的加速による量子エラー低減のためのライトコーンシェーディング」 を参照してください。
SLCアドオンのワークフローでは、ライブラ samplomatic リと、 v0.47.0 で qiskit-ibm-runtimeQiskit Runtime に追加されたおよびクラスを活用しており、これにより Executor``QuantumProgram 、ユーザーは使いやすさを維持しつつ、エラーの抑制や軽減に関する実行設定をよりモジュール的に制御できるようになります。 詳細については、「 指示型実行モデルガイド」をご覧ください。
SLCエラー軽減ワークフローの概要
QPUのノイズをモデル化するために、1クビットおよび2クビットのパウリ誤り率を持つ疎なパウリ・リンドブラッドノイズモデルを用い、デバイスの各クビットおよびエッジで局所的に生成されるように設定する。 この選択により、本チュートリアルで紹介するSLCエラー軽減ワークフローは以下のようになります:
a. CPU — 1クビットおよび2クビットのパウリ誤りによる、誤りごとの影響の限界
- 前方伝播(観測量に対する境界効果)。 各誤差を回路の末端まで伝播させ、観測量とのコミューテータを計算する。
- 計算を扱いやすい範囲に収めるため、進化の過程で演算子の項を切り捨てる。
- 量子速度の限界に基づいて、観測可能量のバックプロパゲーションを緩やかに行うことで、これらの境界をさらに厳密化する。
- 逆伝播(初期状態に対する境界効果)。 各誤差を回路の始点まで伝播させ、初期状態とのコミューテータを計算する。
b. QPU — ノイズ発生率について学ぶ。 を使用して NoiseLearnerV3 、パウリ・リンドブラッド雑音モデルの率を推定します。
c. CPU — 緩和策を優先する
- 学習されたノイズ率を用いて、境界を更新する。 以前に計算された前方境界と後方境界を組み合わせ、学習されたノイズ率を用いてそれらを更新する。
- 計算された範囲と学習されたレートを用いて、軽減すべきランクノイズの構成要素をランク付けする。 バイアスに与える推定影響度および修正にかかる費用に基づいて、考えられる各ノイズ誤差の優先順位を決定する。
d. QPU — ノイズ対策機能を挿入して実行する。 アノテーションを使用して Box 指定したアンチノイズ(逆ノイズ)を適用し、対象の回路を実行します。
e. CPU — 推定値は観測可能。 非マルコフノイズの影響を低減するために、測定に基づく事後選択を適用して、期待値を計算する。
ノイズ学習の概要
ノイズ学習は、いくつかの誤差低減手法において一般的な手順であり、 ノイズ学習器によって実行されます。また、 確率的誤差増幅のチュートリアルでも取り上げられています。 NoiseLearnerV3では、学習対象となるノイズ層をオブジェクトとして CircuitInstruction 具体的に特定できるため、前述の手順に従って各層について所望のSLCノイズ境界を計算することができます。 学習済みパウリ・リンドブラッドモデルは、PEC+SLCの優先順位付けに使用される係数を提供します。 generate_noise_model_paulisゲートを層ごとにまとめる方法は、 および find_unique_box_instructions という generate_boxing_pass_manager 便利関数を使用して決定し、その後、以下のステップ2で説明するように、SLCユーティリティ関数 に渡しることができます。
パート 1 | パート 2 | パート 3 |
|---|---|---|
| パウリ・ツイール2量子ビットゲート層 | 層の同一ペアを繰り返し、ノイズを学習する | 忠実度(各ノイズチャネルごとの誤差)を導出する |
![]() | ![]() | ![]() |
後処理の概要
および Executor フレーム samplomatic ワークを用いて量子ハードウェア上で実行した後、ビットストリングの測定結果を所望の観測量に変換します。 このチュートリアルでは、ミラーリングされたアイジング回路(以下のステップ1で構築したもの)を用いたワークフローを解説します。この回路では、回路の後半部分が前半部分の作用を打ち消すため、測定された観測量の理想的な期待値は1となります。 関 executor_expectation_values 数を用いて観測値を計算する際には、ノイズの影響を低減するためのいくつかの後処理手法を適用しています。 これには、非マルコフノイズの影響を受けたショットの除去、読み出し誤差の低減、および当研究におけるPEC実装の詳細への配慮などが含まれる。 詳細については、以下のステップ4で説明します。
要件
このチュートリアルを始める前に、以下のものがインストールされていることを確認してください:
- Qiskit SDK v2.2 またはそれ以降で、 可視化機能をサポートしているもの
- Qiskit Runtime v0.47 またはそれ以降 (
pip install qiskit-ibm-runtime) - シェーディング付きライトコーン Qiskit アドオン v0.1 以降 (
pip install qiskit-addon-slc) - Qiskit アドオン utils v0.3 以降 (
pip install qiskit-addon-utils) - Samplomatic v0.13 以降 (
pip install samplomatic)
セットアップ
まず、このノートブックを実行するために必要なパッケージと関数をインポートします。
from multiprocessing import set_start_method
# Setting this value prevents itertools.starmap deadlock on UNIX systems
set_start_method("spawn")
# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1Output:
env: OMP_NUM_THREADS=1
import numpy as np
from matplotlib import pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
import samplomatic
from samplomatic.utils import find_unique_box_instructions
from samplomatic.transpiler import generate_boxing_pass_manager
from qiskit_addon_utils.exp_vals.measurement_bases import (
get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
executor_expectation_values,
)
from qiskit_addon_utils.noise_management import (
gamma_from_noisy_boxes,
trex_factors,
)
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
compute_local_scales,
merge_bounds,
tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import (
generate_noise_model_paulis,
map_modifier_ref_to_ref,
)
from qiskit_addon_slc.visualization import draw_shaded_lightcone小規模シミュレータの例
他の学習ベースのエラー低減手法と同様、シェード付きライトコーンを用いたPECは、特定の量子プロセッサに固有の物理的ノイズを低減するものであるため、理想的なシミュレータ上には実質的な対応物がないハードウェア機能に依存している:
NoiseLearnerV3各固有の2量子ビット層において、スパースなパウリ・リンドブラッドノイズチャネルを実験的に特徴づける。 ノイズのないシミュレータでは、除去すべきノイズは存在しない。- この
Executorプリミティブは、バックエンド上でsamplomatic生成された、ツイール処理が施され、アンチノイズが注入された回路のサンプルを生成します。
シェーデッド・ライトコーンの境界の計算は古典的な手法ですが、これは学習されたハードウェアノイズ率を基準としてのみ意味を持ちます。このノイズ率が、緩和の許容範囲とサンプリングのオーバーヘッドを決定するからです。 こうした理由から、小規模なシミュレータの例は省略し、ハードウェア上で直接PEC+SLCの完全なワークフローを実演します。Qiskitパターンの各ステップについては、以下に詳しく説明します。
大規模なハードウェアの例
IBM Quantum® ハードウェア上で実行される20キュービットのミラーリングされたイジング回路を用いて、Qiskitのパターンに示された4つのステップに従い、PEC+SLCワークフロー全体を実行しました。
ステップ1:問題の整理
今回のデモンストレーションは、次のように構成されています:
- ここでは、 1D のイジング鎖ハミルトニアンのもとで時間発展を行うことにする。 1D のアイジング鎖は、非常に緻密な回路構造を形成しており、PECの実装例を示すのに適しています。
- ここでは、観測量 を測定することにした。この観測量には、 成分と 成分がそれぞれ1つずつ含まれている。 これは、SLCの境界がさまざまな種類のエラーをどのように異なる扱いをするかを強調するのに便利です。
- この観測量の理想的な期待値が正確に1になるようにしたい。そうすれば、緩和された推定値の質を判断しやすくなるからだ。 回路を鏡像反転させれば、ほぼ目的が達成されます。回路の後半にある各ゲートに対して、前半にはその逆のゲートが存在するため、理想的な回路は恒等変換として機能するのです。
- 最後に、期待値が1という理想的な値を得るためには、回路が観測量の+1の固有状態から開始し――したがって、ミラーリングのおかげで、理想的にはその固有状態で終了することも必要となる。 観測可能量は非 な成分を含んでおり、プリミティブ
Executorは回路の終端における所望の測定基底を規定しているため、関prepare_basis数を用いて、ミラー回路の先頭に、対応する基底準備ゲートを付加する。 「」get_measurement_bases関数を用いて、どのゲートが必要で、どこに配置すべきかを特定するとともに、「標準基底測定」のセクションで論じたように、注釈の規約からbox生じる量子ビットインデックスの微妙な点にも注意を払います。
# Width of the Ising chain (a free parameter of this demonstration)
num_qubits = 20
# The observable to measure: one X component (on qubit 6) and one Z component
# (on qubit 13), as motivated in "Step 1: Map the problem" above
target_obs_sparse = [("XZ", [6, 13], 1.0)]observable = SparsePauliOp.from_sparse_list(
target_obs_sparse, num_qubits=num_qubits
)# Determine the measurement basis each qubit requires for this observable.
# The reverser is used during post-processing in Step 4.
bases_virt, reverser_virt = get_measurement_bases(observable)# Free parameters of the demonstration circuit: the number of Trotter steps
# sets the circuit depth, and rx_angle is the transverse-field rotation angle
# applied in each step (pi/4 makes the dynamics non-Clifford)
num_trotter_steps = 10
rx_angle = np.pi / 4def construct_ising_circuit(
num_qubits: int,
num_trotter_steps: int,
rx_angle: float,
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)
for _ in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
for first_qubit in (1, 2):
for idx in range(first_qubit, num_qubits, 2):
# equivalent to Rzz(-pi/2):
circuit.sdg([idx - 1, idx])
circuit.cz(idx - 1, idx)
return circuit
def prepare_basis(
circuit: QuantumCircuit, basis: list[int]
) -> QuantumCircuit:
# basis is a list of integer values from 0 to 3. These map to the basis measurement as:
# 0 = I; 1 = Z; 2 = X; 3 = Y
assert len(basis) == circuit.num_qubits
out_circ = circuit.copy_empty_like()
for qb, bas in enumerate(basis):
if bas == 2:
out_circ.h(qb)
elif bas == 3:
out_circ.rx(-np.pi / 2, qb)
out_circ.barrier()
out_circ.compose(circuit, inplace=True)
return out_circ
def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.measure_active()
return mirror_circ# Instantiate the mirrored circuit and prepend the basis-preparation gates
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)
mirrored_circuit = mirror_circuit(circuit)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])mirrored_circuit.draw(
"mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False
)Output:
ステップ 2:最適化
実行する回路、測定対象の観測量、およびノイズ学習パラメータに関連する詳細を最適化します。 まず、負荷が最も低いHeronプロセッサを選択し、分数ゲートを有効にしてインスタンス化します。 これらのフラクショナルゲートにより、当社のポストセレクションフィルタリングの一部において、より高い感度を実現できます。
# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()
# Select the least busy Heron device. This tutorial targets Heron processors:
# the usage estimate and the non-Markovian-noise post-selection discussed in
# Step 4 are specific to this processor family.
backend = service.least_busy(
operational=True,
simulator=False,
filters=lambda b: b.processor_type["family"] == "Heron",
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")Output:
Selected backend: ibm_kingston
まず、 QPU上で実行するために必要な手順として、回路をISA命令にトランスパイルします。 実行に用いる物理量子ビットの選択は、結果の品質に大きな影響を及ぼします。これは、コヒーレンス時間やゲートの忠実度がデバイスごとに異なり、時間の経過とともに変動するためです。 綿密な実験を行う場合、簡単なベンチマーク実験の結果を踏まえてクビットの連鎖を手作業で厳選することは、成果につながる可能性があります(例えば、「 クビット選択のためのリアルタイム・ベンチマーク 」チュートリアルを参照してください)。 このチュートリアルの目的上、バックエンドのカップリングマップと最新のキャリブレーションデータに基づいてレイアウトを選択するために、Qiskitトランスパイラーを利用します。
isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = isa_pm.run(mirrored_circuit)
# The chain of physical qubits selected by the transpiler
layout = isa_circuit.layout.final_index_layout()
print(f"Selected layout: {layout}")
isa_observable = observable.apply_layout(
layout, num_qubits=isa_circuit.num_qubits
)Output:
Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]
wire_order = layout + [
q for q in range(isa_circuit.num_qubits) if q not in layout
]
isa_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)Output:
回路を枠で囲む
実装を容易にするため、回路命令を注釈付きボックスに配置するトランスパイレーションパス generate_boxing_pass_manager を利用しています。 これらのボックスは、PECの場合、回路のどの部分にアンチノイズを注入すべきかを明確に示しています。 設定の詳細については、 Samplomatic のドキュメントを参照してください。
なお、SLCワークフローでは、プロセスの後半で を使用 inject_noise_strategy="individual_modification" する必要があります。これにより、回路内の 命令 BoxOp に対する 注釈 InjectNoise を一意に識別できるようになるためです。 (注釈 InjectNoise のないボックスは、この方法では特定できません。)
SLCバウンド計算では、現在、アノテーションが InjectNoise に site="after" 配置されていることを前提としています( qiskit-addon-slc#33 を参照)。 この設定のデフォルト値は、最近のバージョンで samplomatic 変更されたため( samplomatic#372 を参照)、以下で明示的に設定 inject_noise_site="after" します。
この find_unique_box_instructions 関数は、指定されたボックス化された回路を反復処理し、ノイズ学習およびノイズ注入を目的として、一意の2量子ビット( 2Q )層または測定を持つものを特定します。
# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
twirling_strategy="active",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
inject_noise_targets="gates",
measure_annotations="all",
)
boxed_circuit = boxes_pm.run(isa_circuit)
# Find the unique instructions (layers) from boxed circuit
unique_2q_instructions = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)boxed_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)Output:
標準塩基の測定の準備
なお、量子ビットの順序を正確に把握するよう、特に注意を払う必要があります。 以下では、回路をボックス化したり一意の命令を特定したりする際にクビットの順序がどのように捉えられるかという点を踏まえ、Executor にクビットの順序を適切に更新して渡す手段として、の canonical_qubits 概念について紹介します。 詳細については、Qubitの順序付け規則に関するドキュメントを参照してください。
# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}
assert len(c_2_v) == num_qubits
bases_canon = [
np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base_i in bases_virt
]ライトコーンシェーディング、ノイズ学習、およびアンチノイズ注入のワークフロー
このチュートリアルでは、ノイズ学習が完了する前にSLCバウンド計算を実行するため、低減対象の回路は、学習されたノイズモデルに時間的に可能な限り近いタイミングで実行されます。 原則として、このワークフローはさらに並列化が可能です。つまり、ノイズ学習ジョブを実行しながら、並行してノイズの境界を推定することができます。 任意の量子回路において、ノイズに制約される計算は弱指数関数的な依存性でスケールするため、この制約計算を(例えば、多数のCPUコア間で)並列化することで、与えられた計算時間枠においてより厳しい制約を得ることができ、さらに、QPUによる実行と制約計算自体を並列化することで、最も効率的なワークフローを実現できる。
学習予定のノイズモデル「Paulis」を予測する
ハードウェア上でノイズ学習を実行する前に、まず、ノイズモデルにどのようなパウリ誤差項が含まれるかを列挙する。 各層のノイズは、各アクティブ量子ビット上の重み1のパウリ演算( 、 、または )ごとに1つのエラー率、および接続されたアクティブ量子ビットのペアごとに重み2のパウリ演算ごとに1つのエラー率を持つ疎なパウリ・リンドブラッドモデルとしてモデル化していることを思い出してほしい。 この generate_noise_model_paulis 関数は、指定された回路の個別のボックス化された各レイヤーを順に処理し、回路の量子ビット間の接続関係を考慮に入れて、まさにこのパウリ項のリストを生成します。
つまり、このステップでは、後で学習された発生率で埋められることになる NoiseLearnerV3 ノイズモデルの構造を予測するものである。 この構造を事前に把握しておくことで、ノイズ学習を実行する前から、発生しうるあらゆる誤差について、上界と下界を計算することが可能になる。
noise_model_paulis = generate_noise_model_paulis(
unique_2q_instructions, backend.coupling_map, boxed_circuit
)noise_model_rates = {ref: None for ref in noise_model_paulis}a. 前方境界を計算する
この compute_forward_bounds 関数は、各層のゲートと、前述のように生成されたパウリ項との間の可換関係を、順方向伝播による誤差が目的の観測量 にどのように影響するかという観点から評価する。パウリ項と可換なゲートについては、何もしない。 クリフォードゲートの場合、それらは回路の先頭側に配置されます。 クリフォードゲート以外のゲートについては、ターゲット観測量に対するそれらの影響を近似し、後で(すべての上限が統合された後に)ノイズ除去の優先順位付けに活用する。 この上界は、まず L2 ノルム(すなわち、関連するパウリ項の係数の二乗和の平方根)を適用することで得られる。 関与する量子ビット項が多すぎる場合は、三角不等式を用いたより緩やかな上界に立ち返る。
境界計算パラメータを設定する
以下の設定は、古典的な境界計算を制御します:
slc_atol: 計算の絶対許容誤差。 この閾値を下回る係数を持つ演算子の項は、無視できるものとみなされ、除外されます。slc_eigval_max_qubits: より厳密な、固有値に基づく( L2-norm )上界が評価される量子ビットの最大数。 これよりも多くの量子ビットでサポートされる演算については、前述のより緩やかな三角不等式の境界値が適用される。slc_evolution_max_terms: 回路内で演算子を伝播させる際に保持されるパウリ項の最大数。 最小の項を切り捨てることで、境界の精度がわずかに低下する代償を払う代わりに、計算を扱いやすいものに保つことができる。slc_num_processes: バウンド計算が並列化されるCPUプロセスの数。slc_timeout: 束縛計算にかかる時間(秒単位)。 この予算内で到達できなかったレイヤーには、2 という自明な境界値が割り当てられる。
このチュートリアルでは、タイムアウトに達するのは設計上の意図によるものです。ノートブックを高速に実行できるように、またタイムアウトに達しても安全であることを示すために、意図的にタイムアウト時間を短く設定 slc_timeout しています。 計算されたシェードは各段階における上限値であるため、タイムアウトが発生しても、到達しなかったレイヤーについてはその上限値が(自明な値へと)緩くなるだけであり、エラーの影響を過小評価することは決してありません。 これは、以下の可視化図において、左端のレイヤーがすべて自明な境界で塗りつぶされている様子として表れています。 タイムアウトを長くする(またはプロセス数を増やす)ことで、範囲が狭まり、サンプリングのオーバーヘッドがさらに軽減されます。
slc_atol = 1e-8
slc_eigval_max_qubits = 18
slc_evolution_max_terms = 1000
slc_num_processes = 8
slc_timeout = 60forward_bounds = compute_forward_bounds(
boxed_circuit,
noise_model_paulis,
isa_observable,
evolution_max_terms=slc_evolution_max_terms,
eigval_max_qubits=slc_eigval_max_qubits,
atol=slc_atol,
num_processes=slc_num_processes,
timeout=slc_timeout,
)Output:
Bounds computation timed out.
手作業による検査のためにSLCを可視化する
陰影で示された境界の挙動については、測定項とパウリ項が局所誤差とどのように相互作用するかを検討することで解釈することができます。 これらのパターンは、このキック付きアイジング・ハミルトニアンによる時間発展問題に特徴的なものであり、論文『 Lightcone Shading for Classically Accelerated Quantum Error Mitigation』 にも見られ、いくつかの顕著な特徴を備えている:
- 観測量に含まれる2つの非自明なパウリ演算から生じる2つのコーンを、はっきりと区別することができる。
- キュービット6に対するX測定は、最右層のXエラーと可換であることがわかる。
- キュービット13のZパウリ演算が、右端の層にあるZエラーと可換であることがわかります。
- 上記で指定されたタイムアウトに達すると、左側の残りの層はすべて、値が2の単純な境界で埋め尽くされます。
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
b. 量子速度の限界を用いて前方境界を厳密化する
次に、関数を tighten_with_speed_limit 用いて、その境界をさらに厳密にします。 観測対象の光錐の幾何学的広がりは、上記で計算された前方境界においてすでに完全に考慮されている。 この関数が追加するのは、回路内を情報が流れる速度の制限(2量子ビットゲートの層ごとに、ライトコーンは最大で1量子ビットずつしか拡大しない)を活用した自己一貫性チェックであり、これにより、以前に計算された各境界値間の関係をさらに厳密に調整する。 情報は双方向に流れています:
- コミューテーターの境界から、バックプロパゲーションされた観測量の境界について:ある観測量がクビット上の 誤差と可換である場合、その観測量には、 や の成分がほとんど含まれていないことになる。
- バックプロパゲーションされた観測量の境界から、コミューテーターの境界について:ある量子ビットにおいて、その観測量の および 成分がごくわずかであるならば、その量子ビットにおける とはほぼ可換でなければならない。
forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)手作業による検査のためにSLCを可視化する
原則として、この厳格化により、計算された境界と、タイムアウトを超えたレイヤーに割り当てられた自明な境界との間の移行がよりスムーズになります。 この例では、タイムアウトに達する頃には光錐がすでに回路の端まで広がってしまっているため、その影響はほとんど目に見えません。
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds_tighter,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
c. 後方境界を計算する
ノイズ予測のこの部分では、特定の層における誤差が、入力状態 にどのような影響を与えるかを評価する。この compute_backward_bounds 関数は、まず回路を反転させて測定ゲートを除去し、その後、順方向計算の場合と同様の解析を行う。
backward_bounds = compute_backward_bounds(
boxed_circuit,
noise_model_paulis,
evolution_max_terms=slc_evolution_max_terms,
num_processes=slc_num_processes,
timeout=slc_timeout,
)手作業による検査のためにSLCを可視化する
後方境界を計算することで、初期状態の構造が誤差伝播の初期段階の挙動をどのように左右しているかがわかります:
- Z誤差が、初期状態 |0⟩ と当初どのように可換であるかがはっきりと見て取れる。
- X基底の+1固有状態を初期化する量子ビット6においてのみ、Zエラーは可換性を満たさないのに対し、Xエラーは可換性を満たす。
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
backward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
学習されたノイズ率を使用せずに、統合された境界をプレビューする
この merged_bounds 関数は、回路内のどの点で、逆方向の境界から順方向の境界への切り替えを行うと、目的の観測量に対する推定バイアスの総和が最小になるかを決定する。 このバイアスは、その点より前のすべてのノイズ位置に対する後方方向の寄与の合計に、その点より後のすべてのノイズ位置に対する前方方向の寄与を加えたものとして計算される。 現在、これはすべての量子ビットに対して一律に行われています。
後方境界から前方境界に切り替える最適な時点は、学習されたノイズ率によって決まる。 現段階では、発生率はまだ設定されていません。 noise_model_rates 各層について、は に None 初期化されており、これは merge_bounds 一様なノイズ発生率として扱われます。 したがって、ここに示されている統合された境界はあくまでプレビューに過ぎません。ステップ3では、実際に学習されたレートを用いてこれらを再計算します。
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)Output:
Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
手作業による検査のためにSLCを可視化する
後方境界と引き締められた前方境界を統合すると、統合されたSLCの挙動が明らかになります:
- 上記の関数は、後方境界から引き締められた前方境界への切り替えが行われるパーティションが選択されることを示しています。
- 以下からわかるように、SLCには現在、部分的な後方境界と部分的な引き締められた前方境界が含まれている。
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
ステップ3:実行
このセクションでは、実際の量子デバイスを使用するワークフローの段階に入ります。 この学習ベースのエラー軽減手法には、2つのステップがあります:
NoiseLearnerV3を使用して、その音を覚えてください。- および
Executorフレームワークsamplomaticを用いて、エラー軽減回路を実行する。
量子回路から得られる誤差の上限値をもとに、関連するノイズ率を把握し、エラーバジェットの優先順位付けを行い、サンプリングのオーバーヘッドを決定した上で、QPU上で実行します。
a. 騒音基準値を学ぶ
ノイズ学習器は、スパースなパウリ・リンドブラッドノイズモデルに基づき、対象となる1つ以上の回路内のゲートに影響を与えるノイズ過程を特徴付けます。 この run() メソッドは、noise-learnerの設定で指定されたオプションを使用して、指定された一意の2量子ビット層に対してノイズ学習ジョブを起動します。 これらのオプションは、パウリ・トゥイール戦略、ランダム化とショットの回数、学習深度、およびポストセレクションを制御します。
また、学習の深さも意図的に選定しています。 学習ベースの緩和策に関する実用的な知見として、 samplomatic 緩和対象とする回路の深さと、学習の最大深さを一致させることが極めて有益であることが挙げられる。 NLv3layer_pair_depths* は*レイヤーペア(レイヤーとその逆)単位で測定されるため、最深値は回路の2量子ビットレイヤーの深さの半分に設定します。
post_selection_enabled = True# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer
# plus its inverse), so the deepest value is half the circuit's two-qubit-layer
# depth. Learning to this depth markedly improves the quality of the mitigation.
#
# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after
# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a
# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and
# ``QuantumCircuit.depth`` does not recurse into boxes).
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2 # dividing by 2 since we want pairs
# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated.
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
{d for d in candidate_depths if d < max_layer_pair_depth}
| {max_layer_pair_depth}
)
noise_learner_options = {
"num_randomizations": 64,
"shots_per_randomization": 128,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": post_selection_enabled,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_SLC"]},
}
noise_learner = NoiseLearnerV3(backend, noise_learner_options)noise_learner_job = noise_learner.run(unique_2q_instructions)noise_learner_result = noise_learner_job.result()if post_selection_enabled:
print(
"Minimum fraction of shots kept for noise learning experiments: ",
end="",
)
print(
f"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}"
)Output:
Minimum fraction of shots kept for noise learning experiments: 0.71
# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap
refs_2_plm = noise_learner_result.to_dict(
unique_2q_instructions, require_refs=False
)b.i. 学習された実際のノイズ率に基づいて、境界を更新する
特定のノイズモデルが学習されたことで、学習されたノイズ率を予測されたノイズの範囲に適用し、バイアスを最小化する上でどの範囲が最も大きな影響を与えるかを最終的に特定することができる。
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)Output:
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
b.ii. ハードウェア実行における を local_scales 計算する
compute_local_scales 回路内で起こりうる各ノイズ誤差を検討し、その誤差が最終的な測定値にどの程度のバイアスを及ぼすか、またそれを補正するのにどれほどのコストがかかるかを推定する。 その後、エラーを「軽減する価値」が高い順にランク付けし、許容されるサンプリングコストの範囲内(あるいは所望の精度を達成しつつ)で、バイアスを可能な限り低減できるサブセットを選択する。 その結果、どの誤差が積極的に軽減され、どの誤差が軽減されないかを示す一連のスケーリング係数(local_scales)に加え、予測される総サンプリングコストのオーバーヘッド(sampling_costs)および残存バイアス(residual_bias_bound)が得られる。
所望の残留バイアスを制御できることは、PECのSLC実装における重要な特徴である。 元の実装では、サンプリングのオーバーヘッドは常にバイアスをゼロにすることを目指していましたが、期待される残存バイアスとのトレードオフによって、必要なサンプリングのオーバーヘッドを調整することができます。 これにより、ユーザーは所定のサンプリング予算の範囲内に収めることができます。これは、ワークフローの初期段階でのプロトタイプ作成において特に役立ちます。
id_map = map_modifier_ref_to_ref(boxed_circuit)summed_rates = 0.0
for box_id, noise_id in id_map.items():
learned_plm = refs_2_plm[noise_id]
summed_rates += np.sum(learned_plm.rates)
# print(f"{box_id}:\tgamma = {np.exp(2 * summed_rates):1.6e}\tsampling cost = {np.exp(4 * summed_rates):1.6e}")
total_gamma = np.exp(2 * summed_rates)
print(
f"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}"
)Output:
Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633
biases = []
costs = []
for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():
_, cost_, bias_ = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=bias,
)
biases.append(bias_)
costs.append(cost_)サンプリングのオーバーヘッドと残留バイアスのバランスをとる
xticks = np.arange(0, 11)
fig, ax = plt.subplots()
ax.scatter(
[0], [total_gamma**2], marker="D", c="tab:orange", label="full PEC"
)
ax.plot(
100 * np.array(biases),
np.array(costs),
"o-",
c="tab:blue",
label="PEC+SLC",
)
ax.set_yscale("log")
ax.set_xticks(xticks, [f"{x:.1f}" for x in xticks])
ax.set_xlabel("Remaining bias [%]")
ax.set_ylabel(r"Sampling overhead, $\gamma^2$")
ax.grid()
ax.legend()
fig.suptitle("PEC sampling overhead reduction due to SLC")Output:
Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')
chosen_bias_thres = 0.1local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=chosen_bias_thres,
)
print(
f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} "
f"w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)Output:
PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%
c. antinoise を使用して、対象の回路を実行する
c.i. 以下の方法を使用して、テンプレート回路を準備します。 samplex
template_circuitこれは samplex 、Samplomaticの メソッド build の出力であり、 のランダム化されたパラメータを生成するために必要なすべての情報がエンコードされています。 これらは、オブジェクト QuantumProgram の設定に使用され、そのオブジェクトは、プリミティブ Executor とともにQPU上で実行されます。 samplexそれぞれ QuantumProgram には複数の項目を含めることができ、これらは と の template ペアとして考えることができます。
詳細については、「Hello samplomatic 」のチュートリアルをご覧ください。
# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)# Set up postselection if it's been enabled
if post_selection_enabled:
# Set up post selection PM (to add PS instructions)
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
final_template_circuit = post_selection_pm.run(template_circuit)
else:
final_template_circuit = template_circuitc.ii. を設定する QuantumProgram
num_randomizations = 4096
shots_per_randomization = 64
chunk_size = 256# Set up QuantumProgram
program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)
# no EM
# Collect up a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(0) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# plain PEC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# PEC+SLC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
samplex_inputs |= {"local_scales": local_scales}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)c.iii. Executor プリミティブを使用してプログラムを実行する
executor = Executor(backend)job_exec = executor.run(program)results_exec = job_exec.result()ステップ4:後処理
executor_expectation_valuesを用いて関心のある最終的な期待値を計算するにあたり、可能な限り最高品質の結果が得られるよう、いくつかの後処理手法を採用しています。 まず、読み出しプロセス中に発生するあらゆる誤差を考慮した、当社の「 ツイールド・リードアウト・エラー・エクスティンクション(TREX) 」を適用します。 次に、ポストセレクション法を用いて、Heronバックエンドにおける非マルコフノイズに起因する誤差を修正する。 この手法では、アクティブな量子ビットと観測対象の量子ビットを測定し、各量子ビットに緩やかな回転を施した後、再度測定を行う。 PostSelector2つの測定結果が予想通り反転した量子ビットであることを裏付けない場合、これらのショットは、からのを mask 適用することで破棄される。 マスク計算においては、単一量子ビットノードや隣接するスペクテーターエッジに基づいてフィルタリングを行うという特定の戦略を設定することができ、これにより、フィルタリングによって除外されるショットの数と結果の品質の両方に影響を与える可能性があります。
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)post_selection_strategy = "node"def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):
meas = datum["meas"]
flips = datum["measurement_flips.meas"]
signs = datum.get("pauli_signs", None)
meas_basis_axis = None
avg_axis = 0
mask = None
if ps and post_selection_enabled:
# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=final_template_circuit, coupling_map=backend.coupling_map
)
# Compute the ps mask for filtering results
mask = post_selector.compute_mask(
datum, strategy=post_selection_strategy
)
# Compute fraction of shots kept from post selection
total_num_shots = num_randomizations * shots_per_randomization
ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)
print(
f"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):"
)
results = []
for i in range(steps, num_randomizations + 1, steps):
# Compute mitigated expvals w/out post-selection
res = executor_expectation_values(
meas[:i],
reverser_virt,
meas_basis_axis,
avg_axis=avg_axis,
measurement_flips=flips[:i],
pauli_signs=signs[:i] if signs is not None else None,
postselect_mask=mask[:i] if mask is not None else None,
rescale_factors=trex_scale_factors if trex else None,
gamma_factor=gamma,
)
results.append(res[0])
return resultsgamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)
gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)steps = 16results = {}
for label, result_idx, gamma, use_ps, use_trex in [
("PEC", 1, gamma_pec, True, True),
("PEC+SLC", 2, gamma_slc, True, True),
("Unmitigated", 0, None, False, False),
]:
res = post_process_conv(
results_exec[result_idx],
steps=steps,
gamma=gamma,
ps=use_ps,
trex=use_trex,
)
results[label] = resOutput:
With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):
実験結果を検証することで、PEC、PECとSLCを組み合わせた手法、および緩和策を講じていないベースラインという、異なるアプローチの挙動を直接比較することができる。 特に注目すべき具体的な点は以下の通りです:
- この未調整の結果は、10%のバイアス帯( 0.35 付近)をはるかに超えており、無作為化の回数による影響は受けていない。
- このデバイスでは、フルPECには のサンプリングオーバーヘッドが生じます。初期の大きな変動の後、プレーンPECによる推定値は信号を回復し、 1.1 付近で安定しますが、正確な値をわずかにオーバーシュートしています。 なお、10%のバイアス帯域はPEC+SLC推定量にのみ適用される点に留意してください。単純なPECは原則として無偏であるため、この残差の偏差はバイアス保証の対象外であり、むしろ統計的な変動や学習されたノイズモデルの不完全性を反映したものです。これは、この問題規模においては妥当な結果と言えます。
- SLCにより、オーバーヘッドがさらに ~3.4-fold 削減され( となり、残留バイアスの上限は約10%となる)。 PEC+SLCも、ランダム化回数が少ない段階では大きく変動し、当初は非物理的な領域にまで達することもありますが、その推定値は着実に正確な値へと収束し、バンドのかなり内側にある 1.0 付近に落ち着きます。 サンプリングコストを約3分の1に抑えつつ、通常のPECと同等かそれ以上の精度を実現しており、ライトコーンシェーディングの利点を実証している。
- ランダム化の回数が積み重なるにつれて、両方の緩和推定値の誤差棒は縮小する。ランダム化の回数が一定の場合、PEC+SLC推定量は統計的分散が小さく、その が小さいこととも一致している。
なお、学習されたノイズ率(ひいてはオーバーヘッドや緩和された推定値)は、実行時のバックエンドとそのキャリブレーションに依存するため、このノートブックを再実行すると、定量的に異なる値が得られる場合があります。
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
ax.axhline(1.0, color="black", label="Exact")
ax.fill_between(
[-50, 4100], -10, 0, color="grey", alpha=0.25, label="Unphysical"
)
ax.fill_between([-50, 4100], 1, 10, color="grey", alpha=0.25)
ax.fill_between(
[-50, 4100], 0.9, 1.1, color="red", alpha=0.25, label="10% bias"
)
for label, res in results.items():
ax.errorbar(
list(range(steps, num_randomizations + 1, steps)),
[r[0] for r in res],
yerr=[r[1] for r in res],
alpha=0.75,
marker="o",
linestyle="",
markerfacecolor="none",
label=label,
)
ax.set_ylabel(r"$\langle X_{6}Z_{13}\rangle$")
ax.set_xlabel("# randomizations")
ax.grid()
ax.legend(ncols=2)
ax.set_ylim([-0.1, 2.0])
ax.set_xlim([-50, 4100])Output:
(-50.0, 4100.0)
次のステップ
この作品に興味を持たれた方は、以下の資料もご参考になるかもしれません:


