Tăierea firelor pentru estimarea valorilor de așteptare
Estimare de utilizare: 22 de secunde pe un procesor Heron (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)
Rezultate ale învățării
După parcurgerea acestui tutorial, utilizatorii ar trebui să înțeleagă:
- Cum să folosești
qiskit-addon-cuttingpentru a partiționa un circuit mare în subcircuite mai mici, reducând astfel efectul zgomotului
Cerințe preliminare
Recomandăm utilizatorilor să fie familiarizați cu următorul subiect înainte de a parcurge acest tutorial:
- Utilizarea primitivei Sampler, care este folosită în acest flux de lucru
Context
Circuit-knitting este un termen general care înglobează diverse metode de partiționare a unui circuit în mai multe subcircuite mai mici, implicând mai puține porți sau qubiți. Fiecare dintre subcircuite poate fi executat independent, iar rezultatul final se obține prin procesare clasică ulterioară a rezultatelor fiecărui subcircuit. Această tehnică este accesibilă prin addon-ul Qiskit pentru tăierea circuitelor; consultă documentația alături de alt material introductiv pentru o explicație detaliată a tehnicii.
Acest tutorial se concentrează pe o metodă numită tăierea firelor, în care circuitul este partiționat de-a lungul firului [1], [2]. De remarcat că partiționarea este simplă în circuitele clasice, deoarece rezultatul la punctul de partiționare poate fi determinat determinist și este fie 0, fie 1. Cu toate acestea, starea qubitului la punctul de tăiere este, în general, o stare mixtă. Prin urmare, fiecare subcircuit trebuie măsurat de mai multe ori în baze diferite (de obicei o bază tomografic completă, cum ar fi baza Pauli [3], [4]) și pregătit corespunzător în propria sa stare proprie. Figura de mai jos (cu amabilitatea: [7]) arată un exemplu de tăiere a firelor pentru o stare GHZ cu patru qubiți în trei subcircuite. Aici denotă un set de baze (de obicei Pauli X, Y și Z), iar denotă un set de stări proprii (de obicei , , și ).
Deoarece fiecare subcircuit are mai puțini qubiți și porți, este de așteptat ca acestea să fie mai puțin susceptibile la zgomot. Acest tutorial arată un exemplu în care această metodă poate fi utilizată pentru a suprima eficient zgomotul din sistem.
Cerințe
Înainte de a începe acest tutorial, asigură-te că ai instalate următoarele:
- Qiskit SDK v2.0 sau mai recent, cu suport pentru vizualizare
- Qiskit Runtime v0.22 sau mai recent (
pip install qiskit-ibm-runtime) - Addon-ul Qiskit pentru tăierea circuitelor v0.10.0 sau mai recent (
pip install qiskit-addon-cutting) - Qiskit addon utils 0.3 sau mai recent (
pip install qiskit-addon-utils) - Qiskit Aer (
pip install qiskit-aer)
Configurare
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-aer 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_aer import AerSimulator
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
Exemplu la scară mică cu simulator
Acest tutorial implementează un tipar Qiskit pentru a simula un circuit Many-Body Localization (MBL) unidimensional (1D). Circuitul MBL este un circuit eficient pentru hardware și este parametrizat de doi parametri și . Când este setat la și starea inițială este pregătită în pentru toți qubiții, valoarea de așteptare ideală a este pentru fiecare site de qubit , indiferent de valorile lui . Mai multe detalii despre acest circuit sunt disponibile în acest articol.
De remarcat că într-un simulator fără zgomot, valoarea de așteptare obținută cu și fără tăierea circuitului va fi aceeași.
Pasul 1: Maparea intrărilor clasice la o problemă cuantică
Construirea circuitului MBL 1D
Mai întâi, prezentăm o funcție pentru construirea circuitului MBL 1D.
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)
num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)
Calculăm valoarea medie de așteptare peste toți qubiții pentru . Deoarece valoarea de așteptare ideală a , valoarea de așteptare ideală a lui este, de asemenea, . Parametrii sunt selectați aleatoriu.
np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
Circuitul trebuie adnotat prin inserarea CutWire-urilor în locațiile dorite pentru a-l partiționa. Pentru acest tutorial, optăm pentru o partiție egală. Circuitul MBL este proiectat astfel încât setând use_cut=True în funcție inserează adnotarea în mod corespunzător după qubiți, unde este numărul de qubiți din circuitul original. Am atribuit, de asemenea, parametrii generați aleatoriu circuitului.
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)
Pasul 2: Optimizarea problemei pentru execuția pe hardware cuantic
Tăierea circuitului în subcircuite mai mici
Acum partiționăm circuitul în două subcircuite mai mici folosind qiskit-addon-cutting. qiskit-addon-cutting adaugă o poartă virtuală Move pentru a diviza locația tăierii firului prin ajustarea corespunzătoare a numărului de qubiți. Acum creăm circuitul cu această poartă virtuală. Deoarece există o singură tăiere de fir, numărul de qubiți asociați va crește cu 1.
mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)
Construirea și extinderea observabilului
Observabilul, definit anterior, va fi media lui pe fiecare qubit. Cu toate acestea, după inserarea porții virtuale Move, numărul efectiv de qubiți din circuit crește. Observabilul trebuie, de asemenea, extins corespunzător pentru a ține cont de această schimbare în numărul de qubiți. De remarcat că observabilul acționează întotdeauna trivial (ca ) pe qubitul suplimentar adăugat pentru poarta virtuală Move.
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'])
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])
Acum circuitul poate fi partiționat de-a lungul porții Move și obținem subcircuitele, precum și subobservabilul, care reprezintă porțiunea din observabilul original asociată cu fiecare subcircuit.
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
Aici vizualizăm cele două subcircuite:
subcircuits[0].draw("mpl", fold=-1)
subcircuits[1].draw("mpl", fold=-1)
Extinderea observabilului folosind operația Move necesită o structură de date PauliList. Pentru a reconstitui valoarea de așteptare a circuitului original, avem nevoie de observabil în formatul SparsePauliOp.
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
Așa cum s-a discutat anterior, pentru fiecare tăiere circuitul din amonte trebuie măsurat într-o bază Pauli, iar circuitul din aval trebuie pregătit în starea proprie a bazei. Funcția generate_cutting_experiments creează toate aceste circuite necesare și coeficienții asociați fiecărui circuit necesari pentru reconstrucție. Găsești mai multe detalii în această lucrare.
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
Transpilarea circuitelor pe backend
Pentru primul exemplu care implică doar simulare, transpilăm circuitul în setul de porți de bază al backend-ului:
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)
<IBMBackend('ibm_fez')>
Pasul 3: Execuția folosind primitivele Qiskit
Acum, executăm fiecare subexperiment:
pm_basis = generate_preset_pass_manager(
optimization_level=2, basis_gates=backend.configuration().basis_gates
)
basis_subexperiments = {
label: pm_basis.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
sampler = SamplerV2(mode=AerSimulator())
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in basis_subexperiments.items()
}
Pasul 4: Post-procesarea și returnarea rezultatului în formatul clasic dorit
Acum recuperăm rezultatul fiecărui subexperiment rulat și reconstruim valoarea de așteptare a circuitului netăiat:
# 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
np.float64(0.9953821063041687)
methods = [
"Uncut",
"Wire cut",
]
values = [
1,
reconstructed_expval,
] # since the ideal expectation value in noiseless simulation is +1
ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylabel(r"$M_Z$", fontsize=12)
Text(0, 0.5, '$M_Z$')
Exemplu la scară mare pe hardware
Acum demonstrăm tăierea firelor pentru un circuit MBL de 60 de qubiți. Atât circuitul netăiat, cât și cel tăiat vor fi executate pe hardware IBM Quantum®:
num_qubits = 60
depth = 2
# construct the circuit
mbl = MBLChainCircuit(num_qubits, depth)
# create parameters
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
# construct the cut circuit
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_move = cut_wires(mbl_cut)
# Define observable and expand to account for the wire cut
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)
# Construct a SparsePauliOp version of the observable for later use in reconstruction
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
# Partition the circuit and get subcircuits and subobservables
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
# Obtain subexperiments and coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
# Transpile the subexperiments to the backend
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
# Execute the subexperiments and retrieve results
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
sampler.options.environment.job_tags = ["TUT_WC"]
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
results = {label: job.result() for label, job in jobs.items()}
# Reconstruct the expectation value of the original observable
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
# Compute the uncut circuit to obtain the noisy expectation value for comparison
sampler = SamplerV2(mode=backend)
sampler.options.environment.job_tags = ["TUT_WC"]
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)
# visualize the results
ax = plt.gca()
methods = ["uncut", "cut"]
values = [uncut_expval, reconstructed_expval]
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
plt.text(0.3, 0.95, "Exact result")
plt.show()
uncut_expval
0.9202473958333336
Pași următori
Dacă ai găsit această lucrare interesantă, s-ar putea să fii interesat de următorul material:
Referințe
[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.
[7] Majumdar, R. (2024). Efficient Reduction of Resources and Noise in Discrete Quantum Computing Circuits (Doctoral dissertation, Indian Statistical Institute - Kolkata). https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar&cbl=2026366&diss=y