Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Simulazione esatta con le primitive di Qiskit SDK

Versioni dei pacchetti

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

qiskit[all]~=2.5.2

Le primitive di riferimento in Qiskit SDK eseguono simulazioni locali dello statevector. Queste simulazioni non supportano la modellazione del rumore del dispositivo, ma sono utili per prototipare rapidamente gli algoritmi prima di esaminare tecniche di simulazione più avanzate (usando Qiskit Aer) o l'esecuzione su dispositivi reali (primitive IBM Quantum).

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 — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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 IBM Quantum 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 a cui l'implementazione della primitiva dovrebbe mirare 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 l'Estimator.

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 IBM Quantum 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: {'00': 513, '11': 511}

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 0x1297391d0>

Per un esempio completo, consulta la pagina degli esempi con il Sampler.

Passi successivi​

Raccomandazioni