Přeskočit na hlavní obsah

Řezání vodičů pro odhad střední hodnoty

Odhadovaná spotřeba: jedna minuta na procesoru Eagle (POZNÁMKA: Jde pouze o odhad. Tvůj skutečný čas může být jiný.)

Pozadí

Circuit-knitting je zastřešující pojem, který zahrnuje různé metody rozdělení Circuitu do několika menších podřadů s méně Gaty a/nebo Qubity. Každý z podřadů lze spustit nezávisle a výsledný výstup se získá klasickým post-processingem výsledků jednotlivých podřadů. Tato technika je dostupná v doplňku Qiskit pro řezání Circuit, podrobné vysvětlení techniky najdeš v dokumentaci spolu s dalšími úvodními materiály.

Tento notebook se zabývá metodou zvanou řezání vodičů (wire cutting), při níž je Circuit rozdělen podél vodiče [1], [2]. Všimni si, že rozdělení je v klasických Circuit jednoduché, protože výsledek v místě rozdělení lze určit deterministicky a je buď 0, nebo 1. Stav Qubitu v místě řezu je však obecně smíšený stav. Proto je třeba každý podřad měřit vícekrát v různých bázích (obvykle v tomograficky úplné sadě bází, jako je Pauliho báze [3], [4]), přičemž Circuit se připravuje do odpovídajícího vlastního stavu. Obrázek níže (zdroj: disertační práce, Ritajit Majumdar) ukazuje příklad řezání vodičů pro 4-qubitový stav GHZ rozdělený do tří podřadů. Zde MjM_j označuje sadu bází (obvykle Pauliho X, Y a Z) a PiP_i sadu vlastních stavů (obvykle 0|0\rangle, 1|1\rangle, +|+\rangle a +i|+i\rangle).

wc-1.png wc-2.png

Protože každý podřad obsahuje méně Qubitů a/nebo Gatů, očekává se, že bude méně náchylný k šumu. Tento notebook ukazuje příklad, kdy lze tuto metodu použít k efektivnímu potlačení šumu v systému.

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 Qiskit pro řezání Circuit v0.9.0 nebo novější (pip install qiskit-addon-cutting)

Pro tento notebook budeme uvažovat Circuit Many Body Localization (MBL). MBL Circuit je hardwarově efektivní Circuit parametrizovaný dvěma parametry θ\theta a ϕ\vec{\phi}. Když je θ\theta nastaveno na 00 a počáteční stav je připraven v 0|0\rangle pro všechny Qubity, ideální střední hodnota Zi\langle Z_i \rangle je +1+1 pro každou pozici Qubitu ii bez ohledu na hodnoty ϕ\vec{\phi}. Více podrobností o MBL Circuitech najdeš v tomto článku.

Nastavení

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
import numpy as np
import matplotlib.pyplot as plt

from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.result import sampled_expectation_value

