Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

Simularea clasică a circuitelor cu OBP

În acest ghid vei învăța cum să simulezi clasic instanțe QuantumCircuit pentru a estima valori de așteptare exclusiv prin mijloacele OBP.

Deoarece OBP preia o observabilă și o retropropagă printr-un circuit dat, „simularea” unui circuit înseamnă calcularea valorii de așteptare a observabilei țintă în raport cu acest circuit. După cum vei vedea mai târziu, pachetul qiskit-addon-obp este capabil chiar să gestioneze modele de zgomot simple, permițându-ți să calculezi și valori de așteptare cu zgomot!

Construirea unui circuit exemplu​

Pentru scopurile acestui ghid, vom folosi același circuit exemplu ca în ghidul de trunchiere a termenilor Pauli:

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "rustworkx": "rustworkx"}
_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")
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types

# we generate a linear chain of 10 qubits
num_qubits = 10
linear_chain = rustworkx.generators.path_graph(num_qubits)

# we use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# we evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)

Totuși, cele de mai sus reprezintă doar circuitul care descrie evoluția în timp sub un Hamiltonian ales. Avem nevoie și de o stare inițială de la care să pornim, în raport cu care calculăm valorile de așteptare ale observabilei noastre.

Desigur, am putea alege starea cu toți qubiții în zero (sau starea vid) ca stare inițială, dar pentru a arăta cum ți-ai putea insera propria stare inițială, alegem mai jos una diferită.

O posibilitate ar fi să adăugăm starea inițială înaintea circuitului nostru de evoluție în timp de mai sus: circuit.compose(initial_state, front=True). Dar, deoarece ne-am tăiat deja circuit în felii, este mai simplu să inserăm starea inițială ca prima felie, ceea ce facem mai jos.

În acest fel, putem pur și simplu înlocui prima felie cu o altă stare inițială, dacă vrem să o schimbăm în viitor, fără a fi nevoie să recalculăm feliile.

from qiskit.circuit import QuantumCircuit

initial_state = QuantumCircuit(num_qubits)
for i in range(0, num_qubits, 2):
initial_state.x(i)

slices.insert(0, initial_state)
# for visualization purposes only, we recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)

Output of the previous code cell

Simularea unei valori de așteptare fără zgomot​

Ca observabilă țintă, alegem observabila ZZ pe qubiții centrali:

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp("IIIIZZIIII")

În acest moment suntem deja pregătiți să simulăm clasic valoarea de așteptare folosind OBP. Pentru aceasta, furnizăm pur și simplu toate feliile metodei backpropagate, astfel:

from qiskit_addon_obp import backpropagate

vacuum_state_obs, _, metadata = backpropagate(obs, slices)

Am retropropagat acum observabila noastră țintă obs prin întregul circuit (inclusiv initial_state, pe care l-am plasat în slices[0]), rezultând un nou SparsePauliOp a cărui valoare de așteptare o obținem proiectându-l pe starea vid (|00...00>).

Aceasta se poate realiza direct, însumând coeficienții tuturor termenilor Pauli definiți în baza computațională:

vacuum_state_obs.coeffs[~vacuum_state_obs.paulis.x.any(axis=1)].sum()
np.complex128(-0.8285688012239535+4.9487770271457865e-20j)

Ca verificare de bun-simț (și pentru a dovedi că funcționează), putem compara rezultatul nostru cu Statevector din Qiskit:

from qiskit.quantum_info import Statevector

Statevector(combine_slices(slices)).expectation_value(obs)
np.complex128(-0.8285687255430366+0j)

Câteva note despre performanță​

Eficiența computațională a apelului backpropagate de mai sus va depinde puternic de mai mulți factori, printre care:

  • structura circuit
  • metoda de tăiere a circuitului în felii
  • observabila țintă
  • parametrii de trunchiere

Deoarece metoda backpropagate simplifică observabila după aplicarea fiecărei felii, numărul de porți dintr-o felie poate influența dramatic efortul computațional. Cea mai agresivă strategie de simplificare a operatorului se obține tăind circuitul în felii de porți individuale.

În plus, poți folosi toate mecanismele de trunchiere integrate în metoda backpropagate. Nu am făcut-o mai sus, rezultând efectiv o valoare de așteptare exactă, dar poți afla cum să o faci în ghidul de trunchiere a termenilor Pauli.

