Przejdź do głównej treści

Przykłady Executora

Wersje pakietów

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

qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
samplomatic~=0.18.0
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime samplomatic

Przykłady w tej sekcji ilustrują niektóre typowe sposoby użycia prymitywu Executor. Przed uruchomieniem tych przykładów postępuj zgodnie z instrukcjami w Instalacja Qiskit i Szybki start Executor.

Przed rozpoczęciem

Niektóre z przykładów kodu na tej stronie używają samplex, który jest częścią pakietu Samplomatic. Dlatego przed uruchomieniem tych bloków kodu musisz zainstalować Samplomatic, jak pokazano w poniższym bloku kodu. Więcej informacji znajdziesz w dokumentacji Samplomatic.

pip install samplomatic

# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]

Przykład: Obwód sparametryzowany

Ten przykład ilustruje, jak dodawać elementy obwodu z parametrami, a także jak dodawać elementy samplex. Składa się z następujących kroków:

  1. Konfiguracja obwodu: Generuj i transpiluj docelowy obwód.
  2. Przygotowanie samplexa: Grupuj bramki i pomiary w opisane pola i generuj parę szablon obwodu — samplex.
  3. Wykonanie: Dodaj element obwodu i element samplex do QuantumProgram i wykonaj oba w jednym zadaniu.

Konfiguracja obwodu

Przygotuj trzy-qubitowy stan GHZ, obróć qubity wokół osi Pauliego-Z i zmierz qubity w bazie obliczeniowej.

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# Generate the circuit
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)
circuit.rz(Parameter("lam"), 2)
circuit.measure_all()

Określ backend i transpiluj obwód, aby używał tylko instrukcji obsługiwanych przez QPU (nazywanych obwodem architektury zestawu instrukcji (ISA)).

# Initialize the service and choose a backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Transpile the circuit to ISA
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=3
)
isa_circuit = preset_pass_manager.run(circuit)

Przygotowanie samplexa

Użyj funkcji pomocniczej generate_boxing_pass_manager i jej parametrów twirling, aby grupować bramki dwu-qubitowe i pomiary w pola i stosować adnotacje twirling.

boxing_pm = generate_boxing_pass_manager(
# Add gate twirling
enable_gates=True,
# Add measurement twirling
enable_measures=True,
)

boxed_circuit = boxing_pm.run(isa_circuit)

Użyj metody build, aby wygenerować szablon obwodu i samplex.

# Build the template circuit and the samplex
template_circuit, samplex = build(boxed_circuit)

Wykonanie obwodów

Executor uruchamia obiekty QuantumProgram. Każdy QuantumProgram może zawierać kilka elementów. Ten przykład dodaje element obwodu i element samplex do wykonania. Pełne szczegóły znajdziesz w Wejście i wyjście Executora.

Pierwszym krokiem jest zainicjowanie pustego programu, żądając 1024 shotów dla każdej konfiguracji każdego elementu.

# Generate a quantum program
program = QuantumProgram(shots=1024)

Dołącz element obwodu do QuantumProgram. Ten element obwodu składa się z dwóch części — obwodu ISA i 10 zestawów jego wartości parametrów.

# Append the circuit and the parameter values to the program
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, 3), # 10 sets of parameter values
)

Dołącz element samplex do QuantumProgram z następującymi argumentami:

  • Szablon obwodu i samplex wygenerowane przez funkcję build
  • Dziesięć zestawów wartości parametrów dla oryginalnego obwodu
  • Liczba randomizacji do wykonania
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(
10, 3
), # 10 sets of parameter values
},
shape=(2, 14, 10),
)

Uruchamianie zadania Executor

# initialize an Executor with default options
executor = Executor(mode=backend)

# Submit the job
job = executor.run(program)

# Retrieve the result
result = job.result()

Pobierz wynik dla każdego zadania.

# Access the results of the classical register of task #0, the CircuitItem
result_0 = result[0]["meas"]

# Access the results of the classical register of task #1, the SamplexItem
result_1 = result[1]["meas"]

Przykład: Wykonywanie PEC

Ten przykład pokazuje, jak używać elementu samplex do wykonywania probabilistycznego anulowania błędów (PEC) do mitygacji błędów.

Rozważ lustrzaną wersję obwodu z dziesięcioma qubitami i dwiema unikalnymi warstwami bramek CX. Oto główne zadania:

