Pagputol ng wire para sa pagtatantya ng expectation values
Tantiya sa paggamit: 22 segundo sa Heron processor (PAALALA: Ito ay tantiya lamang. Maaaring mag-iba ang iyong runtime.)
Mga resulta ng pag-aaral
Pagkatapos ng tutorial na ito, dapat maunawaan ng mga user ang:
- Paano gamitin ang
qiskit-addon-cuttingupang hatiin ang isang malaking circuit sa mas maliliit na subcircuits, na nagbabawas ng epekto ng noise
Mga Paunang Kaalaman
Iminumungkahi naming maging pamilyar ang mga user sa sumusunod na paksa bago dumaan sa tutorial na ito:
- Paggamit ng Sampler primitive, na ginagamit sa workflow na ito
Background
Ang circuit-knitting ay isang pangkalahatang termino na sumasaklaw sa iba't ibang pamamaraan ng paghahati ng circuit sa maraming mas maliit na subcircuits na may mas kaunting gates o qubits. Ang bawat isa sa mga subcircuits ay maaaring isagawa nang independiyente, at ang panghuling resulta ay nakukuha sa pamamagitan ng ilang classical post-processing sa kinalabasan ng bawat subcircuit. Ang pamamaraang ito ay maaaring ma-access sa circuit cutting Qiskit addon; tingnan ang dokumentasyon kasama ng iba pang introductory material para sa detalyadong paliwanag ng pamamaraan.
Ang tutorial na ito ay nakatuon sa isang pamamaraang tinatawag na wire cutting, kung saan ang circuit ay pinaghahati sa kahabaan ng wire [1], [2]. Tandaan na ang paghahati ay simple sa mga classical circuits dahil ang kinalabasan sa punto ng partition ay maaaring matukoy nang deterministiko, at ito ay 0 o 1 lamang. Gayunpaman, ang estado ng qubit sa punto ng pagputol ay, sa pangkalahatan, isang mixed state. Samakatuwid, ang bawat subcircuit ay kailangang sukatin nang maraming beses sa iba't ibang bases (karaniwang isang tomographically complete na basis, tulad ng Pauli basis [3], [4]) at naaangkop na inihanda sa kanyang eigenstate. Ang figure sa ibaba (pasasalamat: [7]) ay nagpapakita ng halimbawa ng wire cutting para sa apat na qubit na GHZ state sa tatlong subcircuits. Dito ang ay tumutukoy sa isang set ng mga bases (karaniwang Pauli X, Y at Z), at ang ay tumutukoy sa isang set ng eigenstates (karaniwang , , at ).
Dahil ang bawat subcircuit ay may mas kaunting qubits at gates, inaasahang mas hindi sila madaling maapektuhan ng noise. Ang tutorial na ito ay nagpapakita ng halimbawa kung saan ang pamamaraang ito ay maaaring gamitin upang epektibong pigilin ang noise sa sistema.
Mga Kinakailangan
Bago simulan ang tutorial na ito, tiyaking mayroon kayong mga sumusunod na naka-install:
- Qiskit SDK v2.0 o mas bago, na may visualization support
- Qiskit Runtime v0.22 o mas bago (
pip install qiskit-ibm-runtime) - Circuit cutting Qiskit addon v0.10.0 o mas bago (
pip install qiskit-addon-cutting) - Qiskit addon utils 0.3 o mas bago (
pip install qiskit-addon-utils) - Qiskit Aer (
pip install qiskit-aer)
Setup
# 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
Halimbawa gamit ang maliit na simulator
Ipinapatupad ng tutorial na ito ang isang Qiskit pattern upang i-simulate ang isang one-dimensional (1D) Many-Body Localization (MBL) circuit. Ang MBL circuit ay isang hardware-efficient circuit at may parameter na dalawang parameters na at . Kapag ang ay nakatakda sa at ang initial state ay inihanda sa para sa lahat ng qubits, ang ideal expectation value ng ay para sa bawat qubit site anuman ang mga halaga ng . Mas maraming detalye tungkol sa circuit na ito ay makikita sa artikulong ito.
Pansinin na sa isang noiseless simulator, ang expectation value na nakuha na may at walang circuit cutting ay magiging pareho.
Hakbang 1: I-map ang classical inputs sa quantum problem
Buuin ang 1D MBL circuit
Una, nagpapakita tayo ng function para sa pagbuo ng 1D MBL circuit.
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)
Kinakalkula natin ang average na expectation value na sa lahat ng qubits para sa . Dahil ang ideal na expectation value ng , ang ideal na expectation value ng ay din. Ang mga parameter na ay pinili nang random.
np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
Ang circuit ay kailangang markahan sa pamamagitan ng paglalagay ng CutWire sa mga ninanais na lokasyon upang hatiin ito. Para sa tutorial na ito, pumipili tayo ng pantay na partisyon. Ang MBL circuit ay dinisenyo upang ang pagtatakda ng use_cut=True sa function ay wastong naglalagay ng annotation pagkatapos ng qubits, kung saan ang ay ang bilang ng mga qubits sa orihinal na circuit. Itinalaga rin natin ang mga random na nabuong parameter sa circuit.
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)
Hakbang 2: I-optimize ang problema para sa quantum hardware execution
Putulin ang circuit sa mas maliit na subcircuits
Ngayon ay hinahati natin ang circuit sa dalawang mas maliit na subcircuits gamit ang qiskit-addon-cutting. Ang qiskit-addon-cutting ay nagdadagdag ng virtual na Move gate upang hatiin ang lokasyon ng wire cut sa pamamagitan ng wastong pag-aayos ng bilang ng qubits. Ngayon ay lumilikha tayo ng circuit na may virtual na gate na ito. Dahil may isang wire cut, ang bilang ng mga nauugnay na qubits ay dadagdagan ng 1.
mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)
Buuin at palawakin ang observable
Ang observable, tulad ng tinukoy dati, ay magiging average ng sa bawat qubit. Gayunpaman, kapag nailagay na ang virtual na Move gate, ang epektibong bilang ng qubits sa circuit ay tumataas. Ang observable ay kailangang palawakin din nang naaayon upang isaalang-alang ang pagbabagong ito sa bilang ng mga qubits. Pansinin na ang observable ay palaging kumikilos nang trivially (bilang ) sa karagdagang qubit na idinagdag para sa virtual na Move gate.
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'])
Ngayon ay maaari nang hatiin ang circuit kasabay ng Move gate at makukuha natin ang mga subcircuits, pati na rin ang subobservable, na siyang bahagi ng orihinal na observable na nauugnay sa bawat subcircuit.
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
Dito natin biswalhin ang dalawang subcircuits:
subcircuits[0].draw("mpl", fold=-1)
subcircuits[1].draw("mpl", fold=-1)
Ang pagpapalawak ng observable gamit ang Move operation ay nangangailangan ng PauliList data structure. Upang ma-reconstruct ang expectation value ng orihinal na circuit, kailangan natin ang observable sa format na SparsePauliOp.
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
Tulad ng tinatalakay dati, para sa bawat pagputol ang upstream circuit ay kailangang sukatin sa isang Pauli basis, at ang downstream circuit ay kailangang ihanda sa eigenstate ng basis. Ang function na generate_cutting_experiments ay lumilikha ng lahat ng kinakailangang circuits at mga coefficients na nauugnay sa bawat circuit na kinakailangan para sa reconstruction. Hanapin ang mas maraming detalye sa papel na ito.
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
I-transpile ang mga circuit sa backend
Para sa unang halimbawa na nagsasangkot lamang ng simulation, ita-transpile natin ang circuit sa basis gate set ng backend:
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)
<IBMBackend('ibm_fez')>
Hakbang 3: Isagawa gamit ang Qiskit primitives
Ngayon, isagawa ang bawat 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()
}
Hakbang 4: Mag-post-process at ibalik ang resulta sa nais na classical format
Ngayon ay kinukuha natin ang resulta ng bawat subexperiment run at rine-reconstruct ang expectation value ng uncut circuit:
# 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$')
Halimbawa ng malaking sukat gamit ang hardware
Ngayon ay ipinakikita natin ang wire cutting para sa isang 60-qubit MBL circuit. Ang uncut, pati na rin ang mga cut circuit, ay isasagawa sa IBM Quantum® hardware:
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
Mga Susunod na Hakbang
Kung nakita mong kawili-wili ang gawaing ito, maaaring interesado ka sa sumusunod na materyal:
Mga Reperensiya
[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