기대값 추정을 위한 연산자 역전파(OBP)
예상 소요 시간: Heron r3 프로세서 기준 4분 (참고: 이는 예상치에 불과합니다.) (실행 시간은 다를 수 있습니다.)
학습 성과
- 회로 실행 횟수를 늘리는 대가로 양자 회로의 깊이를 줄이는 방법
qiskit-addon-obp - 를 사용하여
qiskit-addon-utilsXYZ 해밀토니안과 그 시간 발전 회로를 구성하는 방법
전제조건
- Estimator 기본 객체를 사용하여 관측 가능한 변수의 기대값 계산하기
배경
연산자 역전파(operator backpropagation)는 양자 회로의 끝부분에 있는 연산들을 측정 가능한 관측량에 흡수하는 기법으로, 일반적으로 관측량에 추가 항을 도입하는 대가로 회로의 깊이를 줄여준다. 목표는 관측 가능한 변수의 크기가 지나치게 커지지 않도록 하면서 회로의 가능한 한 많은 부분을 역전파하는 것입니다. OBP Qiskit 애드온에서 Qiskit 기반의 구현을 사용할 수 있습니다. 자세한 내용은 관련 문 서를 참조하십시오.
관측량 을 측정해야 하는 회로 예시를 생각해 보자. 여기서 는 파울리 연산자이고, 는 계수이다. 이 회로를 단일 유니터리 연산자 로 표기하자. 이는 아래 그림과 같이 로 논리적으로 분할될 수 있다.
연산자 역전파(operator backpropagation)는 관측량(observable)을 로 진화시킴으로써, 유니터리 연산 을 관측량에 통합합니다. 다시 말해, 계산의 일부는 관측량이 에서 로 진화하는 과정을 통해 고전적으로 수행됩니다. 이제 원래의 문제를, 유니터리가 인 새로운 저깊이 회로에 대해 관측량 을 측정하는 문제로 재구성할 수 있습니다.
단일 벡터 는 여러 개의 슬라이스 로 표현됩니다. 슬라이스를 정의하는 방법은 여러 가지가 있습니다. 예를 들어, 위의 예시 회로에서 의 각 층과 게이트의 각 층은 개별 슬라이스로 간주할 수 있습니다. 역전파(Backpropagation)는 전통적으로 가중치 역전파( )를 계산하는 과정을 포함합니다. 의 각 슬라이스는 로 표현될 수 있으며, 여기서 는 -큐비트 파울리 연산이고 는 스칼라이다. 다음 사실을 확인하는 것은 쉽습니다
위의 예에서, 만약 라면, 기대값을 계산하기 위해 하나의 양자 회로가 아닌 두 개의 양자 회로를 실행해야 합니다. 따라서 역전파는 관측 가능한 함수의 항의 수를 증가시켜 회로 실행 횟수가 늘어날 수 있다. 연산자가 지나치게 커지는 것을 방지하면서도 회로 내로 더 깊이 역전파를 진행하게 하는 한 가지 방법은, 계수가 작은 항들을 연산자에 더하는 대신 잘라내는 것이다. 예를 들어, 위의 예시에서 가 충분히 작다면 가 포함된 항을 잘라낼 수 있다. 항을 잘라내면 실행해야 할 양자 회로의 수가 줄어들 수 있지만, 이 경우 잘라낸 항의 계수 크기에 비례하는 오류가 최종 기대값 계산에 발생하게 된다.
요구사항
이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:
- Qiskit SDK v2.0 또는 그 이후 버전, 시각화 기능 지원
- Qiskit Runtime v0.22 또는 그 이후 (
pip install qiskit-ibm-runtime) - OBP Qiskit 애드온 0.3 이상 (
pip install qiskit-addon-obp) - Qiskit 애드온 유틸리티 0.3 이상 (
pip install qiskit-addon-utils)
설정
import numpy as np
import matplotlib.pyplot as plt
from qiskit.primitives import StatevectorEstimator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import CouplingMap
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit_addon_utils.slicing import slice_by_depth, combine_slices
from qiskit_addon_obp.utils.simplify import OperatorBudget
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.truncating import setup_budget
from rustworkx.visualization import graphviz_draw
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2, EstimatorOptions소규모 시뮬레이터 예시
이 튜토리얼에서는 OBP Qiskit 애드온 을 사용하여 하이젠베르크 스핀 체인의 양자 역학을 시뮬레이션하는 Qiskit 패턴 을 구현합니다. 소음이 없는 시뮬레이터에서는 역전파를 적용했을 때와 적용하지 않았을 때 얻어지는 기대값이 동일하다는 점에 유의하십시오.
1단계: 고전적 입력을 양자 문제에 매핑하기
양자 하이젠베르크 모델의 시간적 변화를 양자 실험에 대응시킨다
먼저, 의 qiskit-addon-utils 함수를 generate_xyz_hamiltonian 사용하여 주어진 연결성 그래프 위에 하이젠베르크형 해밀토니안을 생성할 것이다. 이 그래프는 rustworkx.PyGraph 또는 CouplingMap 중 하나일 수 있습니다. 이하에서는 10개의 큐비트로 구성된 선형 체인을 CouplingMap 사용하겠습니다.
num_qubits = 10
layout = [(i - 1, i) for i in range(1, num_qubits)]
# Instantiate a CouplingMap object
coupling_map = CouplingMap(layout)
graphviz_draw(coupling_map.graph, method="circo")Output:
다음으로, 하이젠베르크 XYZ 해밀토니안을 모델링하는 파울리 연산자를 생성합니다:
여기서 는 결합 매핑의 그래프이다. 이 튜토리얼에서는 을 으로, 을 으로 각각 설정했습니다.
# Get a qubit operator describing the Heisenberg XYZ model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),
ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),
)
print(hamiltonian)Output:
SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII', 'IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIYII', 'IIIIIIIZII', 'IIIIIIXIII', 'IIIIIIYIII', 'IIIIIIZIII', 'IIIIIXIIII', 'IIIIIYIIII', 'IIIIIZIIII', 'IIIIXIIIII', 'IIIIYIIIII', 'IIIIZIIIII', 'IIIXIIIIII', 'IIIYIIIIII', 'IIIZIIIIII', 'IIXIIIIIII', 'IIYIIIIIII', 'IIZIIIIIII', 'IXIIIIIIII', 'IYIIIIIIII', 'IZIIIIIIII', 'XIIIIIIIII', 'YIIIIIIIII', 'ZIIIIIIIII'],
coeffs=[0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,
0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,
1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,
0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,
0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,
1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,
0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,
1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,
1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j])
큐비트 연산자를 통해, 그 시간적 변화를 모델링하는 양자 회로를 생성할 수 있다. 우리는 리-트로터 분해를 이용하여 시간 진화 회로를 구성했습니다 generate_time_evolution_circuit .
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=2),
)
circuit.draw("mpl", style="iqp", fold=-1)Output:
2단계: 양자 하드웨어 실행을 위한 문제 최적화
회로 슬라이스를 생성하여 역전파 수행
이 backpropagate 함수는 회로 슬라이스 전체를 한 번에 역전파합니다. 따라서, 슬라이싱 방식의 선택은 주어진 문제에 대해 역전파가 얼마나 잘 수행되는지에 영향을 미칠 수 있다. 여기서는 함수를 slice_by_depth 사용하여 같은 유형의 게이트를 슬라이스로 묶을 것입니다.
서킷 슬라이싱에 대한 더 자세한 내용은 이 qiskit-addon-utils 패키지의 사용 가이드를 참고하세요.
slices = slice_by_depth(circuit, max_slice_depth=1)
print(f"Separated the circuit into {len(slices)} slices.")Output:
Separated the circuit into 18 slices.
역전파 과정에서 연산자의 크기가 커지는 것을 제한한다
역전파 과정에서 연산자의 항의 개수는 일반적으로 에 빠르게 수렴하는데, 여기서 는 슬라이스 수를 나타낸다. 연산자 내의 두 항이 큐비트 단위로 교환 법칙을 만족하지 않을 경우, 이들에 해당하는 기대값을 구하기 위해서는 별도의 회로가 필요합니다. 예를 들어, 두 큐비트 관측량 이 주어졌을 때, 이므로, 이 두 항의 기대값을 계산하기 위해서는 단일 기저에서의 측정만으로도 충분하다. 그러나 는 나머지 두 항과 반교환하므로, 의 기대값을 계산하려면 별도의 기저 측정이 필요합니다. 다시 말해, 를 계산하려면 하나의 회로가 아닌 두 개의 회로가 필요합니다. 연산자의 항 수가 늘어날수록 필요한 회로 실행 횟수도 증가할 가능성이 있습니다.
연산자의 크기는 backpropagate 함수의 operator_budget kwarg를 지정하여 제한할 수 있습니다 OperatorBudget 인스턴스를 받습니다.
할당되는 추가 리소스의 양(회로 실행 횟수, 즉 필요한 QPU 시간)을 조절하기 위해, 역전파된 관측량이 가질 수 있는 큐비트별 교환 가능한 파울리 군의 최대 개수를 제한합니다. 여기서 우리는 연산자 내 큐비트 단위로 교환하는 파울리 군의 수가 8개를 초과할 경우 역전파가 중단되어야 한다고 명시한다.
op_budget = OperatorBudget(max_qwc_groups=8)회로에서 슬라이스를 역전파한다
먼저 관찰 가능 항목을 , 은 큐비트 수로 지정합니다. 관측 가능한 항이 더 이상 큐비트 단위로 8개 이하의 통근 파울리 그룹으로 결합될 때까지 시간 진화 회로에서 슬라이스를 역전파합니다.
observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / num_qubits) for i in range(num_qubits)],
num_qubits=num_qubits,
)
observableOutput:
SparsePauliOp(['IIIIIIIIIZ', 'IIIIIIIIZI', 'IIIIIIIZII', 'IIIIIIZIII', 'IIIIIZIIII', 'IIIIZIIIII', 'IIIZIIIIII', 'IIZIIIIIII', 'IZIIIIIIII', 'ZIIIIIIIII'],
coeffs=[0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j,
0.1+0.j, 0.1+0.j])
아래에서 보시다시피, 우리는 6개의 슬라이스에 대해 역전파를 수행했으며, 각 항들은 8개가 아닌 6개의 그룹으로 통합되었습니다. 이는 슬라이스를 하나 더 역전파하면 파울리 군의 수가 8개를 초과하게 된다는 것을 의미합니다. 반환된 메타데이터를 확인하면 이것이 사실임을 알 수 있습니다. 또한 이 부분에서는 회로 변환이 정확히 성립한다는 점에 유의하십시오. 즉, 새로운 관측량 의 항 중 어느 것도 잘려나가지 않았다. 역전파 회로와 역전파 연산자는 원래 회로와 연산자와 동일한 정확한 결과를 산출합니다.
# Backpropagate slices onto the observable
bp_obs, remaining_slices, metadata = backpropagate(
observable, slices, operator_budget=op_budget
)
# Recombine the slices remaining after backpropagation
bp_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {metadata.num_backpropagated_slices} slices.")
print(
f"New observable has {len(bp_obs.paulis)} terms, which can be combined into "
f"{len(bp_obs.group_commuting(qubit_wise=True))} groups."
)
print(
f"Note that backpropagating one more slice would result in "
f"{metadata.backpropagation_history[-1].num_paulis[0]} terms "
f"across {metadata.backpropagation_history[-1].num_qwc_groups} groups."
)
print("The remaining circuit after backpropagation looks as follows:")
bp_circuit.draw("mpl", fold=-1, scale=0.6)Output:
Backpropagated 6 slices.
New observable has 60 terms, which can be combined into 6 groups.
Note that backpropagating one more slice would result in 114 terms across 12 groups.
The remaining circuit after backpropagation looks as follows:
시뮬레이터에서 진행하는 소규모 예제의 경우, 절삭 처리는 적용하지 않겠습니다. 이는 잡음이 없는 경우, 역전파를 적용한 회로와 그렇지 않은 회로 모두 동일한 결과를 도출하며, 절삭 처리는 추가적인 근사치로 인해 결과의 정확도를 떨어뜨리기 때문입니다.
회로를 기본 게이트 집합으로 변환하십시오
이제 원본 회로와 역전파된 회로를 모두 백엔드의 기본 게이트로 변환합니다. 소형 인스턴스의 경우 시뮬레이터에서 실행할 예정이므로 실제 백엔드에서 트랜스파일링할 필요가 없습니다.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)Output:
<IBMBackend('ibm_kingston')>
pm_basis = generate_preset_pass_manager(
optimization_level=3, basis_gates=backend.configuration().basis_gates
)
isa_circuit = pm_basis.run(circuit)
isa_bp_circuit = pm_basis.run(bp_circuit)3단계: Qiskit primitives 명령어로 실행합니다
먼저, 원래 회로와 역전파된 회로에 해당하는 두 개의 기본 통합 블록 (PUB)을 생성합니다. 그런 다음 이상적인 추정기를 사용하여 퍼블을 실행해 기대값을 구합니다.
pubs = [(isa_circuit, observable), (isa_bp_circuit, bp_obs)]rng = np.random.default_rng()
estimator = StatevectorEstimator(seed=rng)
job = estimator.run(pubs)4단계: 후처리 후 원하는 표준 형식으로 결과 출력
이제 원래 회로와 역전파된 회로의 기대값을 구합니다.
primitive_result = job.result()
circuit_expval = primitive_result[0].data.evs.item()
bp_circuit_expval = primitive_result[1].data.evs.item()methods = [
"No backpropagation",
"Backpropagation",
]
values = [circuit_expval, bp_circuit_expval]
ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylim([0.6, 0.92])
ax.set_ylabel(r"$M_Z$", fontsize=12)Output:
Text(0, 0.5, '$M_Z$')
예상대로 두 기대값은 일치한다. 우리는 노이즈가 없는 상태 벡터 시뮬레이터에서 실행하고 있으므로, 역전파는 회로-관측값 쌍에 대한 정확한 변환입니다. 따라서 원래 워크플로우와 역전파된 워크플로우는 모두 동일한 값을 산출해야 합니다. 역전파의 이점은 노이즈가 있는 하드웨어 환경에서만 명확히 드러나는데, 아래의 대규모 하드웨어 예시에서 볼 수 있듯이, 역전파된 회로가 더 짧을수록 누적되는 오류가 적기 때문입니다.
대규모 하드웨어 예시
실험을 설계할 때는 시각화 및 시뮬레이션을 더 쉽게 수행할 수 있도록 간단한 회로부터 시작하는 것이 좋습니다. 이제 우리는 및 매개변수에 동일한 값을, 관측량 에는 동일한 값을 적용하되, 트로터 단계(Trotter steps)를 4회 수행한 50-큐비트 하이젠베르크 해밀토니안에 대한 연산자 역전파(operator backpropagation)를 살펴보겠습니다. 이 규모에서는 이상적인 기대값을 무차별 대입법으로 계산할 수 없으므로, 텐서 네트워크를 사용하여 이상적인 기대값을 로 구합니다.
이 대규모 예제에서는 역전파와 더불어, 절단(truncation)을 적용한 역전파도 함께 소개합니다. 이상적으로는 유효 회로의 깊이를 줄이기 위해 가능한 한 많이 역전파를 수행하는 것이 좋습니다. 그러나 이는 업데이트된 관측 가능량에 비교환 항이 대량으로 포함되는 결과를 초래하여, 양자 오버헤드를 증가시키는 경우가 많다. 따라서, ‘절단’이라는 방법을 사용하여 계수가 작은 관측 가능한 항들을 제거할 수 있습니다. 절단(truncation)은 업데이트된 관측 가능한 양의 항의 수를 줄임으로써 전파를 더 많이 허용하지만, 동시에 어느 정도의 근사치를 초래하기도 한다. 따라서, 더 깊은 역전파를 통해 얻는 잡음 감소 효과가 근사 오차에 의해 상쇄되지 않도록, 절삭을 일정 범위 내로 제한해야 한다.
절단량을 제한하기 위해, 함수를 setup_budget 사용하여 각 슬라이스에 대한 오차 예산과 전체 역전파 회로에 대한 총 오차 예산을 할당합니다. 이를 통해 전체 회로뿐만 아니라 각 슬라이스별로도 절단 처리가 제어됩니다. 예산 배정 방법에 대한 다른 내용은 이 가이드도 참고하시기 바랍니다.
num_qubits = 50
layout = [(i - 1, i) for i in range(1, num_qubits)]
# Instantiate a CouplingMap object
coupling_map = CouplingMap(layout)
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),
ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),
)
# Generate a time evolution circuit for the Hamiltonian
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=4),
)
# Define the observable to measure
observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / num_qubits) for i in range(num_qubits)],
num_qubits,
)
slices = slice_by_depth(circuit, max_slice_depth=1)
# Define the maximum number of qwc groups allowed in the
# backpropagated observable,
# and the truncation error budget
op_budget = OperatorBudget(max_qwc_groups=15)
truncation_error_budget = setup_budget(
max_error_total=0.03, max_error_per_slice=0.005
)
# First backpropagation without truncation
bp_obs, remaining_slices, metadata = backpropagate(
observable, slices, operator_budget=op_budget
)
bp_circuit = combine_slices(remaining_slices)
# Now backpropagate with truncation, using the same operator budget and
# the defined truncation error budget
bp_obs_trunc, remaining_slices_trunc, metadata = backpropagate(
observable,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
bp_circuit_trunc = combine_slices(
remaining_slices_trunc, include_barriers=False
)
# Now we transpile the original circuit and the two backpropagated circuits,
# and apply the layout to the corresponding observables
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
isa_circuit = pm.run(circuit)
isa_bp_circuit = pm.run(bp_circuit)
isa_bp_circuit_trunc = pm.run(bp_circuit_trunc)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_bp_observable = bp_obs.apply_layout(isa_bp_circuit.layout)
isa_bp_observable_trunc = bp_obs_trunc.apply_layout(
isa_bp_circuit_trunc.layout
)
# Compare the 2-qubit depth of each transpiled circuit to see how much
# depth backpropagation saved
print(
f"2-qubit depth without backpropagation: "
f"{isa_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"2-qubit depth with backpropagation: "
f"{isa_bp_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"2-qubit depth with backpropagation and truncation: "
f"{isa_bp_circuit_trunc.depth(lambda x: x.operation.num_qubits == 2)}"
)
pubs = [
(isa_circuit, isa_observable),
(isa_bp_circuit, isa_bp_observable),
(isa_bp_circuit_trunc, isa_bp_observable_trunc),
]
# Now we instantiate the Estimator primitive for the hardware with
# ZNE and measurement error
# mitigation and compute the three circuits and observables
options = EstimatorOptions()
options.default_precision = 0.01
options.resilience_level = 2
options.resilience.zne.noise_factors = [1, 1.2, 1.4]
options.resilience.zne.extrapolator = ["linear"]
estimator = EstimatorV2(mode=backend, options=options)
estimator.options.environment.job_tags = ["TUT_OBP"]
job = estimator.run(pubs)
# Retrieve the results and the standard deviations
result_no_bp = job.result()[0].data.evs.item()
result_bp = job.result()[1].data.evs.item()
result_bp_trunc = job.result()[2].data.evs.item()
std_no_bp = job.result()[0].data.stds.item()
std_bp = job.result()[1].data.stds.item()
std_bp_trunc = job.result()[2].data.stds.item()Output:
2-qubit depth without backpropagation: 24
2-qubit depth with backpropagation: 20
2-qubit depth with backpropagation and truncation: 18
print(f"Expectation value without backpropagation: {result_no_bp}")
print(f"Backpropagated expectation value: {result_bp}")
print(f"Backpropagated expectation value with truncation: {result_bp_trunc}")Output:
Expectation value without backpropagation: 0.9543907942381811
Backpropagated expectation value: 0.9445337385406468
Backpropagated expectation value with truncation: 0.934050286970965
# Plot the results
methods = [
"No backpropagation",
"Backpropagation",
"Backpropagation w/ truncation",
]
values = [result_no_bp, result_bp, result_bp_trunc]
error_bars = [std_no_bp, std_bp, std_bp_trunc]
ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.errorbar(methods, values, yerr=error_bars, fmt="o", color="r", capsize=5)
plt.axhline(0.89)
ax.set_ylim([0.8, 0.98])
plt.text(0.25, 0.895, "Exact result")
ax.set_ylabel(r"$M_Z$", fontsize=12)Output:
Text(0, 0.5, '$M_Z$')
다음 단계
이 글이 흥미로웠다면, 다음 자료도 관심 있게 읽어보시기 바랍니다:
- 시간 진화 회로를 위한 근사 양자 컴파일링
- 트로터 오차를 줄이기 위한 다중 제품 공식
pauli-prop, 파울리 전파를 위한 Rust 기반 가속 패키지이며, OBP, 고전적 기대값 추정, 노이즈가 포함된 시뮬레이션에 대한 튜토리얼을 포함하고 있습니다