Sari la conținutul principal

Exemple Executor

Versiuni de pachete

Codul de pe această pagină a fost dezvoltat folosind următoarele cerințe. Recomandăm utilizarea acestor versiuni sau a unora mai noi.

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

Exemplele din această secțiune ilustrează câteva moduri comune de utilizare a primitivului Executor. Înainte de a rula aceste exemple, urmează instrucțiunile din Instalare Qiskit și Pornire rapidă Executor.

Înainte să începi

Unele dintre exemplele de cod de pe această pagină folosesc samplex, care face parte din pachetul Samplomatic. Prin urmare, înainte de a rula acele blocuri de cod, trebuie să instalezi Samplomatic, după cum este arătat în blocul de cod următor. Pentru mai multe informații, consultă documentația Samplomatic.

pip install samplomatic

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

Exemplu: Circuit parametrizat

Acest exemplu ilustrează cum să adaugi elemente de circuit cu parametri, precum și cum să adaugi elemente samplex. Constă din acești pași:

  1. Configurarea circuitului: Generează și transpilează circuitul țintă.
  2. Pregătirea unui samplex: Grupează porțile și măsurătorile în casete adnotate și generează perechea șablon circuit-samplex.
  3. Executare: Adaugă un element de circuit și un element samplex la un QuantumProgram și execută ambele într-un singur job.

Configurarea circuitului

Pregătește o stare GHZ cu trei qubiți, rotește qubiții în jurul axei Pauli-Z și măsoară qubiții în baza computațională.

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

Specifică backend-ul și transpilează circuitul pentru a folosi numai instrucțiuni suportate de QPU (referite ca un circuit de arhitectură a setului de instrucțiuni (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)

Pregătirea samplex-ului

Folosește funcția de conveniență generate_boxing_pass_manager și parametrii săi de twirling pentru a grupa porțile cu doi qubiți și măsurătorile în casete și a aplica adnotări de 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)

Folosește metoda build pentru a genera circuitul șablon și samplex-ul.

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

Executarea circuitelor

Executor rulează obiecte QuantumProgram. Fiecare QuantumProgram poate conține mai multe elemente. Acest exemplu adaugă un element de circuit și un element samplex pentru execuție. Pentru detalii complete, consultă Intrare și ieșire Executor.

Primul pas este inițializarea unui program gol, solicitând 1024 shots pentru fiecare configurație a fiecărui element.

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

Adaugă elementul de circuit la QuantumProgram. Acest element de circuit constă din două părți — circuitul ISA și 10 seturi de valori ale parametrilor săi.

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

Adaugă elementul samplex la QuantumProgram cu aceste argumente:

  • Circuitul șablon și samplex-ul generat de funcția build
  • Zece seturi de valori ale parametrilor pentru circuitul original
  • Numărul de randomizări de efectuat
# 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),
)

Rularea job-ului Executor

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

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

# Retrieve the result
result = job.result()

Preia rezultatul pentru fiecare sarcină.

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

Exemplu: Efectuarea PEC

Acest exemplu arată cum să folosești un element samplex pentru a efectua anularea probabilistică a erorilor (PEC) pentru atenuarea erorilor.

Consideră o versiune oglindită a unui circuit cu zece qubiți și două straturi unice de porți CX. Acestea sunt sarcinile principale:

Pipeline-ul constă din acești pași:

  1. Configurare: Generează circuitul țintă și grupează operațiunile sale în casete.
  2. Învățare: Învață zgomotul instrucțiunilor pe care vrem să le atenuăm cu PEC.
  3. Execuție: Rulează circuitul pe un backend.
  4. Analiză: Post-procesează și analizează rezultatele.

Pentru comparație, vom rula acest circuit oglindă de două ori. O dată cu aplicat numai twirling Pauli și o dată cu atenuare PEC aplicată.

notă

Utilizarea pentru acest exemplu este de aproximativ 10 minute pe un procesor Heron r2.

Configurarea circuitului

Alege un backend și pregătește un circuit de 10 qubiți.

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

Combină circuitul cu inversul său pentru a crea un circuit oglindă.

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

Setează câteva valori de parametri:

import numpy as np

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

Folosește managerul de treceri pentru a transpila circuitul în circuit ISA.

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

isa_circuit = preset_pass_manager.run(mirror_circuit)

Apoi, grupează porțile și măsurătorile în casete adnotate. Poți face acest lucru manual sau folosi funcția generate_boxing_pass_manager din Samplomatic pentru comoditate. Primul circuit va avea aplicat numai twirling și, prin urmare, are nevoie numai de adnotarea Twirl. Al doilea circuit va fi rulat cu atenuare PEC completă și necesită atât adnotările Twirl, cât ș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)

Învățarea zgomotului

Pentru a minimiza numărul de experimente de învățare a zgomotului, identifică instrucțiunile unice din al doilea circuit (cel cu casete adnotate cu InjectNoise). În definirea unicității, două instrucțiuni de casetă sunt egale dacă ambele din cele ce urmează sunt adevărate:

  • Conținutul lor este egal, până la porți cu un singur qubit.
  • Adnotarea lor Twirl este egală (orice altă adnotare este ignorată).

Aceasta duce la trei instrucțiuni unice, și anume casetele de porți impare și pare și caseta de măsurare finală.

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

Inițializează un NoiseLearnerV3, alege parametrii de învățare prin setarea opțiunilor sale și rulează un job de învățare a zgomotului.

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

Convertește result la obiectul solicitat de samplex folosind metoda result.to_dict.

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

Executarea circuitelor

Executor rulează obiecte QuantumProgram. Fiecare QuantumProgram poate conține mai multe elemente, care sunt adăugate la program. Fiecare element este o sarcină pentru program de efectuat.

Inițializează un program gol, solicitând 1000 shots pentru fiecare configurație a fiecărui element.

from qiskit_ibm_runtime.quantum_program import QuantumProgram

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

Apoi, construiește circuitul șablon și samplex-ul pentru mirror_circuit_twirl și adaugă-le la program. Solicită de asemenea 900 de randomizări de la samplex. Aceasta înseamnă că samplex-ul va genera 900 seturi de parametri și fiecare set va fi executat de 1000 ori (numărul de shots) pe QPU.

Aceasta este prima sarcină a programului (rezultatul 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,),
)

În mod similar, adaugă circuitul șablon și samplex-ul construit pentru mirror_circuit_pec, solicitând 900 randomizări. Aceasta este a doua sarcină a programului (rezultatul 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,),
)

Importă Executor și trimite un job.

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)

Analizarea rezultatelor

În final, post-procesează rezultatele pentru a estima valorile așteptate ale operatorilor Pauli-Z cu un singur qubit care acționează pe fiecare dintre cei zece qubiți activi (valoare așteptată: 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

Pașii următori

Recomandări