Simularea unei valori de așteptare cu zgomot​

Pachetul qiskit-addon-obp acceptă și modele de zgomot sub formă de PauliLindbladError. Aceasta este deosebit de utilă atunci când ai caracterizat modelul de zgomot al straturilor de 2 qubiți din circuitul tău, de exemplu folosind NoiseLearner.

În această secțiune vei vedea cum poți folosi obiectele LayerError returnate de NoiseLearner pentru a calcula valori de așteptare cu zgomot folosind OBP.

Obținerea unui model de zgomot​

În mod normal, ai executa NoiseLearner pentru a obține un model de zgomot al circuitului tău specific. Pentru a evita complexitatea (și aleatoriul) din acest tutorial, ne vom abține să facem asta și vom codifica în schimb direct un model de zgomot pentru circuitul nostru mai jos.

Ne asigurăm totuși că structura datelor noastre se potrivește cu cea a NoiseLearnerResult. În forma sa actuală (netranspilată), circuitul nostru conține 4 straturi unice de porți pe 2 qubiți:

  • slices[1]: care are porți Rxx pe toate perechile impare de qubiți
  • slices[2]: care are porți Rxx pe toate perechile pare de qubiți
  • slices[3]: care are porți Ryy pe toate perechile impare de qubiți
  • slices[4]: care are porți Ryy pe toate perechile pare de qubiți

În celula de mai jos, construim manual 4 instanțe LayerError, câte una pentru fiecare dintre aceste straturi, cu rate de eroare aleatorii.

import numpy as np
from qiskit.quantum_info import PauliList
from qiskit_ibm_runtime.utils.noise_learner_result import (
LayerError,
PauliLindbladError,
)

