Vai al contenuto principale

Simulazione esatta con le primitive di Qiskit SDK

Versioni dei pacchetti

Il codice in questa pagina è stato sviluppato con i seguenti requisiti. Consigliamo di usare queste versioni o versioni più recenti.

qiskit[all]~=2.3.0

Le primitive di riferimento nel Qiskit SDK eseguono simulazioni locali dello statevector. Queste simulazioni non supportano la modellazione del rumore del dispositivo, ma sono utili per prototipare rapidamente algoritmi prima di esplorare tecniche di simulazione più avanzate (usando Qiskit Aer) o di eseguire su dispositivi reali (primitive di Qiskit Runtime).

La primitiva Estimator può calcolare i valori attesi dei circuiti, mentre la primitiva Sampler può campionare dalle distribuzioni di output dei circuiti.

Le sezioni seguenti mostrano come usare le primitive di riferimento per eseguire il tuo workflow in locale.

Usare l'Estimator di riferimento

L'implementazione di riferimento di EstimatorV2 in qiskit.primitives che gira su simulatori locali dello statevector è la classe StatevectorEstimator. Può ricevere circuiti, osservabili e parametri come input e restituisce i valori attesi calcolati localmente.

Il codice seguente prepara gli input che verranno usati negli esempi successivi. Il tipo di input atteso per gli osservabili è qiskit.quantum_info.SparsePauliOp. Nota che il circuito nell'esempio è parametrizzato, ma puoi eseguire l'Estimator anche su circuiti non parametrizzati.

nota

Qualsiasi circuito passato a un Estimator non deve includere misurazioni.

# 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 della cella di codice precedente

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]]
Transpila in circuiti e osservabili ISA

Il workflow delle primitive di Qiskit Runtime richiede che circuiti e osservabili vengano trasformati in modo da usare solo le istruzioni supportate dalla QPU (chiamati circuiti e osservabili con instruction set architecture (ISA)). Le primitive di riferimento accettano ancora istruzioni astratte, poiché si basano su simulazioni locali dello statevector, ma eseguire il transpiling del circuito può comunque essere vantaggioso in termini di ottimizzazione.

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

Inizializzare l'Estimator

Istanzia un qiskit.primitives.StatevectorEstimator.

from qiskit.primitives import StatevectorEstimator

estimator = StatevectorEstimator()

Eseguire e ottenere i risultati

Questo esempio usa un solo circuito (di tipo QuantumCircuit) e un solo osservabile.

Esegui la stima chiamando il metodo StatevectorEstimator.run, che restituisce un'istanza di un oggetto PrimitiveJob. Puoi ottenere i risultati dal job (come oggetto qiskit.primitives.PrimitiveResult) con il metodo qiskit.primitives.PrimitiveJob.result.

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

Ottenere il valore atteso dal risultato

Il risultato delle primitive restituisce un array di oggetti PubResult, dove ogni elemento dell'array è un oggetto PubResult che contiene nei suoi dati l'array di valutazioni corrispondente a ogni combinazione circuito-osservabile nel PUB.

Per recuperare i valori attesi e i metadati per la prima (e in questo caso unica) valutazione del circuito, dobbiamo accedere ai data della valutazione per il PUB 0:

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': {}}

Impostare le opzioni di esecuzione dell'Estimator

Per impostazione predefinita, l'Estimator di riferimento esegue un calcolo esatto dello statevector basato sulla classe quantum_info.Statevector. Tuttavia, questo può essere modificato per introdurre l'effetto dell'overhead di campionamento (noto anche come "shot noise").

L'Estimator accetta un argomento precision che esprime le barre di errore che l'implementazione della primitiva dovrebbe puntare a ottenere per le stime dei valori attesi. Questo rappresenta l'overhead di campionamento ed è definito esclusivamente nel metodo .run(). Ciò ti consente di mettere a punto l'opzione fino al livello del PUB.

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

Per un esempio completo, consulta la pagina degli esempi con le primitive.

Usare il Sampler di riferimento

L'implementazione di riferimento di SamplerV2 in qiskit.primitives è la classe StatevectorSampler. Riceve circuiti e parametri come input e restituisce i risultati del campionamento dalle distribuzioni di probabilità di output come distribuzione di quasi-probabilità degli stati di output.

Il codice seguente prepara gli input usati negli esempi successivi. Nota che questi esempi eseguono un singolo circuito parametrizzato, ma puoi eseguire il Sampler anche su circuiti non parametrizzati.

from qiskit import QuantumCircuit

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

Output della cella di codice precedente

nota

Qualsiasi circuito quantistico passato a un Sampler deve includere misurazioni.

Transpila in circuiti e osservabili ISA

Il workflow delle primitive di Qiskit Runtime richiede che i circuiti vengano trasformati in modo da usare solo le istruzioni supportate dalla QPU (chiamati circuiti ISA). Le primitive di riferimento accettano ancora istruzioni astratte, poiché si basano su simulazioni locali dello statevector, ma eseguire il transpiling del circuito può comunque essere vantaggioso in termini di ottimizzazione.

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

Inizializzare SamplerV2

Istanzia qiskit.primitives.StatevectorSampler:

from qiskit.primitives import StatevectorSampler

sampler = StatevectorSampler()

Eseguire e ottenere i risultati

# 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'>

Le primitive accettano più PUB come input e ogni PUB ottiene il proprio risultato. Puoi quindi eseguire circuiti diversi con varie combinazioni di parametri/osservabili e recuperare i risultati dei PUB:

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

Ottenere la distribuzione di probabilità o l'esito della misurazione

I campioni degli esiti delle misurazioni vengono restituiti come bitstring o conteggi. Le bitstring mostrano gli esiti delle misurazioni, preservando l'ordine degli shot in cui sono stati misurati. Gli oggetti risultato del Sampler organizzano i dati in base ai nomi dei registri classici dei circuiti di input, per la compatibilità con i circuiti dinamici.

nota

Il nome del registro classico è "meas" per impostazione predefinita. Questo nome verrà usato in seguito per accedere alle bitstring delle misurazioni.

# 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}

Modificare le opzioni di esecuzione

Per impostazione predefinita, il Sampler di riferimento esegue un calcolo esatto dello statevector basato sulla classe quantum_info.Statevector. Tuttavia, questo può essere modificato per introdurre l'effetto dell'overhead di campionamento (noto anche come "shot noise"). Per gestire questo overhead, l'interfaccia del Sampler accetta un argomento shots che può essere definito a livello di PUB.

Questo esempio presuppone che tu abbia definito due circuiti.

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

Per un esempio completo, consulta la pagina degli esempi con le primitive.

Passi successivi

Raccomandazioni