Zum Hauptinhalt springen

Exakte Simulation mit Qiskit SDK Primitives

Paketversionen

Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen, diese Versionen oder neuere zu verwenden.

qiskit[all]~=2.3.0

Die Referenz-Primitives im Qiskit SDK führen lokale Statevector-Simulationen durch. Diese Simulationen unterstützen keine Modellierung von Geräterauschen, eignen sich aber gut zum schnellen Prototypisieren von Algorithmen, bevor du fortgeschrittenere Simulationstechniken (mit Qiskit Aer) oder echte Geräte (Qiskit Runtime Primitives) nutzt.

Das Estimator Primitive kann Erwartungswerte von Circuits berechnen, und das Sampler Primitive kann aus den Ausgabeverteilungen von Circuits sampeln.

Die folgenden Abschnitte zeigen, wie du die Referenz-Primitives verwendest, um deinen Workflow lokal auszuführen.

Den Referenz-Estimator verwenden

Die Referenzimplementierung von EstimatorV2 in qiskit.primitives, die auf einem lokalen Statevector-Simulator läuft, ist die Klasse StatevectorEstimator. Sie akzeptiert Circuits, Observablen und Parameter als Eingaben und gibt die lokal berechneten Erwartungswerte zurück.

Der folgende Code bereitet die Eingaben vor, die in den nachfolgenden Beispielen verwendet werden. Der erwartete Eingabetyp für die Observablen ist qiskit.quantum_info.SparsePauliOp. Beachte, dass der Circuit im Beispiel parametrisiert ist, du aber den Estimator auch auf nicht-parametrisierte Circuits anwenden kannst.

hinweis

Jeder Circuit, der an einen Estimator übergeben wird, darf keine Messungen enthalten.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# circuit for which you want to obtain the expected value
circuit = QuantumCircuit(2)
circuit.ry(Parameter("theta"), 0)
circuit.h(0)
circuit.cx(0, 1)
circuit.draw("mpl", style="iqp")

Output of the previous code cell

from qiskit.quantum_info import SparsePauliOp
import numpy as np

# observable(s) whose expected values you want to compute

observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1])

# value(s) for the circuit parameter(s)
parameter_values = [[0], [np.pi / 6], [np.pi / 2]]
Transpile zu ISA-Circuits und Observablen

Der Qiskit Runtime Primitives-Workflow erfordert, dass Circuits und Observablen so transformiert werden, dass sie nur Anweisungen verwenden, die vom QPU unterstützt werden (auch als Instruction Set Architecture (ISA)-Circuits und Observablen bezeichnet). Die Referenz-Primitives akzeptieren weiterhin abstrakte Anweisungen, da sie auf lokalen Statevector-Simulationen basieren. Das Transpilieren des Circuits kann jedoch in Bezug auf die Circuit-Optimierung dennoch vorteilhaft sein.

# Generate a pass manager without providing a backend
from qiskit.transpiler import generate_preset_pass_manager

pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)

Estimator initialisieren

Instanziiere einen qiskit.primitives.StatevectorEstimator.

from qiskit.primitives import StatevectorEstimator

estimator = StatevectorEstimator()

Ausführen und Ergebnisse abrufen

Dieses Beispiel verwendet nur einen Circuit (vom Typ QuantumCircuit) und eine Observable.

Führe die Schätzung aus, indem du die Methode StatevectorEstimator.run aufrufst, die eine Instanz eines PrimitiveJob-Objekts zurückgibt. Du kannst die Ergebnisse aus dem Job (als qiskit.primitives.PrimitiveResult-Objekt) mit der Methode qiskit.primitives.PrimitiveJob.result abrufen.

job = estimator.run([(circuit, observable, parameter_values)])
result = job.result()
print(f" > Result class: {type(result)}")
> Result class: <class 'qiskit.primitives.containers.primitive_result.PrimitiveResult'>

Den Erwartungswert aus dem Ergebnis auslesen

Die Ausgabe der Primitives enthält ein Array von PubResult-Objekten, wobei jedes Element des Arrays ein PubResult-Objekt ist, das in seinen Daten das Array der Auswertungen für jede Circuit-Observable-Kombination im PUB enthält.

Um die Erwartungswerte und Metadaten für die erste (und in diesem Fall einzige) Circuit-Auswertung abzurufen, musst du auf die Auswertungs-data für PUB 0 zugreifen:

print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
> Expectation value: [4.         3.73205081 2.        ]
> Metadata: {'target_precision': 0.0, 'circuit_metadata': {}}

Estimator-Ausführungsoptionen festlegen

