ข้ามไปยังเนื้อหาหลัก
โครงการนี้อยู่ในช่วงเบต้า พบข้อผิดพลาด? สร้าง GitHub Issue มีคำถามหรือไอเดีย? ถามใน Discussions — ยินดีรับฟังความคิดเห็น!
คุณภาพการแปลในหน้านี้เป็นอย่างไร?

การจำลองวงจรแบบคลาสสิกด้วย OBP

ในคู่มือนี้ คุณจะได้เรียนรู้วิธีจำลองอินสแตนซ์ QuantumCircuit แบบคลาสสิกเพื่อประมาณค่าคาดหมายโดยใช้ OBP ทั้งหมด

เนื่องจาก OBP จะรับ observable แล้ว backpropagate ผ่านวงจรที่กำหนด "การจำลอง" วงจรจึงเท่ากับการคำนวณค่าคาดหมายของ observable เป้าหมายเทียบกับวงจรนี้ อย่างที่คุณจะได้เห็นต่อไป แพ็กเกจ qiskit-addon-obp สามารถจัดการโมเดลสัญญาณรบกวนอย่างง่ายได้ด้วย ทำให้คุณคำนวณค่าคาดหมายที่มีสัญญาณรบกวนได้เช่นกัน!

การสร้างวงจรตัวอย่าง​

สำหรับวัตถุประสงค์ของคู่มือนี้ เราจะใช้วงจรตัวอย่างเดียวกับในคู่มือการตัดทอนเทอม 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)

อย่างไรก็ตาม ข้างต้นเป็นเพียงวงจรที่อธิบายการวิวัฒนาการตามเวลาภายใต้ Hamiltonian ที่เลือกเท่านั้น เรายังต้องมีสถานะเริ่มต้นเพื่อเริ่มต้น ซึ่งเราจะใช้คำนวณค่าคาดหมายของ observable ของเรา

แน่นอนว่าเราเลือกสถานะศูนย์ทั้งหมด (หรือสถานะสุญญากาศ) เป็นสถานะเริ่มต้นได้ แต่เพื่อแสดงวิธีใส่สถานะเริ่มต้นของตัวเอง ด้านล่างเราเลือกสถานะอื่น

หนึ่งความเป็นไปได้คือการนำสถานะเริ่มต้นไปต่อไว้หน้าวงจรการวิวัฒนาการตามเวลาข้างต้น: circuit.compose(initial_state, front=True) แต่เนื่องจากเราแบ่ง circuit เป็น slice ไปแล้ว การใส่สถานะเริ่มต้นเป็น slice แรกจะง่ายกว่า ซึ่งเราทำด้านล่าง

ด้วยวิธีนี้ เราสามารถแทนที่ slice แรกด้วยสถานะเริ่มต้นอื่นได้ง่าย ๆ หากต้องการเปลี่ยนในอนาคต โดยไม่ต้องคำนวณ 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)

Output of the previous code cell

การจำลองค่าคาดหมายแบบไม่มีสัญญาณรบกวน​

สำหรับ observable เป้าหมาย เราเลือก observable ZZ บน qubit ตรงกลาง:

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp("IIIIZZIIII")

ถึงตอนนี้ เราพร้อมจำลองค่าคาดหมายแบบคลาสสิกด้วย OBP แล้ว เราเพียงส่ง slice ทั้งหมด ให้เมธอด backpropagate ดังนี้:

from qiskit_addon_obp import backpropagate

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

ตอนนี้เราได้ backpropagate observable เป้าหมาย obs ผ่านวงจร_ทั้งหมด_ (รวมถึง initial_state ที่เราวางไว้ที่ slices[0]) ได้ SparsePauliOp ใหม่ ซึ่งเราหาค่าคาดหมายของมันได้โดยการโปรเจกต์ลงบน_สถานะสุญญากาศ_ (|00...00>)