from qiskit_addon_cutting.instructions import CutWire
from qiskit_addon_cutting import (
cut_wires,
expand_observables,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, Batch

class MBLChainCircuit(QuantumCircuit):
def __init__(
self, num_qubits: int, depth: int, use_cut: bool = False
) -> None:
super().__init__(
num_qubits, name=f"MBLChainCircuit<{num_qubits}, {depth}>"
)
evolution = MBLChainEvolution(num_qubits, depth, use_cut)
self.compose(evolution, inplace=True)

class MBLChainEvolution(QuantumCircuit):
def __init__(self, num_qubits: int, depth: int, use_cut) -> None:
super().__init__(
num_qubits, name=f"MBLChainEvolution<{num_qubits}, {depth}>"
)

theta = Parameter("θ")
phis = ParameterVector("φ", num_qubits)

for layer in range(depth):
layer_parity = layer % 2
# print("layer parity", layer_parity)
for qubit in range(layer_parity, num_qubits - 1, 2):
# print(qubit)
self.cz(qubit, qubit + 1)
self.u(theta, 0, np.pi, qubit)
self.u(theta, 0, np.pi, qubit + 1)
if (
use_cut
and layer_parity == 0
and (
qubit == num_qubits // 2 - 1
or qubit == num_qubits // 2
)
):
self.append(CutWire(), [num_qubits // 2])
if use_cut and layer < depth - 1 and layer_parity == 1:
if qubit == num_qubits // 2:
self.append(CutWire(), [qubit])
for qubit in range(num_qubits):
self.p(phis[qubit], qubit)

Část I. Příklad v malém měřítku

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

Nejprve sestavíme šablonový Circuit bez konkrétních hodnot parametrů. Vložíme také zástupné symboly zvané CutWire, které označují místa řezů. Pro příklad v malém měřítku uvažujeme 10-qubitový MBL Circuit.

num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)

Output of the previous code cell

Připomeňme, že chceme najít střední hodnotu pozorovatelné 1ni=1nZi\frac{1}{n}\sum_{i=1} ^n Z_i při θ=0\theta=0. Pro parametr ϕ\vec{\phi} zvolíme náhodné hodnoty.

phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
params
[0,
0.2376615174332788,
0.28244289857682414,
0.019248960591717768,
0.46140600996102477,
0.31408025180068433,
0.718184005135733,
0.991153920182475,
0.09289485768301442,
0.8857848280067783,
0.6177529765767047]

Nyní označíme Circuit pro řezání vložením příslušných CutWire tak, aby vznikly dva přibližně stejně velké úseky. Nastavíme use_cut=True ve funkci a umožníme jí označit řezy za n2\frac{n}{2} Qubity, kde nn je počet Qubitů v původním Circuitu.

mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)

Output of the previous code cell

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

Dále rozdělíme Circuit na dva menší podřady. Pro tento příklad se omezíme na 2 podřady. K tomu využijeme Qiskit Addon: Circuit Cutting.

Rozdělení Circuitu na menší podřady

Přeříznutí vodiče v daném místě zvýší počet Qubitů o jeden. Kromě původního Qubitu přibyde ještě jeden navíc jako zástupný prvek pro Circuit po řezu. Následující obrázek to znázorňuje:

wc-4.png

Tento Addon využívá funkci cut_wires k zahrnutí extra Qubitů vzniklých řezáním.

mbl_move = cut_wires(mbl_cut)

Vytvoření a rozšíření pozorovatelných

Nyní sestavíme pozorovatelnou Mz=1ni=1nZiM_z = \frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle. Protože ideální výsledek Zi\langle Z_i \rangle pro každé ii je +1+1, ideální výsledek MzM_z je také +1+1.

observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
'IIIIIIIIZI', 'IIIIIIIIIZ'])

Všimni si, že počet Qubitů v Circuitu se po vložení virtuálních 2-qubitových operací Move při řezání zvýšil. Proto je třeba rozšířit i pozorovatelné vložením identit tak, aby odpovídaly aktuálnímu Circuitu.

new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])

Všimni si, že každá pozorovatelná se nyní rozšířila na sedm Qubitů (odpovídajících Circuitu s operací Move), namísto původních 6 Qubitů. Dále rozdělíme Circuit na dva podřady.

partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)

Vizualizujme podřady

subcircuits = partitioned_problem.subcircuits
subcircuits[0].draw("mpl", fold=-1)

Output of the previous code cell

subcircuits[1].draw("mpl", fold=-1)

Output of the previous code cell

Pozorovatelné byly také rozděleny, aby odpovídaly podřadům

subobservables = partitioned_problem.subobservables
subobservables
{0: PauliList(['IIIIII', 'IIIIII', 'IIIIII', 'IIIIII', 'IIIIII', 'IZIIII',
'IIZIII', 'IIIZII', 'IIIIZI', 'IIIIIZ']),
1: PauliList(['ZIIII', 'IZIII', 'IIZII', 'IIIZI', 'IIIIZ', 'IIIII', 'IIIII',
'IIIII', 'IIIII', 'IIIII'])}

Všimni si, že každý podřad vede k určitému počtu vzorků. Rekonstrukce bere v úvahu výsledek každého z těchto vzorků. Každý takový vzorek se označuje jako subexperiment. Rozšíření pozorovatelné pomocí operace Move vyžaduje datovou strukturu PauliList. Pozorovatelnou MzM_z lze také vytvořit v obecnější datové struktuře SparsePauliOp, která bude užitečná při pozdější rekonstrukci subexperimentů.