Potok składa się z następujących kroków:

  1. Konfiguracja: Generuj docelowy obwód i grupuj jego operacje w pola.
  2. Uczenie: Naucz się szumu instrukcji, które chcesz mitygować za pomocą PEC.
  3. Wykonanie: Uruchom obwód na backendzie.
  4. Analiza: przetwarzanie końcowe i analiza wyników.

Dla porównania uruchomimy ten lustrzany obwód dwukrotnie. Raz z zastosowanym tylko twirlingiem Pauliego, i raz z zastosowaną mitygacją PEC.

uwaga

Użycie dla tego przykładu wynosi około 10 minut na procesorze Heron r2.

Konfiguracja obwodu

Wybierz backend i przygotuj 10-qubitowy obwód.

from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.transpiler import generate_preset_pass_manager
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic import build

# Initialize the service and choose a backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Prepare a circuit

num_qubits = 10
num_layers = 10

qubits = list(range(num_qubits))
circuit = QuantumCircuit(num_qubits)

for layer_idx in range(num_layers):
circuit.rx(Parameter(f"theta_{layer_idx}"), qubits)
for i in range(num_qubits // 2):
circuit.cz(qubits[2 * i], qubits[2 * i + 1])

circuit.rx(Parameter(f"phi_{layer_idx}"), qubits)
for i in range(num_qubits // 2 - 1):
circuit.cz(qubits[2 * i] + 1, qubits[2 * i + 1] + 1)

circuit.draw("mpl", scale=0.35, fold=100)

Output of the previous code cell

Połącz obwód z jego odwrotnością, aby stworzyć lustrzany obwód.

mirror_circuit = circuit.compose(circuit.inverse())
mirror_circuit.measure_all()

mirror_circuit.draw("mpl", scale=0.35, fold=100)

Output of the previous code cell

Ustaw pewne wartości parametrów:

import numpy as np

parameter_values = np.random.rand(mirror_circuit.num_parameters)

Użyj menedżera przejść do transpilacji obwodu na obwód ISA.

preset_pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
)

isa_circuit = preset_pass_manager.run(mirror_circuit)

Następnie zgrupuj bramki i pomiary w opisane pola. Możesz to zrobić ręcznie lub użyć funkcji generate_boxing_pass_manager z Samplomatic dla wygody. Pierwszy obwód będzie miał zastosowany tylko twirling, dlatego potrzebuje tylko adnotacji Twirl. Drugi obwód będzie uruchamiany z pełną mitygacją PEC i potrzebuje zarówno adnotacji Twirl, jak i InjectNoise.

# Pass manager used to create twirled-annotated boxes.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)

mirror_circuit_twirl = boxing_pm.run(isa_circuit)

# Pass manager used to create a new boxed circuit with
# both Twirl and InjectNoise annotations.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates", # no measurement mitigation
inject_noise_strategy="uniform_modification",
)

mirror_circuit_pec = boxing_pm.run(isa_circuit)

Uczenie szumu

Aby zminimalizować liczbę eksperymentów uczenia szumu, zidentyfikuj unikalne instrukcje w drugim obwodzie (tym z polami opisanymi adnotacją InjectNoise). Przy definiowaniu unikalności dwie instrukcje pól są równe, jeśli oba z poniższych warunków są spełnione:

  • Ich zawartość jest równa, z dokładnością do bramek jednoqubitowych.
  • Ich adnotacja Twirl jest równa (każda inna adnotacja jest pomijana).

Prowadzi to do trzech unikalnych instrukcji, mianowicie nieparzystych i parzystych pól bramek oraz końcowego pola pomiarowego.

from samplomatic.utils import find_unique_box_instructions

unique_box_instructions = find_unique_box_instructions(
mirror_circuit_pec.data
)
assert len(unique_box_instructions) == 3

Zainicjuj NoiseLearnerV3, wybierz parametry uczenia poprzez ustawienie jego opcji i uruchom zadanie uczenia szumu.

from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3

learner = NoiseLearnerV3(backend)

learner.options.shots_per_randomization = 128
learner.options.num_randomizations = 32
learner.options.layer_pair_depths = [0, 1, 2, 4, 16, 32]

learner_job = learner.run(unique_box_instructions)

learner_job.job_id()
learner_result = learner_job.result()

Przekonwertuj result na obiekt wymagany przez samplex za pomocą metody result.to_dict.

noise_maps = learner_result.to_dict(
instructions=unique_box_instructions, require_refs=False
)

Wykonanie obwodów