ทำได้ตรงไปตรงมาโดยการรวมสัมประสิทธิ์ของเทอม Pauli ทั้งหมดที่กำหนดใน computational basis:

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

เพื่อตรวจสอบความถูกต้อง (และพิสูจน์ว่าวิธีนี้ใช้ได้) เราเปรียบเทียบผลลัพธ์กับ Statevector ของ Qiskit ได้:

from qiskit.quantum_info import Statevector

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

หมายเหตุเกี่ยวกับประสิทธิภาพ​

ประสิทธิภาพเชิงการคำนวณของการเรียก backpropagate ข้างต้นจะขึ้นอยู่กับหลายสิ่งอย่างมาก ได้แก่:

  • โครงสร้างของ circuit
  • วิธีการแบ่งวงจรเป็น slice
  • observable เป้าหมาย
  • พารามิเตอร์การตัดทอน

เนื่องจากเมธอด backpropagate ทำให้ observable ง่ายขึ้นหลังจากใช้ทุก slice จำนวน Gate ใน slice จึงส่งผลอย่างมากต่อภาระการคำนวณ กลยุทธ์ที่ลดความซับซ้อนของ operator ได้เข้มข้นที่สุดทำได้โดยการแบ่งวงจรเป็น slice ที่มี Gate เดี่ยว ๆ

นอกจากนี้ คุณยังใช้กลไกการตัดทอนทั้งหมดที่มีในเมธอด backpropagate ได้ เราไม่ได้ทำเช่นนั้นข้างต้น ส่งผลให้ได้ค่าคาดหมายที่แน่นอน แต่คุณสามารถเรียนรู้วิธีทำได้ในคู่มือการตัดทอนเทอม Pauli

การจำลองค่าคาดหมายแบบมีสัญญาณรบกวน​

แพ็กเกจ qiskit-addon-obp ยังรองรับการจัดการโมเดลสัญญาณรบกวนในรูปแบบ PauliLindbladError ด้วย สิ่งนี้มีประโยชน์เป็นพิเศษเมื่อคุณได้ระบุลักษณะโมเดลสัญญาณรบกวนของเลเยอร์ 2 qubit ในวงจรแล้ว เช่น ด้วยการใช้ NoiseLearner

ในส่วนนี้ คุณจะได้เห็นวิธีใช้อ็อบเจ็กต์ LayerError ที่ NoiseLearner คืนมาเพื่อคำนวณค่าคาดหมายที่มีสัญญาณรบกวนด้วย OBP

การได้โมเดลสัญญาณรบกวน​

โดยปกติ คุณจะรัน NoiseLearner เพื่อให้ได้โมเดลสัญญาณรบกวนของวงจรเฉพาะของคุณ เพื่อหลีกเลี่ยงความซับซ้อน (และความสุ่ม) ในบทเรียนนี้ เราจะไม่ทำเช่นนั้น แต่จะกำหนดโมเดลสัญญาณรบกวนแบบฮาร์ดโค้ดสำหรับวงจรของเราด้านล่างแทน

อย่างไรก็ตาม เราทำให้แน่ใจว่าโครงสร้างข้อมูลของเราตรงกับ NoiseLearnerResult ในรูปแบบปัจจุบัน (ที่ยังไม่ผ่านการ transpile) วงจรของเรามีเลเยอร์เฉพาะตัวของ Gate 2 qubit จำนวน 4 เลเยอร์:

  • slices[1]: มี Gate Rxx กระทำกับคู่ qubit ที่เป็นเลขคี่ทั้งหมด
  • slices[2]: มี Gate Rxx กระทำกับคู่ qubit ที่เป็นเลขคู่ทั้งหมด
  • slices[3]: มี Gate Ryy กระทำกับคู่ qubit ที่เป็นเลขคี่ทั้งหมด
  • slices[4]: มี Gate Ryy กระทำกับคู่ qubit ที่เป็นเลขคู่ทั้งหมด