Standardmäßig führt der Referenz-Estimator eine exakte Statevector-Berechnung basierend auf der Klasse quantum_info.Statevector durch. Dies kann jedoch angepasst werden, um den Effekt des Sampling-Overheads (auch bekannt als „Shot-Rauschen") einzubeziehen.

Der Estimator akzeptiert ein precision-Argument, das die Fehlerbalken angibt, auf die die Primitive-Implementierung bei Erwartungswert-Schätzungen abzielen soll. Dies ist der Sampling-Overhead und wird ausschließlich in der .run()-Methode definiert. Damit kannst du die Option bis auf PUB-Ebene fein abstimmen.

# Estimate expectation values for two PUBs, both with 0.05 precision.
precise_job = estimator.run(
[(circuit, observable, parameter_values)], precision=0.05
)

Ein vollständiges Beispiel findest du auf der Seite Primitives-Beispiele.

Den Referenz-Sampler verwenden

Die Referenzimplementierung von SamplerV2 in qiskit.primitives ist die Klasse StatevectorSampler. Sie akzeptiert Circuits und Parameter als Eingaben und gibt die Ergebnisse des Sampelns aus den Ausgabe-Wahrscheinlichkeitsverteilungen als Quasi-Wahrscheinlichkeitsverteilung der Ausgabezustände zurück.

Der folgende Code bereitet die Eingaben vor, die in den nachfolgenden Beispielen verwendet werden. Beachte, dass diese Beispiele einen einzelnen parametrisierten Circuit ausführen, du den Sampler aber auch auf nicht-parametrisierte Circuits anwenden kannst.

from qiskit import QuantumCircuit

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw("mpl", style="iqp")

Output of the previous code cell

hinweis

Jeder Quantenschaltkreis, der an einen Sampler übergeben wird, muss Messungen enthalten.

Transpile zu ISA-Circuits und Observablen

Der Qiskit Runtime Primitives-Workflow erfordert, dass Circuits so transformiert werden, dass sie nur Anweisungen verwenden, die vom QPU unterstützt werden (auch als ISA-Circuits bezeichnet). Die Referenz-Primitives akzeptieren weiterhin abstrakte Anweisungen, da sie auf lokalen Statevector-Simulationen basieren. Das Transpilieren des Circuits kann jedoch in Bezug auf die Circuit-Optimierung dennoch vorteilhaft sein.

# Generate a pass manager without providing a backend
from qiskit.transpiler import generate_preset_pass_manager

pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(qc)

SamplerV2 initialisieren

Instanziiere qiskit.primitives.StatevectorSampler:

from qiskit.primitives import StatevectorSampler

sampler = StatevectorSampler()

Ausführen und Ergebnisse abrufen

# execute 1 circuit with Sampler
job = sampler.run([circuit])
pub_result = job.result()[0]
print(f" > Result class: {type(pub_result)}")
> Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>

Primitives akzeptieren mehrere PUBs als Eingaben, und jeder PUB erhält sein eigenes Ergebnis. Daher kannst du verschiedene Circuits mit unterschiedlichen Parameter-/Observable-Kombinationen ausführen und die PUB-Ergebnisse abrufen:

from qiskit.transpiler import generate_preset_pass_manager

# create two circuits
circuit1 = circuit.copy()
circuit2 = circuit.copy()

# transpile circuits
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit1 = pm.run(circuit1)
isa_circuit2 = pm.run(circuit2)
# execute 2 circuits using Sampler
job = sampler.run([(isa_circuit1), (isa_circuit2)])
pub_result_1 = job.result()[0]
pub_result_2 = job.result()[1]
print(f" > Result class: {type(pub_result)}")
> Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>

Wahrscheinlichkeitsverteilung oder Messergebnis abrufen

Messergebnisse werden als Bitstrings oder Counts zurückgegeben. Die Bitstrings zeigen die Messergebnisse und bewahren dabei die Reihenfolge der Shots, in der sie gemessen wurden. Die Sampler-Ergebnisobjekte organisieren Daten anhand der Namen der klassischen Register ihrer Eingabe-Circuits, um die Kompatibilität mit dynamischen Circuits sicherzustellen.

hinweis

Der Name des klassischen Registers ist standardmäßig "meas". Dieser Name wird später verwendet, um auf die Mess-Bitstrings zuzugreifen.

# Define quantum circuit with 2 qubits
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()
┌───┐      ░ ┌─┐
q_0: ┤ H ├──■───░─┤M├───
└───┘┌─┴─┐ ░ └╥┘┌─┐
q_1: ─────┤ X ├─░──╫─┤M├
└───┘ ░ ║ └╥┘
meas: 2/══════════════╩══╩═
0 1
# Transpile circuit
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(circuit)
# Run using sampler
result = sampler.run([circuit]).result()
# Access result data for PUB 0
data_pub = result[0].data
# Access bitstring for the classical register "meas"
bitstrings = data_pub.meas.get_bitstrings()
print(f"The number of bitstrings is: {len(bitstrings)}")
# Get counts for the classical register "meas"
counts = data_pub.meas.get_counts()
print(f"The counts are: {counts}")
The number of bitstrings is: 1024
The counts are: {'11': 515, '00': 509}

Ausführungsoptionen ändern

Standardmäßig führt der Referenz-Sampler eine exakte Statevector-Berechnung basierend auf der Klasse quantum_info.Statevector durch. Dies kann jedoch angepasst werden, um den Effekt des Sampling-Overheads (auch bekannt als „Shot-Rauschen") einzubeziehen. Um diesen Overhead zu steuern, akzeptiert das Sampler-Interface ein shots-Argument, das auf PUB-Ebene definiert werden kann.

Dieses Beispiel setzt voraus, dass du zwei Circuits definiert hast.

# Sample two circuits at 128 shots each.
sampler.run([isa_circuit1, isa_circuit2], shots=128)
# Sample two circuits at different amounts of shots. The "None"s are necessary
# as placeholders
# for the lack of parameter values in this example.
sampler.run([(isa_circuit1, None, 123), (isa_circuit2, None, 456)])
<qiskit.primitives.primitive_job.PrimitiveJob at 0x7fa430e39dd0>

Ein vollständiges Beispiel findest du auf der Seite Primitives-Beispiele.

Nächste Schritte

Empfehlungen