Przejdź do głównej treści

Dokładna symulacja przy użyciu prymitywów Qiskit SDK

Wersje pakietów

Kod na tej stronie został opracowany przy użyciu poniższych wymagań. Zalecamy korzystanie z tych wersji lub nowszych.

qiskit[all]~=2.3.0

Referencyjne prymitywy w Qiskit SDK wykonują lokalne symulacje wektora stanu. Symulacje te nie obsługują modelowania szumów urządzenia, ale są przydatne do szybkiego prototypowania algorytmów przed skorzystaniem z bardziej zaawansowanych technik symulacji (przy użyciu Qiskit Aer) lub uruchamianiem na prawdziwych urządzeniach (prymitywy Qiskit Runtime).

Prymityw Estimator może obliczać wartości oczekiwane obwodów, a prymityw Sampler może próbkować z rozkładów wyjściowych obwodów.

Poniższe sekcje pokazują, jak korzystać z referencyjnych prymitywów, aby uruchamiać swój przepływ pracy lokalnie.

Użycie referencyjnego Estimatora

Referencyjna implementacja EstimatorV2 w qiskit.primitives, działająca na lokalnym symulatorze wektora stanu, to klasa StatevectorEstimator. Przyjmuje ona obwody, obserwable i parametry jako dane wejściowe, a zwraca lokalnie obliczone wartości oczekiwane.

Poniższy kod przygotowuje dane wejściowe, które zostaną użyte w kolejnych przykładach. Oczekiwanym typem wejściowym dla obserwabli jest qiskit.quantum_info.SparsePauliOp. Zwróć uwagę, że obwód w przykładzie jest sparametryzowany, ale możesz też uruchomić Estimator na nieparametryzowanych obwodach.

uwaga

Każdy obwód przekazywany do Estimatora nie może zawierać żadnych pomiarów.

# 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]]
Transpilacja do obwodów i obserwabli ISA

Przepływ pracy prymitywów Qiskit Runtime wymaga, aby obwody i obserwable były przekształcone tak, by korzystały wyłącznie z instrukcji obsługiwanych przez QPU (określanych jako obwody i obserwable architektury zestawu instrukcji (ISA)). Referencyjne prymitywy nadal przyjmują abstrakcyjne instrukcje, ponieważ opierają się na lokalnych symulacjach wektora stanu, ale transpilacja obwodu może być nadal korzystna pod względem optymalizacji.

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

Inicjalizacja Estimatora

Utwórz instancję qiskit.primitives.StatevectorEstimator.

from qiskit.primitives import StatevectorEstimator

estimator = StatevectorEstimator()

Uruchomienie i uzyskanie wyników

Ten przykład wykorzystuje tylko jeden obwód (typu QuantumCircuit) i jedną obserwablę.

Uruchom estymację, wywołując metodę StatevectorEstimator.run, która zwraca instancję obiektu PrimitiveJob. Możesz uzyskać wyniki z zadania (jako obiekt qiskit.primitives.PrimitiveResult) przy użyciu metody 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'>

Pobieranie wartości oczekiwanej z wyniku

Wynik prymitywów zwraca tablicę obiektów PubResult, gdzie każdy element tablicy to obiekt PubResult zawierający w swoich danych tablicę ewaluacji odpowiadającą każdej kombinacji obwód-obserwabla w PUB.

Aby pobrać wartości oczekiwane i metadane dla pierwszej (i w tym przypadku jedynej) ewaluacji obwodu, należy uzyskać dostęp do data ewaluacji dla 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': {}}

Ustawianie opcji uruchomienia Estimatora

Domyślnie referencyjny Estimator wykonuje dokładne obliczenia wektora stanu oparte na klasie quantum_info.Statevector. Można to jednak zmodyfikować, aby uwzględnić efekt narzutu próbkowania (znany też jako „szum strzałowy").

Estimator przyjmuje argument precision, który określa słupki błędu, na jakie implementacja prymitywu powinna celować przy szacowaniu wartości oczekiwanych. Jest to narzut próbkowania definiowany wyłącznie w metodzie .run(). Pozwala to precyzyjnie dostosowywać tę opcję aż do poziomu PUB.

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

Pełny przykład znajdziesz na stronie Przykłady z prymitywami.

Użycie referencyjnego Samplera

Referencyjna implementacja SamplerV2 w qiskit.primitives to klasa StatevectorSampler. Przyjmuje ona obwody i parametry jako dane wejściowe, a zwraca wyniki próbkowania z wyjściowych rozkładów prawdopodobieństwa jako quasi-rozkład prawdopodobieństwa stanów wyjściowych.

Poniższy kod przygotowuje dane wejściowe używane w kolejnych przykładach. Zwróć uwagę, że te przykłady uruchamiają pojedynczy sparametryzowany obwód, ale możesz też uruchomić Sampler na nieparametryzowanych obwodach.

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

uwaga

Każdy obwód kwantowy przekazywany do Samplera musi zawierać pomiary.

Transpilacja do obwodów i obserwabli ISA

Przepływ pracy prymitywów Qiskit Runtime wymaga, aby obwody były przekształcone tak, by korzystały wyłącznie z instrukcji obsługiwanych przez QPU (określanych jako obwody ISA). Referencyjne prymitywy nadal przyjmują abstrakcyjne instrukcje, ponieważ opierają się na lokalnych symulacjach wektora stanu, ale transpilacja obwodu może być nadal korzystna pod względem optymalizacji.

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

Inicjalizacja SamplerV2

Utwórz instancję qiskit.primitives.StatevectorSampler:

from qiskit.primitives import StatevectorSampler

sampler = StatevectorSampler()

Uruchomienie i uzyskanie wyników

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

Prymitywy przyjmują wiele PUB-ów jako dane wejściowe, a każdy PUB otrzymuje własny wynik. Możesz więc uruchamiać różne obwody z różnymi kombinacjami parametrów/obserwabli i pobierać wyniki 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'>

Pobieranie rozkładu prawdopodobieństwa lub wyniku pomiaru

Próbki wyników pomiaru są zwracane jako łańcuchy bitów lub zliczenia. Łańcuchy bitów pokazują wyniki pomiarów, zachowując kolejność strzałów, w jakiej zostały zmierzone. Obiekty wynikowe Samplera organizują dane według nazw klasycznych rejestrów obwodów wejściowych, dla zachowania zgodności z dynamicznymi obwodami.

uwaga

Domyślna nazwa klasycznego rejestru to "meas". Ta nazwa będzie używana później do uzyskania dostępu do łańcuchów bitów pomiaru.

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

Zmiana opcji uruchomienia

Domyślnie referencyjny Sampler wykonuje dokładne obliczenia wektora stanu oparte na klasie quantum_info.Statevector. Można to jednak zmodyfikować, aby uwzględnić efekt narzutu próbkowania (znany też jako „szum strzałowy"). Aby ułatwić zarządzanie tym narzutem, interfejs Samplera przyjmuje argument shots, który może być zdefiniowany na poziomie PUB.

Ten przykład zakłada, że zdefiniowałeś dwa obwody.

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

Pełny przykład znajdziesz na stronie Przykłady z prymitywami.

Kolejne kroki

Zalecenia