주 콘텐츠로 건너뛰기
이 프로젝트는 베타 단계입니다. 버그를 발견하셨나요? GitHub 이슈 만들기. 질문이나 아이디어가 있으신가요? Discussions에서 질문하기 — 피드백을 기다립니다!
이 페이지의 번역 품질은 어떻습니까?

역전파 중 Pauli 항 절단하기

이 가이드는 qiskit_addon_obp.utils.truncating 모듈이 제공하는 Pauli 항 절단 메커니즘을 설정하는 방법을 설명합니다.

연산자 역전파(OBP)는 관측량이 더 복잡해지는 대가로 양자 회로의 깊이를 줄이는 데 사용할 수 있습니다. OBP에서 의미 있는 결과를 얻으려면 일반적으로 관측량이 너무 커지지 않도록 항을 절단해야 합니다. 연산자가 너무 커지는 것을 막으면서 회로 속으로 더 깊이 역전파할 수 있게 하는 한 가지 방법은, 계수가 작은 항을 연산자에 추가하는 대신 절단하는 것입니다. 항을 절단하면 실행할 양자 회로의 수가 줄어들 수 있지만, 최종 기댓값 계산에 절단된 항의 계수 크기에 비례하는 약간의 오차가 생깁니다.

backpropagate 메서드는 선택적으로 TruncationErrorBudget을 받으며, 이는 모든 슬라이스의 역전파가 성공한 후 각 관측량에서 가중치가 낮은 Pauli 항을 절단하는 방식을 설정합니다. 절단되는 항의 양은 사용자가 지정한 여러 설정 매개변수에 따라 달라집니다. 현재로서는 truncate_binary_search 메서드라는 단 하나의 절단 전략만 사용할 수 있습니다. 관측량과 어떤 _예산_이 주어지면, 이 메서드는 해당 관측량 안의 Pauli 항과 계수에 대해 이진 탐색을 수행하여 최적의 임계값을 찾습니다. 이때 절단된 계수의 합이 최대이면서 예산보다 작아집니다.

참고: 기본적으로 절단 오차를 평가하고 한정하는 데 L1 노름이 사용됩니다. 하지만 p_norm 설정을 사용하면 사용할 Lp-노름을 지정할 수 있습니다. 이 설정의 사용 방법은 Use different Lp-norms for Pauli term truncation 가이드를 참조하세요. 다음 예제는 함께 제공되는 setup_budget 함수를 사용하여 TruncationErrorBudget을 만드는 여러 방법을 보여 줍니다.

예제 회로 구성하기​

이 가이드는 다음 회로 슬라이스를 사용합니다.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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
linear_chain = rustworkx.generators.path_graph(10)

# 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, 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 of the previous code cell

단일한 간단한 관측량 하나를 살펴보겠습니다.

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp("IIIIIZIIII")

가장 단순한 경우: 슬라이스마다 고정된 절단 예산​

Pauli 항을 절단하는 데 사용할 수 있는 예산은 역전파의 각 단계마다 다를 수 있습니다. 이것이 어떻게 작동하는지 이해하기 위해, 먼저 사용자가 지정한 고정 절단 예산이라는 가장 단순한 경우를 살펴봅니다.

절단 예산을 지정하는 가장 간단한 방법은 max_error_per_slice 인수를 사용하는 것입니다. 사실 Reducing depth of circuits with operator backpropagation 튜토리얼에서도 이렇게 하고 있습니다. max_error_per_slice를 float로 설정하면 각 슬라이스에 그 값과 같은 예산이 할당됩니다. 아래 예제에서는 이 값을 0.001로 설정하며, 18개 슬라이스가 모두 역전파된다면 암묵적인 절단 오차가 최대 0.018임을 보장합니다.

슬라이스를 역전파하고 계수가 작은 항을 절단한 후 남은 오차 예산은 항상 다음 슬라이스의 오차 예산에 더해진다는 점에 유의하세요.

from qiskit_addon_obp.utils.truncating import setup_budget

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 29 terms and 10 commuting groups.