Executor uruchamia obiekty QuantumProgram. Każdy QuantumProgram może zawierać kilka elementów, które są dołączane do programu. Każdy element to zadanie dla programu do wykonania.

Zainicjuj pusty program, żądając 1000 shotów dla każdej konfiguracji każdego elementu.

from qiskit_ibm_runtime.quantum_program import QuantumProgram

# Initialize an empty QuantumProgram
program = QuantumProgram(shots=1000)

Następnie zbuduj szablon obwodu i samplex dla mirror_circuit_twirl i dołącz je do programu. Zażądaj też 900 randomizacji od samplexa. Oznacza to, że samplex wygeneruje 900 zestawów parametrów, a każdy zestaw będzie wykonywany 1000 razy (liczba shotów) na QPU.

To jest pierwsze zadanie programu (wynik 0).

template_twirl, samplex_twirl = build(mirror_circuit_twirl)

program.append_samplex_item(
template_twirl,
samplex=samplex_twirl,
samplex_arguments={"parameter_values": parameter_values},
shape=(900,),
)

Podobnie dołącz szablon obwodu i samplex zbudowany dla mirror_circuit_pec, żądając 900 randomizacji. To jest drugie zadanie programu (wynik 1).

template_pec, samplex_pec = build(mirror_circuit_pec)

program.append_samplex_item(
template_pec,
samplex=samplex_pec,
samplex_arguments={
"parameter_values": parameter_values,
"pauli_lindblad_maps": noise_maps,
"noise_scales": {
ref: -1.0 for ref in noise_maps
}, # Set the scales to -1 for PEC
},
shape=(900,),
)

Importuj Executor i prześlij zadanie.

from qiskit_ibm_runtime.executor import Executor

executor = Executor(backend)
executor_job = executor.run(program)

executor_job.job_id()

executor_results = executor_job.result()
executor_results

twirl_result = executor_results[0]

print(f"Twirl result keys:\n {list(twirl_result.keys())}\n")
print(f"Shape of results: {twirl_result['meas'].shape}")

pec_result = executor_results[1]

print(f"PEC result keys:\n {list(pec_result.keys())}\n")
print(f"Shape of results: {pec_result['meas'].shape}")
Twirl result keys:
['meas', 'measurement_flips.meas']

Shape of results: (900, 1000, 10)
PEC result keys:
['meas', 'measurement_flips.meas', 'pauli_signs']

Shape of results: (900, 1000, 10)

Analiza wyników

Na koniec przetwórz końcowo wyniki, aby estymować wartości oczekiwane jednoqubitowych operatorów Pauliego-Z działających na każdym z dziesięciu aktywnych qubitów (oczekiwana wartość: 1.0).

# Undo measurement twirling
twirl_result_unflipped = (
twirl_result["meas"] ^ twirl_result["measurement_flips.meas"]
)

# Calculate the expectation values of single-qubit Z operators
exp_vals = 1 - 2 * twirl_result_unflipped.mean(axis=1).mean(axis=0)

for qubit, val in enumerate(exp_vals):
print(f"Qubit {qubit} -> {np.round(val, 2)}")
Qubit 0 -> 0.77
Qubit 1 -> 0.76
Qubit 2 -> 0.66
Qubit 3 -> 0.71
Qubit 4 -> 0.69
Qubit 5 -> 0.67
Qubit 6 -> 0.62
Qubit 7 -> 0.59
Qubit 8 -> 0.62
Qubit 9 -> 0.68
# Undo measurement twirling
pec_result_unflipped = (
pec_result["meas"] ^ pec_result["measurement_flips.meas"]
)

# Calculate the signs for PEC mitigation
signs = np.prod((-1) ** pec_result["pauli_signs"], axis=-1)
signs = signs.reshape((signs.shape[0], 1))

# Calculate the expectation values of single-qubit Z operators as required by
# PEC mitigation
exp_vals = 1 - (2 * pec_result_unflipped.mean(axis=1) * signs).mean(axis=0)

for qubit, val in enumerate(exp_vals):
print(f"Qubit {qubit} -> {np.round(val, 2)}")
Qubit 0 -> 0.98
Qubit 1 -> 0.99
Qubit 2 -> 0.96
Qubit 3 -> 0.98
Qubit 4 -> 0.98
Qubit 5 -> 0.98
Qubit 6 -> 0.98
Qubit 7 -> 0.95
Qubit 8 -> 0.95
Qubit 9 -> 0.94

Następne kroki

Zalecenia