Skip to main content
IBM Quantum Platform

Drahtschneiden zur Schätzung von Erwartungswerten

Geschätzte Ausführungszeit: 22 Sekunden auf einem Heron-Prozessor (HINWEIS: Dies ist lediglich eine Schätzung. (Die Laufzeit kann variieren.)


Lernziele

  • Wie man nutzt qiskit-addon-cutting , um eine große Schaltung in kleinere Teilschaltungen zu unterteilen und so die Auswirkungen von Rauschen zu verringern

Voraussetzungen


Hintergrund

„Circuit-Knitting“ ist ein Oberbegriff, der verschiedene Methoden umfasst, bei denen eine Schaltung in mehrere kleinere Teilschaltungen mit weniger Logikgattern oder Qubits unterteilt wird. Jede der Teilschaltungen kann unabhängig ausgeführt werden, und das Endergebnis ergibt sich aus einer klassischen Nachbearbeitung der Ergebnisse der einzelnen Teilschaltungen. Diese Technik ist im Qiskit-Add-on „Circuit cutting“ verfügbar; eine ausführliche Erläuterung der Technik finden Sie in der Dokumentation sowie in weiteren Einführungsmaterialien.

Dieses Tutorial befasst sich mit einer Methode namens „Wire Cutting“, bei der die Schaltung entlang der Leiterbahn unterteilt wird [1], [2]. Beachten Sie, dass die Teilung in klassischen Schaltungen einfach ist, da das Ergebnis am Teilungspunkt deterministisch bestimmt werden kann und entweder 0 oder 1 ist. Der Zustand des Qubits zum Zeitpunkt des Schnitts ist jedoch im Allgemeinen ein gemischter Zustand. Daher muss jeder Teilkreis mehrfach in verschiedenen Basiszuständen (in der Regel in einer tomographisch vollständigen Basis, wie beispielsweise der Pauli-Basis [3], [4] ) gemessen und entsprechend in seinem Eigenzustand vorbereitet werden. Die folgende Abbildung (Quelle: [7] ) zeigt ein Beispiel für die Aufteilung eines GHZ-Zustands mit vier Qubits in drei Teilschaltungen. Hier bezeichnet MjM_j eine Menge von Basisvektoren (in der Regel Pauli X, Y und Z), und PiP_i bezeichnet eine Menge von Eigenzuständen (in der Regel ∣0⟩|0\rangle, ∣1⟩|1\rangle, ∣+⟩|+\rangle und ∣+i⟩|+i\rangle ).

wc-1.png wc-2.png

Da jede Teilschaltung über weniger Qubits und Gatter verfügt, ist davon auszugehen, dass sie weniger anfällig für Rauschen ist. Dieses Tutorial zeigt ein Beispiel, wie diese Methode eingesetzt werden kann, um das Rauschen im System wirksam zu unterdrücken.


Anforderungen

Bevor Sie mit diesem Tutorial beginnen, sollten Sie sicherstellen, dass Sie Folgendes installiert haben:

  • Qiskit SDK v2.0 oder später, mit Visual isierungsunterstützung
  • Qiskit Runtime v0.22 oder später ( pip install qiskit-ibm-runtime )
  • Schaltkreis-Erweiterung für Qiskit v0.10.0 oder höher (pip install qiskit-addon-cutting)
  • Qiskit-Add-on „utils“ 0.3 oder höher (pip install qiskit-addon-utils)
  • Qiskit Aer (pip install qiskit-aer )

Einrichtung

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

Beispiel für einen Simulator im kleinen Maßstab

In diesem Tutorial wird ein Qiskit-Muster zur Simulation einer eindimensionalen ( 1D en) Many-Body-Localization-Schaltung (MBL) implementiert. Der MBL-Schaltkreis ist hardwareeffizient und wird durch die beiden Parameter θ\theta und ϕ⃗\vec{\phi} parametrisiert. Wenn θ\theta auf 00 gesetzt und der Anfangszustand für alle Qubits auf ∣0⟩|0\rangle vorbereitet wird, beträgt der ideale Erwartungswert von ⟨Zi⟩\langle Z_i \rangle für jedes Qubit ii +1+1 } unabhängig von den Werten von ϕ⃗\vec{\phi}. Weitere Details zu diesem Schaltkreis finden Sie in diesem Artikel.

Beachten Sie, dass in einem rauschfreien Simulator der Erwartungswert mit und ohne Schaltungsunterbrechung identisch ist.

Schritt 1: Klassische Eingaben einem Quantenproblem zuordnen

Bauen Sie die MBL-Schaltung „ 1D “ auf

Zunächst stellen wir eine Funktion zur Erstellung der MBL-Schaltung nach „ 1D “ vor.

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)

Output:

Output of the previous code cell

Wir berechnen den durchschnittlichen Erwartungswert O=1n∑iZiO = \frac{1}{n} \sum_i Z_i über alle Qubits für θ=0\theta = 0. Da der ideale Erwartungswert von ⟨Zi⟩=1\langle Z_i \rangle = 1 ∀\forall ii ist, beträgt der ideale Erwartungswert von OO ebenfalls 11. Die Parameter ϕ\phi werden zufällig ausgewählt.

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