M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
M_z
SparsePauliOp(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII', 'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII', 'IIIIIIIIZI', 'IIIIIIIIIZ'],
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])
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)

Podívejme se na dva příklady, kdy jsou řezané Qubity měřeny ve dvou různých bázích. Nejprve je měřen v běžné bázi Z, poté v bázi X.

subexperiments[0][6].draw("mpl", fold=-1)

Output of the previous code cell

subexperiments[0][2].draw("mpl", fold=-1)

Output of the previous code cell

Transpilace každého subexperimentu

Před odesláním Circuitů ke spuštění je nyní třeba je transpilovat. Proto nejprve transpilujeme každý Circuit v subexperimentech.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)

Nyní potřebujeme transpilovat každý Circuit v subexperimentech. Nejprve vytvoříme pass manager a poté ho použijeme k transpilaci jednotlivých Circuitů.

pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
isa_subexperiments[0][0].draw("mpl", fold=-1, idle_wires=False)

Output of the previous code cell

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

Nyní spustíme každý Circuit v subexperimentu. Qiskit-addon-cutting používá SamplerV2 ke spuštění subexperimentů.

with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}

Krok 4: Post-processing a vrácení výsledku v požadovaném klasickém formátu

Po spuštění Circuitů je třeba načíst výsledky a rekonstruovat střední hodnotu pro neřezaný Circuit a původní pozorovatelnou.

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
0.9674376845359803

Křížové ověření

Nyní spusťme Circuit bez řezání a zkontrolujme výsledek. Všimni si, že pro spuštění neřezaného Circuitu bychom mohli přímo použít EstimatorV2 k výpočtu středních hodnot. Ale budeme používat stejnou Primitive po celou dobu. Proto použijeme SamplerV2 k získání rozdělení pravděpodobností a vypočítáme střední hodnotu pomocí funkce sampled_expectation_value.

Nejprve potřebujeme transpilovat neřezaný Circuit mbl.

sampler = SamplerV2(mode=backend)

if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)

Dále sestavíme pub a spustíme neřezaný Circuit.

pub = (isa_mbl, params)
uncut_job = sampler.run([pub])
uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)
uncut_expval
0.9498046875000001

Všimněme si, že střední hodnota získaná pomocí řezání vodičů je blíže ideální hodnotě +1+1 než výsledek neřezaného Circuitu. Nyní zvětšíme rozsah problému.

Část II. Zvětši to!

Dříve jsme ukázali výsledky pro 10-qubitový MBL Circuit. Nyní ukážeme, že zlepšení střední hodnoty lze dosáhnout i pro větší Circuity. Abychom to demonstrovali, zopakujeme postup pro 60-qubitový MBL Circuit.

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

num_qubits = 60
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)

Vytvoříme náhodnou sadu hodnot pro ϕ\vec{\phi}

phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

Dále sestavíme přeříznutý Circuit

mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)

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

Jak bylo ukázáno na příkladu malého měřítka, rozdělíme Circuit a pozorovatelnou veličinu pro experimenty s řezáním.

mbl_move = cut_wires(mbl_cut)

# Define observable
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)

# Partition the circuit into subcircuits
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)

# Get subcircuits
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

Vytvoříme také objekt SparsePauliOp pro pozorovatelnou veličinu se správnými koeficienty.

M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)

Dále vygenerujeme dílčí experimenty a transpilujeme každý Circuit v dílčím experimentu.

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

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

Pro spuštění všech Circuitů v dílčích experimentech použijeme režim Batch.

with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}

Krok 4: Post-processing a vrácení výsledku v požadovaném klasickém formátu

Nyní načteme výsledky pro každý Circuit v dílčím experimentu a rekonstruujeme střední hodnotu odpovídající nepřeříznutému Circuitu a původní pozorovatelné veličině.

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
0.9631355921427409

Křížové ověření

Stejně jako v příkladu malého měřítka znovu získáme střední hodnotu spuštěním nepřeříznutého Circuitu a porovnáme výsledek s řezáním Circuitu. Pro zachování jednotnosti v použití primitiv použijeme SamplerV2.

sampler = SamplerV2(mode=backend)

