파울리 항 절단을 위해 서로 다른 Lp-노름 사용
참고: 이 가이드를 읽기 전에, 지정된 중량 임계값( TruncationErrorBudget )을 기반으로 역전파 (backpropagate) 방법에 내장된 저중량 파울리 항의 절단(truncation)에 대해 설명하는 ‘파울리 항 절단( Truncate Pauli terms )’ 가이드를 먼저 읽어보시기 바랍니다.
이 가이드에서는, 잘린 파울리 항으로 인해 발생하는 오차를 추정하는 데 사용되는 Lp-노름을 변경하는 데 활용할 수 있는 ‘backpropagate’ 키워드 인자에 대해 알아보게 됩니다 p_norm.
예제 회로를 구성해 보세요
이 가이드에서는 ‘파울리 항 절단’ 가이드와 동일한 예제 회로를 사용합니다:
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 노름보다 발생한 오차를 더 잘 근사한다고 주장할 수 있다. 이는 양자 상태 가 하르(Haar) 무작위 앙상블에서 추출된 것으로 가정할 수 있기 때문이며, 이 경우 발생하는 오차는 평균이 0이고 분산이 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:
누적 오차는 다시 개별 슬라이스 오차의 합계라는 점에 유의하십시오. 이는 뮌크프스키 부등식 때문에 또 다른 느슨한 상한입니다. 이 상한을 재귀적으로 계산해야 하기 때문입니다:
여기서 새로운 첨자 는 현재 슬라이스 반복 횟수를 나타내며, 이에 따라 는 역전파 반복 에서의 실제 오차, 는 반복 에서 산출된 근사 절단 오차, 는 반복 에서 절단된 파울리 항의 집합을 나타낸다.