Um die Schaltung zu unterteilen, muss sie durch Einfügen von „ CutWire “ an den gewünschten Stellen kommentiert werden. Für dieses Tutorial entscheiden wir uns für eine gleichmäßige Aufteilung. Der MBL-Schaltkreis ist so ausgelegt, dass die Einstellung use_cut=True in der Funktion die Anmerkung korrekt nach n2\frac{n}{2} Qubits einfügt, wobei nn die Anzahl der Qubits im ursprünglichen Schaltkreis ist. Außerdem haben wir die zufällig generierten Parameter der Schaltung zugewiesen.

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

Output:

Output of the previous code cell

Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware

Schneiden Sie den Schaltkreis in kleinere Teilkreise

qiskit-addon-cuttingNun unterteilen wir den Schaltkreis mithilfe von in zwei kleinere Teilschaltkreise. qiskit-addon-cutting fügt ein virtuelles Move Gatter hinzu, um die Stelle des Drahtschnitts durch entsprechende Anpassung der Anzahl der Qubits zu teilen. Nun erstellen wir die Schaltung mit diesem virtuellen Gatter. Da ein Draht durchtrennt ist, erhöht sich die Anzahl der zugehörigen Qubits um 1.

mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)

Output:

Output of the previous code cell

Den Observablen konstruieren und erweitern

Die zuvor definierte Observable ist der Mittelwert von „ ZZ “ für jedes Qubit. Durch das Einfügen des virtuellen Move Gatters erhöht sich jedoch die effektive Anzahl der Qubits in der Schaltung. Der Observabel muss ebenfalls entsprechend erweitert werden, um dieser Änderung der Anzahl der Qubits Rechnung zu tragen. Beachte, dass die Beobachtungsgröße auf das für das virtuelle Move Gatter hinzugefügte zusätzliche Qubit stets trivial wirkt (wie in II ).

observable = PauliList(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable

Output:

PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
           'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
           'IIIIIIIIZI', 'IIIIIIIIIZ'])
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs

Output:

PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
           'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
           'IIIIIIIIIZI', 'IIIIIIIIIIZ'])

Nun kann die Schaltung entlang des Move Gates unterteilt werden, und wir erhalten die Teilschaltungen sowie die Teilobservable, also den Teil der ursprünglichen Observable, der jeder Teilschaltung zugeordnet ist.

partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

Hier stellen wir die beiden Teilschaltungen dar:

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

Output:

Output of the previous code cell
subcircuits[1].draw("mpl", fold=-1)

Output:

Output of the previous code cell

Um das Beobachtbare mithilfe der Move Operation zu erweitern, ist eine PauliList Datenstruktur erforderlich. Um den Erwartungswert der ursprünglichen Schaltung zu rekonstruieren, benötigen wir die Observable im SparsePauliOp Format.

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

Wie bereits erwähnt, muss für jeden Schnitt der vorgelagerte Kreis in einer Pauli-Basis gemessen werden, und der nachgelagerte Kreis muss im Eigenzustand dieser Basis vorbereitet werden. Die Funktion generate_cutting_experiments erstellt alle erforderlichen Schaltungen sowie die zu jeder Schaltung gehörenden Koeffizienten, die für die Rekonstruktion benötigt werden. Weitere Einzelheiten finden Sie in diesem Artikel.

subexperiments, coefficients = generate_cutting_experiments(
    circuits=subcircuits,
    observables=subobservables,
    num_samples=np.inf,
)

Die Schaltkreise auf das Backend transpilieren

Für das erste Beispiel, bei dem es ausschließlich um die Simulation geht, transpilieren wir die Schaltung in den Basis-Gatesatz des Backends:

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

print(backend)

Output:

<IBMBackend('ibm_fez')>

Schritt 3: Mit „ Qiskit primitives “ ausführen

Führen Sie nun jedes Teilversuch durch:

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

Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format

Nun holen wir das Ergebnis jedes einzelnen Durchlaufs der Teilversuche ab und berechnen den Erwartungswert der ungeschnittenen Schaltung:

# 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

Output:

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)

Output:

Text(0, 0.5, '$M_Z$')
Output of the previous code cell

Beispiel für groß angelegte Hardware

Nun demonstrieren wir das Drahtschneiden für eine MBL-Schaltung mit 60 Qubits. Sowohl die ungeschnittenen als auch die geschnittenen Schaltkreise werden auf „ IBM Quantum® “-Hardware ausgeführt:

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

Output:

Output of the previous code cell
uncut_expval

Output:

0.9202473958333336

Nächste Schritte

Empfehlungen

Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an folgenden Materialien interessiert sein:


Referenzen

[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulation großer Quantenschaltungen auf einem kleinen Quantencomputer. Physical Review Letters, 125(15), 150504.

[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: Verwendung kleiner Quantencomputer für die Bewertung großer Quantenschaltungen. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (S. 473-486).

[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum Circuit Cutting mit Maximum-Likelihood-Tomographie. npj Quantum Information, 7(1), 64.

[4] Majumdar, R., & Wood, C. J. (2022). Fehlervermindertes Schneiden von Quantenschaltungen. arXiv vorabdruck arXiv:2211.13431.

[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelisierung quantenklassischer Workloads: Profiling der Auswirkungen von Splitting-Techniken. 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). Verteiltes Scheduling von Quantenschaltungen mit Rauschen und Zeitoptimierung. arXiv vorabdruck arXiv:2309.06005.

[7] Majumdar, R. (2024). Effiziente Reduzierung von Ressourcen und Rauschen in Schaltkreisen für diskrete Quantencomputer (Doktorarbeit, Indian Statistical Institute – Kalkutta). https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar&cbl=2026366&diss=y

War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.