Přeskočit na hlavní obsah

Zpětná propagace operátoru (OBP) pro odhad střední hodnoty

Odhadovaná doba použití: 4 minuty na procesoru Heron r3 (POZNÁMKA: Jedná se pouze o odhad. Skutečná doba běhu se může lišit.)

Výsledky učení

Po absolvování tohoto tutoriálu by uživatelé měli rozumět:

  • Jak používat qiskit-addon-obp ke snížení hloubky kvantového obvodu za cenu zvýšeného počtu spuštění obvodu
  • Jak používat qiskit-addon-utils k vytváření XYZ hamiltonianů a jejich obvodů časového vývoje

Předpoklady

Doporučujeme, aby uživatelé před tímto tutoriálem ovládali tato témata:

  • Použití primitivy Estimator pro výpočet středních hodnot pozorovatelné veličiny

Pozadí

Zpětná propagace operátoru je technika, která spočívá v absorpci operací z konce kvantového obvodu do měřené pozorovatelné veličiny. Obecně tak snižuje hloubku obvodu za cenu většího počtu členů v pozorovatelné veličině. Cílem je zpětně propagovat co největší část obvodu bez toho, aby pozorovatelná veličina příliš narostla. Implementace v Qiskitu je k dispozici v doplňku OBP Qiskit. Podrobnosti najdeš v příslušné dokumentaci.

Uvažujme příkladový obvod, pro nějž má být změřena pozorovatelná veličina O=PcPPO = \sum_P c_P P, kde PP jsou Pauliho operátory a cPc_P jsou koeficienty. Obvod označíme jako jedinou unitární transformaci UU, kterou lze logicky rozdělit na U=UCUQU = U_C U_Q, jak je znázorněno na obrázku níže.

Circuit diagram showing Uq followed by Uc

Zpětná propagace operátoru absorbuje unitární transformaci UCU_C do pozorovatelné veličiny jejím vyvinutím jako O=UCOUC=PcPUCPUCO' = U_C^{\dagger}OU_C = \sum_P c_P U_C^{\dagger}PU_C. Jinými slovy, část výpočtu se provede klasicky pomocí vývoje pozorovatelné veličiny z OO na OO'. Původní problém lze nyní přeformulovat jako měření pozorovatelné veličiny OO' pro nový obvod s menší hloubkou, jehož unitární transformace je UQU_Q.

Unitární transformace UCU_C je reprezentována jako počet řezů UC=USUS1...U2U1U_C = U_S U_{S-1}...U_2U_1. Existuje více způsobů, jak definovat řez. Například ve výše uvedeném příkladovém obvodu lze každou vrstvu RzzR_{zz} a každou vrstvu brány RxR_x považovat za samostatný řez. Zpětná propagace zahrnuje klasický výpočet O=Πs=1SPcPUsPUsO' = \Pi_{s=1}^S \sum_P c_P U_s^{\dagger} P U_s. Každý řez UsU_s lze vyjádřit jako Us=exp(iθsPs2)U_s = exp(\frac{-i\theta_s P_s}{2}), kde PsP_s je nn-qubitový Pauliho operátor a θs\theta_s je skalár. Snadno ověříme, že

UsPUs=Pif [P,Ps]=0,U_s^{\dagger} P U_s = P \qquad \text{if} ~[P,P_s] = 0, UsPUs=cos(θs)P+isin(θs)PsPif {P,Ps}=0U_s^{\dagger} P U_s = \qquad cos(\theta_s)P + i sin(\theta_s)P_sP \qquad \text{if} ~\{P,P_s\} = 0

Ve výše uvedeném příkladu, pokud {P,Ps}=0\{P,P_s\} = 0, potřebujeme ke výpočtu střední hodnoty spustit dva kvantové obvody místo jednoho. Proto zpětná propagace může zvýšit počet členů v pozorovatelné veličině, což vede k vyššímu počtu spuštění obvodů. Jedním ze způsobů, jak umožnit hlubší zpětnou propagaci do obvodu a zároveň zabránit příliš velkému nárůstu operátoru, je ořezat členy s malými koeficienty namísto jejich přidání do operátoru. Například v uvedeném příkladu lze zvolit ořezání členu obsahujícího PsPP_sP za předpokladu, že θs\theta_s je dostatečně malý. Ořezání členů může vést k menšímu počtu kvantových obvodů, ale způsobuje určitou chybu ve výsledném výpočtu střední hodnoty úměrnou velikosti koeficientů ořezaných členů.

Požadavky

Před zahájením tohoto tutoriálu se ujisti, že máš nainstalováno následující:

  • Qiskit SDK v2.0 nebo novější, s podporou vizualizace
  • Qiskit Runtime v0.22 nebo novější (pip install qiskit-ibm-runtime)
  • Doplněk OBP Qiskit 0.3 nebo novější (pip install qiskit-addon-obp)
  • Pomocné nástroje Qiskit addon 0.3 nebo novější (pip install qiskit-addon-utils)