다음으로 OBPMetadata 인스턴스와 visualization 모듈이 제공하는 도구를 사용하여 역전파 과정을 시각화합니다.

  • 왼쪽 위 그래프는 세 번째 슬라이스를 역전파한 후부터 관측량의 항을 절단할 만큼 예산이 충분함을 보여 줍니다. 세 번째 슬라이스부터는 역전파하는 각 슬라이스 후에 어느 정도 절단 오차가 발생하므로, 슬라이스마다 적어도 하나의 항이 절단된다는 것을 알 수 있습니다.

  • 오른쪽 위 그래프는 세 번째 슬라이스에서 오차 예산이 .003으로 늘어남을 보여 줍니다. 남은 예산이 급격히 줄어드는 것이 보이는데, 이는 관측량에서 항이 절단되었음을 의미합니다. 이는 왼쪽 위 그래프에서 추론한 내용과 일치합니다.

  • 왼쪽 아래 그래프는 관측량에서 항을 절단함에 따라 전체 누적 오차가 단조롭게 증가함을 보여 줍니다. 이 그래프는 세 번째 슬라이스를 역전파하기 전까지는 절단된 항이 없었음도 확인해 줍니다.

  • 오른쪽 아래 그래프는 관측량의 가환 Pauli 그룹 수가 지정한 한계인 10까지 늘어났음을 보여 줍니다. 이 그래프는 또한 검은 선과 빨간 선이 교차하는 지점에서 볼 수 있듯이, 한 레이어를 더 역전파하면 관측량이 지정한 한계를 넘어 커지게 됨을 보여 줍니다.

이 모든 그래프에서 x축은 역전파된 슬라이스를 나열하지만, OBP는 회로의 끝에서부터 작동하므로 slice 1이 맨 마지막 슬라이스이고, slice 2가 그 앞의 슬라이스이며, 이런 식으로 이어집니다.

from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_left_over_error_budget,
plot_num_qwc_groups,
plot_slice_errors,
)

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

슬라이스 예산을 명시적으로 지정하기​

역전파 성능이 최적화되도록 각 슬라이스에 예산을 할당하는 방법을 알고 있다면, 각 슬라이스에 예산을 명시적으로 할당하는 것이 좋을 수 있습니다. 시연을 위해 처음 세 슬라이스에는 예산을 0으로 할당하고, 나머지 슬라이스에는 슬라이스당 .001의 예산을 사용하겠습니다.

# Zero out the first 3 slices' budgets
max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 32 terms and 10 commuting groups.

처음 세 레이어에서 예산을 제거하자, 이 그래프 중 세 개에서 확인할 수 있듯이 네 번째 레이어 이후까지 절단된 항이 없었습니다. 다소 의외일 수 있는 점은 네 번째 슬라이스에는 넘겨받은 남은 예산이 없었는데도 할당된 .001 예산을 사용하여 항 하나가 절단되었다는 것입니다. 이는 왼쪽 위 그래프에서 분명하게 보이고, 세 번째와 네 번째 슬라이스 사이에서 남은 예산 곡선이 평평해지는 오른쪽 위 그래프에서도 관찰할 수 있습니다. 또 하나 눈여겨볼 점은 이전 예제와 마찬가지로 이 시점부터는 적어도 하나의 항이 절단되었다는 것입니다.

핵심은 일부 슬라이스의 예산을 0으로 만들어 두 번째 예제에서는 절단 오차가 더 적게 발생했지만, 같은 수의 슬라이스를 역전파할 수 있었고 관측량에 포함된 가환 Pauli 그룹의 수도 같다는 것입니다. 왼쪽 아래 그래프를 살펴보면 두 번째 예제에서 오차의 한계가 더 작음을 확인할 수 있습니다.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

예산을 순환 방식으로 지정하기​

Trotter 회로처럼 반복되는 패턴이 있는 회로라면, 반복되는 슬라이스 부분집합에 대한 예산을 지정하고 그 예산이 이후 반복되는 모든 슬라이스에 사용되게 할 수 있습니다.

좀 더 구체적으로, 우리가 사용하는 예제 회로에는 여섯 개의 슬라이스가 세 번 반복되어 총 18개의 슬라이스가 있습니다. 임의로 단일 큐비트 레이어와 RYY 레이어에는 예산을 0으로, 각 RXX 레이어에는 .003의 예산을 할당하겠습니다. 예산을 길이 6의 시퀀스로 지정하면 그 예산이 18개의 모든 슬라이스에 순환적으로 적용되는 것을 확인할 수 있습니다.

슬라이스는 역순(즉, 끝에서부터)으로 역전파된다는 점에 다시 한번 유의하세요. 따라서 순환 예산의 첫 번째 항목은 실제로 마지막 슬라이스에, 두 번째 항목은 그 앞의 슬라이스에 사용되며, 이런 식으로 이어집니다.

# Specify a length-6 per-slice budget.
# This will be cycled over three times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)
op_budget = OperatorBudget(max_qwc_groups=20)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 13 circuit slices.
New observable contains 49 terms and 14 commuting groups.

왼쪽 위와 왼쪽 아래 이미지에서 볼 수 있듯이, 처음 네 슬라이스에는 오차 예산이 할당되지 않았으므로 절단이 수행되지 않았습니다. 다섯 번째와 여섯 번째 슬라이스는 예산이 생기면서 일부 항이 절단되었습니다.