# fmt: off
pauli_errors_even = ['IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIXX', 'IIIIIIIIXY', 'IIIIIIIIXZ', 'IIIIIIIIYI', 'IIIIIIIIYX', 'IIIIIIIIYY', 'IIIIIIIIYZ', 'IIIIIIIIZI', 'IIIIIIIIZX', 'IIIIIIIIZY', 'IIIIIIIIZZ', 'IIIIIIIXII', 'IIIIIIIXXI', 'IIIIIIIXYI', 'IIIIIIIXZI', 'IIIIIIIYII', 'IIIIIIIYXI', 'IIIIIIIYYI', 'IIIIIIIYZI', 'IIIIIIIZII', 'IIIIIIIZXI', 'IIIIIIIZYI', 'IIIIIIIZZI', 'IIIIIIXIII', 'IIIIIIXXII', 'IIIIIIXYII', 'IIIIIIXZII', 'IIIIIIYIII', 'IIIIIIYXII', 'IIIIIIYYII', 'IIIIIIYZII', 'IIIIIIZIII', 'IIIIIIZXII', 'IIIIIIZYII', 'IIIIIIZZII', 'IIIIIXIIII', 'IIIIIXXIII', 'IIIIIXYIII', 'IIIIIXZIII', 'IIIIIYIIII', 'IIIIIYXIII', 'IIIIIYYIII', 'IIIIIYZIII', 'IIIIIZIIII', 'IIIIIZXIII', 'IIIIIZYIII', 'IIIIIZZIII', 'IIIIXIIIII', 'IIIIXXIIII', 'IIIIXYIIII', 'IIIIXZIIII', 'IIIIYIIIII', 'IIIIYXIIII', 'IIIIYYIIII', 'IIIIYZIIII', 'IIIIZIIIII', 'IIIIZXIIII', 'IIIIZYIIII', 'IIIIZZIIII', 'IIIXIIIIII', 'IIIXXIIIII', 'IIIXYIIIII', 'IIIXZIIIII', 'IIIYIIIIII', 'IIIYXIIIII', 'IIIYYIIIII', 'IIIYZIIIII', 'IIIZIIIIII', 'IIIZXIIIII', 'IIIZYIIIII', 'IIIZZIIIII', 'IIXIIIIIII', 'IIXXIIIIII', 'IIXYIIIIII', 'IIXZIIIIII', 'IIYIIIIIII', 'IIYXIIIIII', 'IIYYIIIIII', 'IIYZIIIIII', 'IIZIIIIIII', 'IIZXIIIIII', 'IIZYIIIIII', 'IIZZIIIIII', 'IXIIIIIIII', 'IXXIIIIIII', 'IXYIIIIIII', 'IXZIIIIIII', 'IYIIIIIIII', 'IYXIIIIIII', 'IYYIIIIIII', 'IYZIIIIIII', 'IZIIIIIIII', 'IZXIIIIIII', 'IZYIIIIIII', 'IZZIIIIIII', 'XIIIIIIIII', 'XXIIIIIIII', 'XYIIIIIIII', 'XZIIIIIIII', 'YIIIIIIIII', 'YXIIIIIIII', 'YYIIIIIIII', 'YZIIIIIIII', 'ZIIIIIIIII', 'ZXIIIIIIII', 'ZYIIIIIIII', 'ZZIIIIIIII']
pauli_errors_odd = ['IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIXXI', 'IIIIIIIXYI', 'IIIIIIIXZI', 'IIIIIIIYII', 'IIIIIIIYXI', 'IIIIIIIYYI', 'IIIIIIIYZI', 'IIIIIIIZII', 'IIIIIIIZXI', 'IIIIIIIZYI', 'IIIIIIIZZI', 'IIIIIIXIII', 'IIIIIIXXII', 'IIIIIIXYII', 'IIIIIIXZII', 'IIIIIIYIII', 'IIIIIIYXII', 'IIIIIIYYII', 'IIIIIIYZII', 'IIIIIIZIII', 'IIIIIIZXII', 'IIIIIIZYII', 'IIIIIIZZII', 'IIIIIXIIII', 'IIIIIXXIII', 'IIIIIXYIII', 'IIIIIXZIII', 'IIIIIYIIII', 'IIIIIYXIII', 'IIIIIYYIII', 'IIIIIYZIII', 'IIIIIZIIII', 'IIIIIZXIII', 'IIIIIZYIII', 'IIIIIZZIII', 'IIIIXIIIII', 'IIIIXXIIII', 'IIIIXYIIII', 'IIIIXZIIII', 'IIIIYIIIII', 'IIIIYXIIII', 'IIIIYYIIII', 'IIIIYZIIII', 'IIIIZIIIII', 'IIIIZXIIII', 'IIIIZYIIII', 'IIIIZZIIII', 'IIIXIIIIII', 'IIIXXIIIII', 'IIIXYIIIII', 'IIIXZIIIII', 'IIIYIIIIII', 'IIIYXIIIII', 'IIIYYIIIII', 'IIIYZIIIII', 'IIIZIIIIII', 'IIIZXIIIII', 'IIIZYIIIII', 'IIIZZIIIII', 'IIXIIIIIII', 'IIXXIIIIII', 'IIXYIIIIII', 'IIXZIIIIII', 'IIYIIIIIII', 'IIYXIIIIII', 'IIYYIIIIII', 'IIYZIIIIII', 'IIZIIIIIII', 'IIZXIIIIII', 'IIZYIIIIII', 'IIZZIIIIII', 'IXIIIIIIII', 'IXXIIIIIII', 'IXYIIIIIII', 'IXZIIIIIII', 'IYIIIIIIII', 'IYXIIIIIII', 'IYYIIIIIII', 'IYZIIIIIII', 'IZIIIIIIII', 'IZXIIIIIII', 'IZYIIIIIII', 'IZZIIIIIII']
# fmt: on

np.random.seed(42)

layer_error_odd_xx = LayerError(
circuit=slices[1],
qubits=list(range(num_qubits)),
error=PauliLindbladError(
PauliList(pauli_errors_odd),
0.0001 + 0.0004 * np.random.rand(len(pauli_errors_odd)),
),
)

layer_error_even_xx = LayerError(
circuit=slices[2],
qubits=list(range(num_qubits)),
error=PauliLindbladError(
PauliList(pauli_errors_even),
0.0001 + 0.0004 * np.random.rand(len(pauli_errors_even)),
),
)

layer_error_odd_yy = LayerError(
circuit=slices[3],
qubits=list(range(num_qubits)),
error=PauliLindbladError(
PauliList(pauli_errors_odd),
0.0001 + 0.0004 * np.random.rand(len(pauli_errors_odd)),
),
)