Nastavení

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-obp qiskit-addon-utils qiskit-ibm-runtime rustworkx
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

Příklad na simulátoru v malém měřítku

Tento tutoriál implementuje vzor Qiskit pro simulaci kvantové dynamiky Heisenbergova spinového řetězce pomocí doplňku OBP Qiskit. Všimni si, že na bezešumém simulátoru bude střední hodnota získaná s i bez zpětné propagace stejná.

Krok 1: Mapování klasických vstupů na kvantový problém

Mapování časového vývoje kvantového Heisenbergova modelu na kvantový experiment

Nejprve použijeme funkci generate_xyz_hamiltonian z balíčku qiskit-addon-utils pro generování Heisenbergova hamiltoniánu na daném grafu konektivity. Tento graf může být buď rustworkx.PyGraph nebo CouplingMap. V následující části použijeme lineární řetězec CouplingMap 10 Qubitů.

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

Dále vygenerujeme Pauliho operátor modelující Heisenbergův hamiltonián XYZ:

H^XYZ=(j,k)E(Jxσjxσkx+Jyσjyσky+Jzσjzσkz)+jV(hxσjx+hyσjy+hzσjz),{\hat{\mathcal{H}}_{XYZ} = \sum_{(j,k)\in E} (J_{x} \sigma_j^{x} \sigma_{k}^{x} + J_{y} \sigma_j^{y} \sigma_{k}^{y} + J_{z} \sigma_j^{z} \sigma_{k}^{z}) + \sum_{j\in V} (h_{x} \sigma_j^{x} + h_{y} \sigma_j^{y} + h_{z} \sigma_j^{z}),}

kde G(V,E)G(V,E) je graf mapy propojení. Pro tento tutoriál jsme použili Jx,Jy,JzJ_x, J_y, J_z rovné π8,π4,π2\frac{\pi}{8}, \frac{\pi}{4}, \frac{\pi}{2} a hx,hy,hzh_x, h_y, h_z rovné π3,π6,π9\frac{\pi}{3}, \frac{\pi}{6}, \frac{\pi}{9}.

# 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)
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])

Z qubitového operátoru můžeme vygenerovat kvantový obvod modelující jeho časový vývoj. Použili jsme funkci generate_time_evolution_circuit s Lie-Trotterovou dekompozicí pro sestavení obvodu časového vývoje.

circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=2),
)
circuit.draw("mpl", style="iqp", fold=-1)

Output of the previous code cell

Krok 2: Optimalizace problému pro spuštění na kvantovém hardwaru

Vytvoření řezů obvodu pro zpětnou propagaci

Funkce backpropagate zpracovává vždy celé řezy obvodu najednou, takže způsob dělení může ovlivnit výkon zpětné propagace pro daný problém. Zde seskupíme brány stejného typu do řezů pomocí funkce slice_by_depth.

Podrobnější diskusi o dělení obvodu najdeš v tomto návodu balíčku qiskit-addon-utils.

slices = slice_by_depth(circuit, max_slice_depth=1)
print(f"Separated the circuit into {len(slices)} slices.")
Separated the circuit into 18 slices.

Omezení maximálního nárůstu operátoru během zpětné propagace

Během zpětné propagace počet členů v operátoru obecně rychle narůstá směrem k 2L2^L, kde LL je počet řezů. Pokud dva členy operátoru vzájemně nekomutují po qubitech, potřebujeme k získání odpovídajících středních hodnot samostatné obvody. Například pro dvouqubitovou pozorovatelnou veličinu O=0.1XX+0.3IZ0.5IXO = 0.1 XX + 0.3 IZ - 0.5 IX platí, že [XX,IX]=0[XX,IX] = 0, takže k výpočtu středních hodnot těchto dvou členů stačí měření v jediné bázi. Avšak IZIZ antikomutuje s oběma zbývajícími členy, takže potřebujeme samostatné měření v jiné bázi pro výpočet střední hodnoty IZIZ. Jinými slovy, k výpočtu O\langle O \rangle potřebujeme dva obvody místo jednoho. S narůstajícím počtem členů v operátoru může narůstat i požadovaný počet spuštění obvodů.

Velikost operátoru lze omezit zadáním argumentu operator_budget funkce backpropagate, který přijímá instanci OperatorBudget.

Abychom omezili množství přidělených zdrojů (počet spuštění obvodů, a tedy požadovaný čas QPU), omezíme maximální počet qubitově komutujících Pauliho skupin, které může mít zpětně propagovaná pozorovatelná veličina. Zde určíme, že zpětná propagace se zastaví, jakmile počet qubitově komutujících Pauliho skupin v operátoru překročí osm.

op_budget = OperatorBudget(max_qwc_groups=8)

Zpětná propagace řezů z obvodu

