Ř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 označuje sadu bází (obvykle Pauliho X, Y a Z) a sadu vlastních stavů (obvykle , , a ).

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 a . Když je nastaveno na a počáteční stav je připraven v pro všechny Qubity, ideální střední hodnota je pro každou pozici Qubitu bez ohledu na hodnoty . 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)

Připomeňme, že chceme najít střední hodnotu pozorovatelné při . Pro parametr 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 Qubity, kde 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)

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:

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 . Protože ideální výsledek pro každé je , ideální výsledek je také .
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)

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

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

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

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)

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ě 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
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()

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.

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ů.

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í.

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.
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.