パウリ項の切り捨てには、異なるLpノルムを用いる
注: このガイドを読む前に、 「Truncate Pauli terms」 ガイドをお読みください。このガイドでは、指定された TruncationErrorBudget に基づいて、 バックプロパゲーション法に組み込まれている低重みのパウリ項の切り捨てについて解説しています。
このガイドでは、切り捨てられたパウリ項によって生じる誤差を推定するために使用される Lp-ノルムを変更するために使用できる p_norm、backpropagate キーワード引数について解説します。
例となる回路を組み立てる
このガイドでは、「 パウリ項の切り捨て」 のガイドと同じ例題回路を使用しています:
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
# Generate a linear chain of 10 qubits
num_qubits = 10
linear_chain = rustworkx.generators.path_graph(num_qubits)
# Use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# Evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)
# For visualization purposes only, recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)Output:
総磁化のオブザーバブルを定義する:
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp.from_sparse_list(
[("Z", [i], 1.0) for i in range(num_qubits)], num_qubits=num_qubits
)参考までに、正確な期待値を計算してみましょう:
from qiskit.primitives import StatevectorEstimator
estimator = StatevectorEstimator()
job = estimator.run([(circuit, obs)])
res = job.result()
exact_exp = res[0].data.evs
print(exact_exp)Output:
9.318197859862146
L1 の規範を使用する
デフォルトでは、「 パウリ項の切り捨て」 のガイドですでに確認したように、であり p_norm=1、これは誤差が次のように推定されることを意味します:
ここで、 は量子状態、 は厳密な観測量と切り捨てられた観測量との間の実際の差(これは未知である)、 は切り捨てられたパウリ項の集合、 はパウリ項の係数である。 この不等式は、ほとんどのシナリオにおいて、厳密ではあるものの、非常に緩やかな上界となります。
このガイドでは、サンプル回路の6つのスライスについて、スライスごとの誤差を一定値としてバックプロパゲーションを行います 0.001。 この数値は、指定された範囲内の予算として理解してください p_norm。
from qiskit_addon_obp.utils.truncating import setup_budget
l1_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=1)
print(l1_truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
max_slices = 6
l1_bp_obs, l1_remaining_slices, l1_metadata = backpropagate(
obs,
slices[-max_slices:],
truncation_error_budget=l1_truncation_error_budget,
)
l1_reduced_circuit = combine_slices(
slices[:-max_slices] + l1_remaining_slices
)
print(
f"Backpropagated {max_slices - len(l1_remaining_slices)} circuit slices."
)
print(
f"New observable contains {len(l1_bp_obs)} terms and {len(l1_bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 6 circuit slices.
New observable contains 116 terms and 10 commuting groups.
これで、バックプロパゲーションされた観測量の期待値と、正確な基準値に対する誤差を計算できるようになった:
estimator = StatevectorEstimator()
job = estimator.run([(l1_reduced_circuit, l1_bp_obs)])
res = job.result()
l1_exp = res[0].data.evs
l1_error = exact_exp - l1_exp
print(l1_exp, l1_error)Output:
9.317869899338842 0.00032796052330397174
最後に、各スライスのバックプロパゲーション中に生じた誤差と、累積誤差をプロットすることができます。 累積誤差は、各スライスの誤差の合計です。 累積誤差は、実際の誤差に対する非常に緩い上界であることがわかります。
from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_slice_errors,
)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[1].plot([6], [l1_error], "x", color="red", label="actual error")
plot_slice_errors(l1_metadata, axes[0])
plot_accumulated_error(l1_metadata, axes[1])Output:
L2 の規範を使用する
L2 ノルムは、 L1 ノルムよりも、生じた誤差をよりよく近似していると言えるだろう。 これは、量子状態 がハーランダムアンサンブルから抽出されたものと見なすことができるためであり、その場合、生じる誤差は、平均がゼロで、分散が L2 ノルムによって近似的に上界が定まる分布に従うことになる:
この境界は厳密なものではありませんが、病理的なケースにおいてのみこの境界が破られることになります。
例示回路の6つのスライスについて、スライスごとの最大誤差を( 0.001 今回は L2 ノルムで解釈される)として、再びバックプロパゲーションを行います。
l2_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=2)
print(l2_truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=2)
max_slices = 6
l2_bp_obs, l2_remaining_slices, l2_metadata = backpropagate(
obs,
slices[-max_slices:],
truncation_error_budget=l2_truncation_error_budget,
)
l2_reduced_circuit = combine_slices(
slices[:-max_slices] + l2_remaining_slices
)
print(
f"Backpropagated {max_slices - len(l2_remaining_slices)} circuit slices."
)
print(
f"New observable contains {len(l2_bp_obs)} terms and {len(l2_bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 6 circuit slices.
New observable contains 84 terms and 6 commuting groups.
ここでも、バックプロパゲーションされた観測量の期待値と、正確な基準値に対する誤差を計算する:
estimator = StatevectorEstimator()
job = estimator.run([(l2_reduced_circuit, l2_bp_obs)])
res = job.result()
l2_exp = res[0].data.evs
l2_error = exact_exp - l2_exp
print(l2_exp, l2_error)Output:
9.317829770853422 0.00036808900872387085
スライスごとの発生誤差と累積誤差をプロットすると、以前と同様の傾向が確認できる。
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[1].plot([6], [l2_error], "x", color="red", label="actual error")
plot_slice_errors(l2_metadata, axes[0])
plot_accumulated_error(l2_metadata, axes[1])Output:
なお、累積誤差は、やはり個々のスライスの誤差の和であることに注意してください。 これもミンコフスキーの不等式に起因する大まかな上界です。というのも、この上界を再帰的に計算しなければならないからです:
ここで、新しい添字 は現在のスライス反復回数を表し、 はバックプロパゲーションの反復 における実際の誤差、 は反復 からの近似された切り捨て誤差、 は反復 で切り捨てられたパウリ項の集合となる。