Nejprve definujeme pozorovatelnou veličinu jako MZ=1Ni=1NZiM_Z = \frac{1}{N} \sum_{i=1}^N \langle Z_i \rangle, kde NN je počet Qubitů. Budeme zpětně propagovat řezy z obvodu časového vývoje, dokud členy v pozorovatelné veličině nelze sloučit do osmi nebo méně qubitově komutujících Pauliho skupin.

observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / num_qubits) for i in range(num_qubits)],
num_qubits=num_qubits,
)
observable
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])

Níže uvidíš, že bylo zpětně propagováno šest řezů a členy byly sloučeny do šesti, nikoli osmi skupin. To znamená, že zpětná propagace dalšího řezu by způsobila překročení osmi Pauliho skupin. Tuto skutečnost můžeme ověřit prohlédnutím vrácených metadat. Všimni si také, že v této části je transformace obvodu přesná — žádné členy nové pozorovatelné veličiny OO' nebyly ořezány. Zpětně propagovaný obvod a zpětně propagovaný operátor dávají přesně stejný výsledek jako původní obvod a operátor.

# 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)
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:

Output of the previous code cell

Pro příklad malého měřítku na simulátoru nebudeme používat ořezání. Důvodem je, že v nepřítomnosti šumu vede obvod s i bez zpětné propagace ke stejnému výsledku a ořezání výsledek zhorší kvůli zavedené aproximaci.

Transpilace obvodu do sady bazových bran

Nyní transpilujeme původní i zpětně propagovaný obvod do bazových bran backendu. Nepotřebujeme transpilovat na skutečném backendu, protože pro malý příklad poběžíme na simulátoru.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)
<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)

Krok 3: Spuštění pomocí Qiskit primitiv

Nejprve vytvoříme dva Primitive Unified Blocs (PUBs) odpovídající původnímu obvodu a zpětně propagovanému obvodu. Poté spustíme PUBs na ideálním Estimatoru a získáme střední hodnoty.

pubs = [(isa_circuit, observable), (isa_bp_circuit, bp_obs)]
rng = np.random.default_rng()
estimator = StatevectorEstimator(seed=rng)
job = estimator.run(pubs)

Krok 4: Následné zpracování a vrácení výsledku do požadovaného klasického formátu

Nyní získáme střední hodnoty původního a zpětně propagovaného obvodu.

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)
Text(0, 0.5, '$M_Z$')

Output of the previous code cell

Jak se očekávalo, obě střední hodnoty se shodují. Protože provozujeme bezešumý stavový vektorový simulátor, je zpětná propagace přesnou transformací páru obvod–pozorovatelná veličina, takže původní i zpětně propagovaný postup musí produkovat stejnou hodnotu MZM_Z. Přínos zpětné propagace se projeví až na hlučném hardwaru, kde kratší zpětně propagovaný obvod akumuluje menší chybu, jak je ukázáno v příkladu velkého měřítka na hardwaru níže.

Příklad na hardwaru ve velkém měřítku

Při vývoji experimentu je užitečné začít s malým obvodem, aby se usnadnily vizualizace a simulace. Nyní se zaměříme na zpětnou propagaci operátoru pro 50-qubitový Heisenbergův hamiltonián se stejnými hodnotami parametrů JJ a hh a stejnou pozorovatelnou MZM_Z, ale pro čtyři Trotterovy kroky. Ideální střední hodnota v tomto měřítku nelze vypočítat hrubou silou, takže použijeme tenzorovou síť a získáme ideální střední hodnotu 0.89\simeq 0.89.

Spolu se zpětnou propagací v tomto rozsáhlém příkladu také představujeme zpětnou propagaci s ořezáním. Ideálně chceme zpětně propagovat co nejvíce, abychom snížili hloubku efektivního obvodu. Zpětná propagace však často vede k velkému počtu nekomutujících členů v aktualizované pozorovatelné veličině, čímž zvyšuje kvantovou režii. Proto lze eliminovat členy pozorovatelné veličiny s malými koeficienty pomocí postupu nazývaného ořezání. Ačkoli ořezání umožňuje hlubší propagaci snížením počtu členů v aktualizované pozorovatelné veličině, zavádí také jistou aproximaci. Je proto nutné omezit ořezání v určitých mezích tak, aby chyba aproximace nepřevážila snížení šumu díky hlubší zpětné propagaci.

K omezení množství ořezání přidělíme chybový rozpočet pro každý řez i celkový chybový rozpočet pro celý zpětně propagovaný obvod pomocí funkce setup_budget. Tím je zajištěno, že ořezání je řízeno jak pro každý řez, tak pro celý obvod. Viz také tento návod pro jiné způsoby alokace rozpočtu.

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()
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}")
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)
Text(0, 0.5, '$M_Z$')

Output of the previous code cell

Další kroky

Pokud tě tato práce zaujala, možná tě budou zajímat následující materiály:

Doporučení