Classical na pag-simulate ng mga circuit gamit ang OBP
Sa gabay na ito, malalaman mo kung paano i-simulate nang classical ang mga instance ng QuantumCircuit para tantiyahin ang mga expectation value nang buo sa pamamagitan ng OBP.
Dahil kukuha ang OBP ng isang observable at ibabackpropagate ito sa isang ibinigay na circuit, ang "simulation" ng isang circuit ay katumbas ng pagkalkula ng expectation value ng target na observable kaugnay ng circuit na ito.
Gaya ng makikita mo mamaya, kaya pa ng package na qiskit-addon-obp na humawak ng mga simpleng noise model, kaya makakalkula mo rin ang mga noisy na expectation value!
Paggawa ng halimbawang circuit
Para sa layunin ng gabay na ito, gagamitin natin ang parehong halimbawang circuit tulad ng sa gabay sa Pauli term truncation:
# 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)
Gayunpaman, ang nasa itaas ay purong circuit lang na naglalarawan ng time evolution sa ilalim ng napiling Hamiltonian. Kailangan din natin ng initial state na panimulan, na gagamitin natin sa pagkalkula ng mga expectation value ng ating observable.
Siyempre, maaari nating piliin ang all-zero (o vacuum) state bilang initial state, pero para ipakita kung paano maglalagay ng sariling initial state, pipili tayo ng iba sa ibaba.
Isang posibilidad ay i-prepend ang initial state sa time-evolution circuit natin sa itaas: circuit.compose(initial_state, front=True).
Pero dahil hinati na natin ang ating circuit sa mga slice, mas madaling ilagay na lang ang initial state bilang unang slice, na gagawin natin sa ibaba.
Sa ganitong paraan, madali nating mapapalitan ang unang slice ng ibang initial state kung gusto nating palitan ito sa hinaharap, nang hindi na kailangang kalkulahing muli ang ating mga slice.
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)