if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)

pub = (isa_mbl, params)
uncut_job = sampler.run([pub])
uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)
uncut_expval
0.9426757812499998

Vizualizace

Vizualizujme zlepšení střední hodnoty dosažené použitím wire cutting.

ax = plt.gca()
methods = ["cut", "uncut"]
values = [reconstructed_expval, uncut_expval]

plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
ax.set_ylim([0.85, 1.02])
plt.text(0.3, 0.99, "Exact result")
plt.show()

Output of the previous code cell

Závěr

Pozorujeme, že jak v problémech malého, tak velkého měřítka vede wire cutting k lepším výsledkům než nepřeříznutý Circuit. Všimni si, že pro tyto experimenty nebyly použity žádné techniky zmírňování chyb. Zlepšení výsledku je tedy způsobeno pouze wire cutting. Výsledky by mohlo být možné dále vylepšit pomocí různých metod zmírňování v kombinaci s řezáním Circuitu.

Navíc jsme v tomto notebooku vypočítali oba dílčí Circuity na stejném hardware. V [5], [6] autoři ukazují metodu distribuce dílčích Circuitů na různý hardware s využitím informací o šumu za účelem maximalizace potlačení šumu a paralelizace procesu.

Příloha: úvaha o škálování zdrojů

Počet Circuitů k spuštění roste s počtem řezů. Ačkoli mnoho řezů může produkovat malé dílčí Circuity a dále zlepšovat výkon, vede to také k výrazně vysokému počtu spuštění Circuitů, což nemusí být pro většinu případů praktické. Níže ukazujeme příklad počtu dílčích Circuitů odpovídajících počtu řezů pro 50-qubitový Circuit.

wc-5.png

Všimni si, že i pro pět řezů je počet dílčích experimentů kolem 200 tis. Proto by mělo být řezání Circuitu použito pouze tehdy, když je počet řezů malý.

Jeden příklad přívětivého a nepřívětivého Circuitu pro řezání

Circuit přívětivý pro řezání

Jak bylo zmíněno dříve, Circuit je přívětivý pro řezání tehdy, když lze Circuit rozdělit na menší disjunktní dílčí Circuity s malým počtem řezů. Jakýkoli hardwarově efektivní Circuit, tj. Circuit vyžadující při mapování na coupling map hardware málo nebo žádné SWAP Gaty, je obecně přívětivý pro řezání. Níže ukazujeme příklad ansatzu zachovávajícího excitaci, který se používá v kvantové chemii. Všimni si, že takový Circuit lze rozdělit na dva dílčí Circuity jediným řezem bez ohledu na počet Qubitů.

wc-6.png

Circuit nepřívětivý pro řezání

Circuit je nepřívětivý pro řezání tehdy, když počet řezů potřebných k vytvoření disjunktních oddílů obecně výrazně roste s hloubkou nebo počtem Qubitů. Připomeňme, že s každým řezem je potřeba jeden extra Qubit. S počtem řezů tedy roste i efektivní počet Qubitů. Níže ukazujeme příklad 3-qubitového Groverova Circuitu s možnou instancí řezání.

wc-7.png

Všimni si, že jsou potřeba tři řezy a řez je spíše vertikální než horizontální. To znamená, že se očekává, že počet řezů bude lineárně růst s počtem Qubitů, což není pro řezání vhodné.

Reference

[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulating large quantum circuits on a small quantum computer. Physical review letters, 125(15), 150504.

[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: using small quantum computers for large quantum circuit evaluations. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).

[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum circuit cutting with maximum-likelihood tomography. npj Quantum Information, 7(1), 64.

[4] Majumdar, R., & Wood, C. J. (2022). Error mitigated quantum circuit cutting. arXiv preprint arXiv:2211.13431.

[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelizing Quantum-Classical Workloads: Profiling the Impact of Splitting Techniques. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.

[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization. arXiv preprint arXiv:2309.06005.

Průzkum tutoriálu

Vyplň prosím tento krátký průzkum a poskytni nám zpětnou vazbu k tomuto tutoriálu. Tvoje poznatky nám pomohou zlepšit náš obsah a uživatelský zážitek.

Odkaz na průzkum

Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.