ในเซลล์ด้านล่าง เราสร้างอินสแตนซ์ LayerError 4 อินสแตนซ์ด้วยตนเองสำหรับแต่ละเลเยอร์เหล่านี้ พร้อมอัตราความคลาดเคลื่อนที่สุ่มขึ้น

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

หากคุณใช้ NoiseLearner ระบุเลเยอร์ Gate 2 qubit เฉพาะตัวของวงจรและระบุลักษณะสัญญาณรบกวนของเลเยอร์เหล่านั้น คุณจะได้อ็อบเจ็กต์ NoiseLearnerResult ผลลัพธ์นี้จะมีรายการของอ็อบเจ็กต์ LayerError เหมือนกับที่เราสร้างด้วยตนเองข้างต้น

สำหรับแต่ละเลเยอร์ 2 qubit เฉพาะตัว LayerError ประกอบด้วย:

  • QuantumCircuit ที่แทนเลเยอร์ Gate 2 qubit นั้น
  • ดัชนี qubit ที่วงจรนี้กระทำ
  • PauliLindbladError ซึ่งแทนโมเดลสัญญาณรบกวนที่ระบุลักษณะแล้วของเลเยอร์นี้

PauliLindbladError จะประกอบด้วยสองอ็อบเจ็กต์:

  • รายการของความคลาดเคลื่อน Pauli ที่ระบุลักษณะแล้ว
  • อัตราความคลาดเคลื่อนที่สอดคล้องกับความคลาดเคลื่อน Pauli แต่ละตัว

โดยปกติ รายการของความคลาดเคลื่อน Pauli จะเบาบาง กล่าวคือจะประกอบด้วยความคลาดเคลื่อน Pauli แบบ qubit เดี่ยวบนทุก qubit ที่มี Gate กระทำอยู่ รวมถึงความคลาดเคลื่อน Pauli แบบสอง qubit บนทุก qubit ที่เชื่อมต่อกัน

การแทรกเลเยอร์ที่มีสัญญาณรบกวนลงในวงจรของเรา​

ในส่วนก่อนหน้า เราได้สร้าง LayerError หนึ่งอันสำหรับแต่ละเลเยอร์ Gate 2 qubit เฉพาะตัวที่เรารู้จักโดยเฉพาะ ซึ่งหมายความว่าเรารู้ว่า LayerError ใดตรงกับ slice ใด slice หนึ่งของเราพอดี

โดยปกติ เมื่อใช้ LayerError คุณจะต้องหาให้ได้ว่าเลเยอร์ Gate 2 qubit เฉพาะตัวคืออะไร และเกิดขึ้นที่ใดภายในวงจรของคุณ จากนั้นคุณต้องปรับ circuit และ/หรือ slices เพื่อแทรก LayerError ตามนั้น วิธีทำในกรณีทั่วไปอยู่นอกขอบเขตของคู่มือวิธีทำนี้ TODO: link to external documentation, once it exists! ที่นี่เรื่องง่ายขึ้นเพราะเรารู้ว่า LayerError สอดคล้องกับ slice ใด ดังนั้นตอนนี้จึงเหลือเพียงการแทรก slice ใหม่เพื่อแทนสัญญาณรบกวน

สังเกตว่าเราต้องห่อ PauliLindbladError แต่ละอันจาก LayerError.error ด้วย PauliLindbladErrorInstruction เพื่อให้เป็นคำสั่งของ 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_)

เราตรวจสอบงานของเราและวาดวงจรด้านล่างได้:

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

Output of the previous code cell

การจำลองค่าคาดหมายแบบมีสัญญาณรบกวน​

ถึงตอนนี้ การจำลองค่าคาดหมายแบบคลาสสิกทำงานเหมือนกับก่อนหน้านี้ทุกประการ เพียงแต่

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)

เราขอย้ำอีกครั้งว่าควรพิจารณาประเด็นด้านประสิทธิภาพหลายอย่าง โปรดย้อนกลับไปดูส่วนที่เกี่ยวข้องข้างต้น