왼쪽 위 이미지에서 볼 수 있듯이, 일곱 번째 슬라이스에는 예산이 할당되지 않았는데도 이를 역전파한 후 비교적 큰 오차가 발생했습니다. 이는 다섯 번째와 여섯 번째 슬라이스에 할당된 전체 예산 .006 중 약 .002만 사용되었기 때문이며, 남은 예산이 일곱 번째 슬라이스로 넘어가 오른쪽 위 이미지에서 보이듯이 대부분 소진되었습니다.

왼쪽 위와 오른쪽 위 이미지는 남은 소량의 예산이 슬라이스 8~10 사이에서 모두 소진되고, 예상대로 슬라이스 11에서 새 예산이 생김을 보여 줍니다. 왼쪽 위, 오른쪽 위, 왼쪽 아래 이미지는 모두 max_error_per_slice 인수의 길이가 슬라이스 수보다 작을 때의 순환 동작을 보여 줍니다. 이 순환 동작은 모든 슬라이스에 걸쳐 계속되었겠지만, 오른쪽 아래 이미지에서 보듯이 13개의 슬라이스를 역전파한 후 max_qwc_groups 중단 조건에 도달했습니다.

또한 흥미로운 점은 슬라이스 11에서 두 번째 예산이 생긴 후 Pauli 그룹의 수가 실제로 줄어든다는 것입니다. 이는 계수가 작은 그룹이 있었지만 슬라이스 11을 역전파한 후 예산이 더 생기기 전까지는 절단할 수 없었기 때문이며, 그래서 여러 번의 반복 동안 관측량에 쌓여 있었습니다. 이 경우는 알고리즘이 종료되려면 max_qwc_groups를 초과해야 한다는 점도 보여 줍니다.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

전체 오차 제한하기​

슬라이스별 오차 예산을 지정하는 것 외에도, 절단으로 발생할 수 있는 최대 오차를 지정할 수 있습니다. 그 한계에 도달하면 더 이상 절단이 수행되지 않습니다. 하지만 관측량이 너무 커져 중단 조건 중 하나가 충족될 때까지 역전파는 계속됩니다.

이제 위의 실험을 다시 실행하되, 일곱 번째 슬라이스를 역전파한 후 오차 예산이 소진되도록 max_error_total에 상한을 설정합니다.

# Specify a length-6 per-slice budget.
# This will be cycled over 3 times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice, max_error_total=0.006
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 10 circuit slices.
New observable contains 67 terms and 20 commuting groups.

예상대로 절단 오차는 .006에서 상한에 도달합니다(왼쪽 아래 차트). 이번 실행에서는 슬라이스 11을 역전파할 수 없었다는 점이 눈에 띕니다. 항을 절단할 예산이 충분하지 않았고, 오른쪽 아래 이미지에서 보듯이 가환 Pauli 그룹의 수가 한계인 20을 넘어 늘어났기 때문입니다.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

max_error_per_slice를 지정하지 않고 max_error_total만으로 전체 예산을 제한하는 것이 바람직할 수도 있습니다.

truncation_error_budget = setup_budget(max_error_total=0.018)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)

위 셀의 출력은 per_slice_budget이 max_error_total로 설정되어 있어 다소 의외일 수 있습니다. 이는 사용 가능한 전체 예산이 탐욕적으로 소비된다는 뜻입니다. 이렇게 생각할 수 있습니다. 전체 예산이 각 슬라이스에서 사용 가능합니다(per_slice_budget에 대해 반복하기 때문입니다). 하지만 이미 소비된 예산은 알고리즘의 해당 시점에서 사용 가능한 예산에서 차감됩니다.

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 9 circuit slices.
New observable contains 25 terms and 9 commuting groups.

오른쪽 위 이미지는 .018의 전체 오차 예산이 첫 번째 슬라이스에서 사용 가능해지는 모습을 보여 줍니다. 세 번째 슬라이스까지는 절단이 수행되지 않으므로 남은 예산은 일정하게 유지됩니다. 전체 예산이 소진될 때까지 역전파되는 각 슬라이스에서 전체 예산을 사용할 수 있으므로, 예산은 단조롭게 감소합니다.

이 노트북의 첫 번째 실험과 거의 같은 이 실험에서 역전파된 슬라이스가 두 개 더 적었다는 점이 눈에 띕니다. 이는 일부 문제에서는 예산을 고르게 분배하는 것이 최적일 수 있음을 보여 줍니다. 다른 문제에서는 슬라이스가 전체 예산을 탐욕적으로 사용하게 하는 것이 더 나은 성능을 낼 수도 있습니다.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

역전파할 슬라이스 수와 전체 오차를 함께 제한하기​

오차 예산을 회로 끝까지 분배하고 싶지도 않고 탐욕적으로 소비하고 싶지도 않다면, 역전파할 슬라이스 수(num_slices)와 전체 오차 예산(max_error_total)을 함께 지정할 수 있습니다. 그러면 오차 예산이 (p_norm에 따라) 입력 슬라이스에 균일하게 분배됩니다.