layer_error_even_yy = LayerError(
circuit=slices[4],
qubits=list(range(num_qubits)),
error=PauliLindbladError(
PauliList(pauli_errors_even),
0.0001 + 0.0004 * np.random.rand(len(pauli_errors_even)),
),
)

Dacă ai fi folosit NoiseLearner pentru a identifica straturile unice de porți pe 2 qubiți ale circuitului tău și pentru a le caracteriza zgomotul, ai fi obținut un obiect NoiseLearnerResult. Acest rezultat ar conține o listă de obiecte LayerError, exact ca cele construite manual mai sus.

Pentru fiecare strat unic de 2 qubiți, LayerError conține:

  • QuantumCircuit care reprezintă acel strat de porți pe 2 qubiți
  • indicii qubiților pe care acționează acest circuit
  • PauliLindbladError care reprezintă modelul de zgomot caracterizat al acestui strat

PauliLindbladError va conține două obiecte:

  • lista erorilor Pauli care au fost caracterizate
  • ratele de eroare corespunzătoare fiecăreia dintre acele erori Pauli

În mod normal, lista erorilor Pauli va fi rară (sparse). Mai exact, va conține erorile Pauli pe un singur qubit pentru toți qubiții asupra cărora acționează porți, precum și erorile Pauli pe doi qubiți pentru toți acei qubiți care sunt conectați.

Inserarea straturilor cu zgomot în circuitul nostru​

În secțiunea anterioară am construit special câte un LayerError pentru fiecare dintre straturile unice de porți pe 2 qubiți cunoscute. Aceasta înseamnă că știm exact care LayerError corespunde unei anumite felii a noastre.

În mod normal, când folosești LayerError, va trebui să afli care este stratul unic de porți pe 2 qubiți și unde apare în circuitul tău. Apoi va trebui să-ți ajustezi circuit și/sau slices pentru a insera LayerError în mod corespunzător. Cum se face acest lucru în cazul general depășește scopul acestui ghid practic. TODO: link to external documentation, once it exists! Aici, lucrurile sunt mai simple deoarece știm cărei felii îi corespunde un LayerError. Prin urmare, rămâne doar să inserăm felii noi pentru a reprezenta zgomotul.

Reține că trebuie să învelim fiecare PauliLindbladError din LayerError.error într-un PauliLindbladErrorInstruction pentru ca acesta să fie o instrucțiune QuantumCircuit validă.

from qiskit_addon_obp.utils.noise import PauliLindbladErrorInstruction

noisy_slices = []
for slice_ in slices:
if slice_ == layer_error_even_xx.circuit:
noisy_slices.append(
QuantumCircuit.from_instructions(
[
(
PauliLindbladErrorInstruction(
layer_error_even_xx.error
),
slice_.qubits,
)
]
)
)
elif slice_ == layer_error_odd_xx.circuit:
noisy_slices.append(
QuantumCircuit.from_instructions(
[
(
PauliLindbladErrorInstruction(
layer_error_odd_xx.error
),
slice_.qubits,
)
]
)
)
elif slice_ == layer_error_even_yy.circuit:
noisy_slices.append(
QuantumCircuit.from_instructions(
[
(
PauliLindbladErrorInstruction(
layer_error_even_yy.error
),
slice_.qubits,
)
]
)
)
elif slice_ == layer_error_odd_yy.circuit:
noisy_slices.append(
QuantumCircuit.from_instructions(
[
(
PauliLindbladErrorInstruction(
layer_error_odd_yy.error
),
slice_.qubits,
)
]
)
)
noisy_slices.append(slice_)

Ne putem verifica munca desenând circuitul mai jos:

combine_slices(noisy_slices, include_barriers=True).draw(
"mpl", fold=100, scale=0.8
)

Output of the previous code cell

Simularea unei valori de așteptare cu zgomot​

În acest moment, simularea clasică a valorii de așteptare funcționează exact la fel ca înainte, doar că

vacuum_state_noisy_obs, _, metadata = backpropagate(obs, noisy_slices)
vacuum_state_noisy_obs.coeffs[
~vacuum_state_noisy_obs.paulis.x.any(axis=1)
].sum()
np.complex128(-0.7230801696448901+7.082755280463563e-19j)

Subliniem din nou că trebuie luate în considerare mai multe aspecte de performanță. Te rugăm să te întorci la secțiunea corespunzătoare de mai sus.