Pag-simulate ng noiseless na expectation value
Bilang target na observable, pinipili natin ang observable na ZZ sa mga qubit sa gitna:
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp("IIIIZZIIII")
Sa puntong ito, handa na tayong i-simulate nang classical ang expectation value gamit ang OBP.
Para gawin ito, ibibigay lang natin ang lahat ng slice sa method na backpropagate, ganito:
from qiskit_addon_obp import backpropagate
vacuum_state_obs, _, metadata = backpropagate(obs, slices)
Na-backpropagate na natin ang ating target na observable na obs sa buong circuit (kasama ang initial_state na inilagay natin sa slices[0]) na nagresulta sa bagong SparsePauliOp na ang expectation value ay makukuha natin sa pag-project nito sa vacuum state (|00...00>).
Magagawa ito nang diretso sa pamamagitan ng pagsusuma ng mga coefficient ng lahat ng Pauli term na naka-define sa computational basis:
vacuum_state_obs.coeffs[~vacuum_state_obs.paulis.x.any(axis=1)].sum()
np.complex128(-0.8285688012239535+4.9487770271457865e-20j)
Bilang sanity check (at para patunayang gumagana ito), maaari nating ihambing ang resulta natin sa Statevector ng Qiskit:
from qiskit.quantum_info import Statevector
Statevector(combine_slices(slices)).expectation_value(obs)
np.complex128(-0.8285687255430366+0j)
Ilang tala tungkol sa performance
Ang computational efficiency ng tawag sa backpropagate sa itaas ay malaki ang nakasalalay sa maraming bagay, kabilang ang:
- ang istruktura ng
circuit - ang paraan ng paghiwa sa circuit
- ang target na observable
- ang mga truncation parameter
Dahil pinapasimple ng method na backpropagate ang observable pagkatapos ma-apply ang bawat slice, malaki ang maaaring maging epekto ng bilang ng mga gate sa isang slice sa computational burden.
Ang pinaka-agresibong estratehiya sa pagpapasimple ng operator ay makakamit sa paghiwa ng circuit mo sa mga slice na may tig-isang gate.
Bukod pa rito, magagamit mo ang lahat ng truncation mechanism na built-in sa method na backpropagate.
Hindi natin ito ginawa sa itaas, kaya eksaktong expectation value ang naging resulta, pero malalaman mo kung paano sa gabay sa Pauli term truncation.
Pag-simulate ng noisy na expectation value
Sinusuportahan din ng package na qiskit-addon-obp ang paghawak ng mga noise model sa anyo ng PauliLindbladError.
Lalo itong kapaki-pakinabang kapag na-characterize mo na ang noise model ng mga 2-qubit layer sa circuit mo, halimbawa gamit ang NoiseLearner.
Sa seksyong ito, makikita mo kung paano magagamit ang mga object na LayerError na ibinabalik ng NoiseLearner para kalkulahin ang mga noisy na expectation value gamit ang OBP.
Pagkuha ng noise model
Karaniwan, ie-execute mo ang NoiseLearner para makuha ang noise model ng iyong partikular na circuit.
Para maiwasan ang komplikasyon (at randomness) sa tutorial na ito, hindi natin ito gagawin, at sa halip ay maghahard-code tayo ng ilang noise model para sa ating circuit sa ibaba.
Gayunpaman, tinitiyak natin na ang istruktura ng ating data ay tumutugma sa NoiseLearnerResult.
Sa kasalukuyang (hindi pa na-transpile) anyo nito, ang circuit natin ay may 4 na natatanging layer ng 2-qubit gate:
slices[1]: na may mga gate naRxxna kumikilos sa lahat ng odd na pares ng qubitslices[2]: na may mga gate naRxxna kumikilos sa lahat ng even na pares ng qubitslices[3]: na may mga gate naRyyna kumikilos sa lahat ng odd na pares ng qubitslices[4]: na may mga gate naRyyna kumikilos sa lahat ng even na pares ng qubit
Sa cell sa ibaba, manu-mano tayong gumagawa ng 4 na instance ng LayerError para sa bawat isa sa mga layer na ito na may ilang randomized na error rate.
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)),
),
)
Kung ginamit mo ang NoiseLearner para tukuyin ang mga natatanging 2-qubit gate layer ng circuit mo at i-characterize ang noise ng mga ito, makakakuha ka ng object na NoiseLearnerResult.
Magtataglay ang resultang ito ng listahan ng mga object na LayerError, katulad ng mga manu-mano nating ginawa sa itaas.
Para sa bawat natatanging 2-qubit layer, ang LayerError ay naglalaman ng:
- ang
QuantumCircuitna kumakatawan sa 2-qubit gate layer na iyon - ang mga qubit index na pinagkikilusan ng circuit na ito
- ang
PauliLindbladErrorna kumakatawan sa na-characterize na noise model ng layer na ito
Ang PauliLindbladError ay maglalaman ng dalawang object:
- ang listahan ng mga Pauli error na na-characterize
- ang mga error rate na tumutugma sa bawat isa sa mga Pauli error na iyon
Karaniwan, sparse ang listahan ng mga Pauli error. Mas tiyak, maglalaman ito ng mga single-qubit Pauli error sa lahat ng qubit na may mga gate na kumikilos sa kanila pati na ang mga two-qubit Pauli error sa lahat ng qubit na magkakonekta.
Paglalagay ng mga noisy na layer sa ating circuit
Sa nakaraang seksyon, partikular tayong gumawa ng isang LayerError para sa bawat isa sa ating mga kilalang natatanging 2-qubit gate layer.
Ang ibig sabihin nito, alam natin kung aling LayerError ang eksaktong tumutugma sa isang partikular na slice natin.
Karaniwan, kapag ginagamit ang LayerError, kakailanganin mong alamin kung ano ang natatanging 2-qubit gate layer, at kung saan ito lumilitaw sa loob ng circuit mo.
Kakailanganin mo pang ayusin ang iyong circuit at/o slices para ilagay nang naaayon ang LayerError.
Ang kung paano ito gagawin sa pangkalahatang kaso ay lampas sa saklaw ng how-to guide na ito.
TODO: link to external documentation, once it exists!
Dito, mas simple ang buhay natin dahil alam natin kung aling slice ang tumutugma sa isang LayerError.
Kaya, usapin na lang ito ng paglalagay ng mga bagong slice para kumatawan sa noise.
Tandaan na kailangan nating i-wrap ang bawat PauliLindbladError mula sa LayerError.error sa isang PauliLindbladErrorInstruction para maging valid itong instruction ng QuantumCircuit.
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_)
Maaari nating suriin ang ginawa natin at iguhit ang circuit sa ibaba:
combine_slices(noisy_slices, include_barriers=True).draw(
"mpl", fold=100, scale=0.8
)
Pag-simulate ng noisy na expectation value
Sa puntong ito, ang classical na pag-simulate ng expectation value ay gumagana nang eksaktong kapareho ng dati, kaya lang
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)
Muli nating binibigyang-diin na dapat isaalang-alang ang ilang alalahanin sa performance. Pakibalik sa kaukulang seksyon sa itaas.