여기서는 역전파할 슬라이스 수를 최대 12로 제한하고 전체 오차 예산은 그대로 유지합니다.

num_slices = 12

truncation_error_budget = setup_budget(
max_error_total=0.018, num_slices=num_slices, p_norm=1
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)

이제 이전 단계에서 예산을 할당한 12개의 슬라이스를 역전파해 보겠습니다. 회로의 마지막 12개 슬라이스를 그대로 전달하면 됩니다. p_norm=1이면 12개 슬라이스 각각에 사용 가능한 예산은 0.018 / num_slices = 0.0015입니다.

bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices[-num_slices:],
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)

슬라이스의 일부(코드 slices[-num_slices:])를 backpropagate에 전달했으므로, 역전파 후 남은 슬라이스를 역전파로 보내지 않은 슬라이스(코드 slices[:-num_slices])와 결합해야 합니다.

남은 슬라이스를 모두 결합한 후에는 combine_slices를 사용하여 깊이가 줄어든 QuantumCircuit을 생성할 수 있습니다. 다음으로 역전파된 슬라이스 수와 관측량이 얼마나 커졌는지를 살펴봅니다.

# Recombine the slices remaining after backprop with the rest of the original circuit
reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)

print(f"Backpropagated {num_slices - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 12 circuit slices.
New observable contains 29 terms and 9 commuting groups.

플롯을 보면 관측량을 10개 미만의 교환 가능한 Pauli 그룹으로 유지하면서 12개 슬라이스를 모두 성공적으로 역전파했음을 알 수 있습니다. 또한 num_slices를 max_error_total과 함께 사용하면 슬라이스 전반에 예산이 분배되고, 사용하지 않은 예산이 다음 슬라이스로 다시 전달되는 것도 볼 수 있습니다. 이는 역전파 과정 내내 예산이 소모되고 다시 보충되는 오른쪽 위 플롯에서 가장 뚜렷하게 나타납니다.

이 노트북의 첫 번째 실험이나 바로 앞의 예제와 비교했을 때, 이렇게 오차를 분배하는 방법이 가장 좋은 결과(더 많은 슬라이스를 역전파)를 냈다는 점이 눈에 띕니다. 이 예제들은 모두 .018의 오차 예산을 할당했지만, 그 예산을 어떻게 분배하느냐에 따라 역전파 성능이 달라졌습니다.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

여러 관측량 다루기​

qiskit_addon_obp.backpropagate 메서드는 관측량의 시퀀스를 전달할 수 있습니다. 이를 통해 여러 대상 관측량을 다룰 때 워크플로가 단순해집니다.

이 경우 절단 전략이 어떻게 동작하는지 알려드리기 위해 여기서 명시적으로 설명합니다. 이 예제에서는 지금까지 사용한 관측량에 관측량 하나를 추가합니다.

obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]

마지막으로, 이 튜토리얼의 첫 번째 실험을 반복하되 회로를 역전파할 대상으로 관측량 두 개를 사용합니다.

이 예제에서는 역전파할 수 있는 슬라이스 수에 영향이 없습니다. 하지만 두 관측량이 서로 다른 개수의 Pauli 항과 교환 가능한 그룹을 만들어 냈다는 것을 볼 수 있습니다.

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups."
)
print(
f"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
The new first observable contains 29 terms and 10 commuting groups.
The new second observable contains 23 terms and 8 commuting groups.

아래 플롯은 역전파 알고리즘이 여러 관측량을 어떻게 처리하는지 보여 줍니다.

먼저 왼쪽 위, 오른쪽 위, 왼쪽 아래 플롯은 항을 절단하기 위한 예산이 각 관측량마다 개별적으로 설정된다는 것을 보여 줍니다. 즉, 두 관측량 모두 역전파된 슬라이스당 0.001의 오차를 가정하고 항을 절단할 수 있습니다. 관측량의 성격이 다르기 때문에 예산 소모도 달라집니다. 이 예제에서는 두 관측량이 많이 겹치지만, 항상 그런 것은 아닙니다.

오른쪽 아래 플롯은 max_qwc_groups가 모든 관측량을 고려한다는 것을 보여 줍니다. 즉, 모든 관측량의 항을 함께 묶어 큐비트 단위로 교환 가능한(qubit-wise commuting) 그룹의 최종 개수 하나를 구하고, 이를 max_qwc_paulis와 비교합니다. max_paulis 임곗값(이 노트북에서는 다루지 않음)도 마찬가지로, 모든 관측량에 걸친 Pauli 항의 개수에 한도를 설정할 수 있습니다.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell