ข้ามไปยังเนื้อหาหลัก

การทดลองระดับ Utility-Scale ครั้งที่ 1

หมายเหตุ

Tamiya Onodera (5 กรกฎาคม 2024)

ดาวน์โหลด pdf ของเลคเชอร์ต้นฉบับ โปรดทราบว่าโค้ดบางส่วนอาจล้าสมัย เนื่องจากเป็นภาพนิ่ง

เวลา QPU โดยประมาณสำหรับการทดลองนี้คือ 45 วินาที

1. บทนำสู่ utility paper

ในบทเรียนนี้ เราจะรัน Circuit ระดับ utility-scale ที่ปรากฏในสิ่งที่เราเรียกกันอย่างไม่เป็นทางการว่า "utility paper" ซึ่งตีพิมพ์ใน Nature เล่มที่ 618 วันที่ 15 มิถุนายน 2023 บทความนี้กล่าวถึงวิวัฒนาการตามเวลาของแบบจำลอง Ising แบบสองมิติในสนามขวาง โดยเฉพาะอย่างยิ่งพวกเขาพิจารณา dynamics ตามเวลาของ Hamiltonian

H=HZZ+HX=J(i,j)ZiZj+hiXiH = H_{ZZ} + H_X = - J \sum_{(i,j)} Z_i Z_j + h \sum_{i} X_i

โดยที่ J>0J > 0 คือการคัปปลิ้งของสปินที่อยู่ติดกันที่ i<ji < j และ hh คือสนามขวางแบบ global พวกเขาจำลอง spin dynamics จากสถานะเริ่มต้นโดยใช้ first-order Trotter decomposition ของ time-evolution operator

exp(iHZZδt)=(i,j)exp(iJδtZiZj)=(i,j)RZiZj(2Jδt)exp(iHXδt)=iexp(ihδtXi)=iRXi(2hδt)\begin{aligned} \exp(-i H_{ZZ} \delta t) &= \prod_{(i,j)} \exp (i J \delta t Z_i Z_j) = \prod_{(i,j)} \mathrm{R}_{Z_i Z_j} ( - 2 J \delta t) \\ \exp(-i H_X \delta t) &= \prod_{i} \exp (-i h \delta t X_i ) = \prod_{i} \mathrm{R}_{X_i} ( 2 h \delta t) \end{aligned}

โดยที่เวลาวิวัฒนาการ TT ถูกแบ่งเป็น T/δtT / \delta t Trotter steps และ RZiZj(θJ)\mathrm{R}_{Z_i Z_j}(\theta_J) และ RXi(θh)\mathrm{R}_{X_i}(\theta_h) คือ ZZZZ และ XX rotation Gate ตามลำดับ

พวกเขารันการทดลองบน IBM Quantum® Eagle processor ซึ่งเป็นอุปกรณ์ 127 Qubit ที่มีการเชื่อมต่อแบบ heavy-hex โดยใช้ XX interactions กับ Qubit ทั้งหมด และ ZZZZ interactions สำหรับทุกขอบของ coupling map โปรดทราบว่า ZZZZ interactions ทั้งหมดไม่สามารถใช้พร้อมกันได้เนื่องจาก "data dependance" ดังนั้นพวกเขาจึงจัดสีให้ coupling map เพื่อจัดกลุ่มเป็น layer ต่าง ๆ โดย layer เดียวกันจะได้รับสีเดียวกันและสามารถรันพร้อมกันได้

นอกจากนี้ เพื่อความง่ายในการทดลอง พวกเขาเน้นที่กรณี θJ=π/2\theta_J=-\pi /2

ผลงานสำคัญของบทความนี้คือพวกเขาสร้าง quantum circuit ในขนาดที่เกินความสามารถของ statevector simulation รันบนคอมพิวเตอร์ควอนตัมที่มีนอยส์ และประสบความสำเร็จในการดึงผลลัพธ์ที่เชื่อถือได้ นั่นคือพวกเขาแสดงให้เห็นถึงประโยชน์ของคอมพิวเตอร์ควอนตัมที่มีนอยส์ ในการทำเช่นนี้ พวกเขาใช้ zero-noise extrapolation (ZNE) ร่วมกับ probabilistic error amplification (PEA) เพื่อลดความผิดพลาดจากอุปกรณ์ที่มีนอยส์

ตั้งแต่นั้นมา เราเรียกการทดลองและ Circuit ลักษณะนี้ว่า "utility-scale"

1.1 เป้าหมายของคุณ

เป้าหมายในบทเรียนนี้คือการสร้าง Circuit ระดับ utility-scale และรันบน Eagle processor การดึงผลลัพธ์ที่เชื่อถือได้นั้นอยู่นอกขอบเขตของ notebook นี้ ส่วนหนึ่งเพราะ PEA เป็นฟีเจอร์ทดลองใน Qiskit ณ ขณะที่เขียน และส่วนหนึ่งเพราะการใช้ ZNE กับ PEA จะใช้เวลามาก

โดยเฉพาะ คุณจะต้องสร้างและรัน Circuit ที่ตรงกับ Figure 4b ของบทความ และพล็อตจุด "unmitigated" ของตัวเอง ดังที่เห็น นี่คือ Circuit ขนาด 127 Qubit × 60 layer (20 Trotter steps) โดยมี Z62\langle Z_{62} \rangle เป็น observable image.png ฟังดูยาก?   ไม่ต้องกังวล สามบทเรียนสุดท้ายของคอร์สนี้จะเป็นขั้นตอนนำทาง เราจะเริ่มสาธิตการทดลองขนาดเล็กกว่า ซึ่งเป็นการสร้างและรันบน fake device ซึ่งเป็น Circuit ขนาด 27 Qubit × 6 layer (2 Trotter steps) โดยมี Z13\langle Z_{13} \rangle เป็น observable

เพียงเท่านี้สำหรับบทนำ ขอให้สนุกกับการผจญภัยระดับ utility-scale!

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime rustworkx
import qiskit

qiskit.__version__
'2.0.2'
#!pip install qiskit_ibm_runtime
#!pip install qiskit_aer
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx

from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import YGate
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import (
QiskitRuntimeService,
fake_provider,
EstimatorV2 as Estimator,
)
from qiskit_aer import AerSimulator
service = QiskitRuntimeService()

2. การเตรียมการ

2.1 สร้าง RZZ(-π\pi / 2)

ก่อนอื่น สังเกตว่า RZZ gate โดยทั่วไปต้องใช้ CXCX gate สองตัว

from qiskit.circuit.library import RZZGate

θ_h = Parameter("$\\theta_h$")
qc1 = QuantumCircuit(2)
qc1.append(RZZGate(θ_h), [0, 1])
qc1.decompose(reps=1).draw("mpl")

Output of the previous code cell

ดังที่กล่าวไว้ข้างต้น เราเน้นที่ RZZ gate ที่มีมุมเฉพาะ -π\pi / 2 สำหรับการทดลองนี้ ดังที่แสดงในบทความ สามารถสร้างได้ด้วย CXCX gate เพียงตัวเดียว

qc2 = QuantumCircuit(2)

qc2.sdg([0, 1])
qc2.append(YGate().power(1 / 2), [1])
qc2.cx(0, 1)
qc2.append(YGate().power(1 / 2).adjoint(), [1])

qc2.draw("mpl")

Output of the previous code cell

เราสร้าง Gate จาก Circuit นี้สำหรับการอ้างอิงในอนาคต

rzz = qc2.to_gate(label="RZZ")

ลองใช้ rzz ที่สร้างขึ้นใหม่แบบสุ่ม

qc3 = QuantumCircuit(3)
qc3.append(rzz, [0, 1])
qc3.append(rzz, [0, 2])
display(qc3.draw("mpl"))
# display(qc.decompose(reps=1).draw("mpl"))

Output of the previous code cell

ก่อนนำไปใช้ต่อ ลองตรวจสอบความเท่าเทียมเชิงตรรกะของ qc1 (RZZ gate) สำหรับ -pi/2 กับ rzz หรือ qc2 gate ที่สร้างขึ้น:

from qiskit.quantum_info import Operator

op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)

op1.equiv(op2)
True

2.2 การจัดสีให้ coupling map

ลองศึกษาวิธีจัดสีให้ coupling map ของ Backend ซึ่งจำเป็นสำหรับการจัดกลุ่ม ZZZZ interactions เป็น layer

เริ่มต้น ลองแสดงภาพ coupling map ของ Backend โปรดทราบว่า coupling map ของอุปกรณ์ IBM Quantum ปัจจุบันทุกตัวเป็นแบบ heavy-hexagonal

backend = service.least_busy(operational=True, simulator=False)

backend.coupling_map.draw()

Output of the previous code cell

สำหรับการจัดสี coupling map เราใช้ rustworkx ซึ่งเป็น Python package สำหรับทำงานกับกราฟและ complex networks โดยมีอัลกอริทึมการจัดสีหลายแบบ ซึ่งล้วนเป็น heuristic และจึงไม่รับประกันว่าจะหาการจัดสีที่น้อยที่สุด

เนื่องจาก heavy-hex graph เป็น bipartite เราจึงเลือกใช้ graph_bipartite_edge_color ซึ่งควรหาการจัดสีที่น้อยที่สุดสำหรับกราฟเหล่านี้

def color_coupling_map(backend):
graph = backend.coupling_map.graph
undirected_graph = graph.to_undirected(multigraph=False)
edge_color_map = rx.graph_bipartite_edge_color(undirected_graph)
if edge_color_map is None:
edge_color_map = rx.graph_greedy_edge_color(undirected_graph)
# build a map from color to a list of edges
edge_index_map = undirected_graph.edge_index_map()
color_edges_map = {color: [] for color in edge_color_map.values()}
for edge_index, color in edge_color_map.items():
color_edges_map[color].append(
(edge_index_map[edge_index][0], edge_index_map[edge_index][1])
)
return edge_color_map, color_edges_map

Heavy-hexagonal graph ควรถูกทาสีสามสี ลองตรวจสอบสิ่งนี้กับ coupling map ข้างต้น

edge_color_map, color_edges_map = color_coupling_map(backend)
print(
f"{backend.name}, {backend.num_qubits}-qubit device, {len(color_edges_map.keys())} colors assigned."
)
ibm_strasbourg, 127-qubit device, 3 colors assigned.

ใช่แล้ว!

เพื่อความสนุก ลองทาสี coupling map ตามการจัดสีที่ได้ โดยใช้ฟีเจอร์ visualization ของ rustworkx

color_str_map = {0: "green", 1: "red", 2: "blue"}

undirected_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
for i in undirected_graph.edge_indices():
undirected_graph.get_edge_data_by_index(i)["color"] = color_str_map[
edge_color_map[i]
]

rx.visualization.graphviz_draw(
undirected_graph, method="neato", edge_attr_fn=lambda edge: {"color": edge["color"]}
)

Output of the previous code cell

3. แก้ปัญหาวิวัฒนาการตามเวลาแบบ Trotterized ของแบบจำลอง Ising แบบ 2D

ลองสร้าง routine เพื่อสร้าง Circuit ของ utility paper สำหรับวิวัฒนาการตามเวลาของแบบจำลอง Ising แบบ 2D โดย routine รับสามพารามิเตอร์ ได้แก่ Backend จำนวนเต็มที่บอกจำนวน Trotter steps และค่า Boolean ที่ควบคุมการแทรก barrier

def get_utility_circuit(backend, num_steps: int, barrier: bool = False):
num_qubits = backend.num_qubits
_, color_edges_map = color_coupling_map(backend)
θ_h = Parameter("$\\theta_h$")
qc = QuantumCircuit(num_qubits)

for i in range(num_steps):
qc.rx(θ_h, range(num_qubits))

for _, edge_list in color_edges_map.items():
for edge in edge_list:
qc.append(rzz, edge)

if barrier:
qc.barrier()
return qc

โปรดทราบว่าเราได้ทำ qubit mapping และ routing ของ Circuit ที่สร้างขึ้นเองแล้ว ดังนั้นเมื่อเรา transpile Circuit ในภายหลัง เราจะไม่ (และ__ไม่ควร__) ขอให้ Transpiler ทำ qubit mapping และ routing ดังที่คุณจะเห็นในอีกสักครู่ เราเรียกใช้ด้วย optimization level 1 และ layout method เป็น "trivial"

ถัดไป เราสร้าง routine ง่าย ๆ เพื่อรับข้อมูลเกี่ยวกับ Circuit ที่สร้างขึ้นเพื่อตรวจสอบเบื้องต้น

def get_circuit_info(qc: QuantumCircuit, reps: int = 0):
qc0 = qc.decompose(reps=reps)
return (
f"{qc0.num_qubits} qubits × {qc0.depth(lambda x: x.operation.num_qubits == 2)} layers ({qc0.depth()}-depth)"
+ ", "
+ f"""Gate breakdown: {", ".join([f"{k.upper()} {v}" for k, v in qc0.count_ops().items()])}"""
)

ลองทดสอบ routine เหล่านี้ คุณควรเห็น Circuit ขนาด 27 Qubit × 15 layer (5 Trotter steps) เนื่องจาก fake device มี 28 ขอบ ควรมี entangling gate 28*5 ตัว

backend = fake_provider.FakeTorontoV2()
num_steps = 5
qc = get_utility_circuit(backend, num_steps, True)

display(qc.draw(output="mpl", fold=-1))
print(get_circuit_info(qc, reps=0))
print(get_circuit_info(qc, reps=1))

Output of the previous code cell

27 qubits × 15 layers (20-depth),  Gate breakdown: CIRCUIT-165 140, RX 135, BARRIER 5
27 qubits × 15 layers (60-depth), Gate breakdown: SDG 280, UNITARY 280, CX 140, R 135, BARRIER 5

4. แก้ปัญหาเวอร์ชัน 27 Qubit

เราจะสาธิตการทดลองขนาดเล็กกว่าของ utility experiment เราสร้าง Circuit ขนาด 27 Qubit × 6 layer (2 Trotter steps) โดยมี Z13\langle Z_{13} \rangle เป็น observable และรันทั้งบน AerSimulator และ fake device

แน่นอน เราทำตาม workflow สี่ขั้นตอน "Qiskit pattern" ซึ่งประกอบด้วย Map, Optimize, Execute และ Post-Process โดยเฉพาะ:

  • แมป input แบบคลาสสิกไปยังการคำนวณเชิงควอนตัม
  • ปรับปรุง Circuit สำหรับการคำนวณเชิงควอนตัม
  • รัน Circuit โดยใช้ primitive
  • ประมวลผลและส่งคืนผลลัพธ์ในรูปแบบคลาสสิก

ด้านล่าง เรามีขั้นตอน Map สำหรับสร้าง Circuit ของการทดลองขนาดเล็ก จากนั้นมีชุด Optimize และ Execute ชุดหนึ่งสำหรับ AerSimulator และอีกชุดสำหรับ fake device สุดท้ายมีขั้นตอน Post-Process เพื่อพล็อตผลลัพธ์

4.1 ขั้นตอนที่ 1: Map

backend = fake_provider.FakeTorontoV2()  # a 27 qubit fake device.
num_steps = 2
qc = get_utility_circuit(backend, num_steps)
obs = SparsePauliOp.from_sparse_list(
[("Z", [13], 1)], num_qubits=backend.num_qubits
) # Falcon
angles = [
0,
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
1.0,
np.pi / 2,
] # We try 11 angles for theta_h.

4.2 ขั้นตอนที่ 2 และ 3: Optimize และ Execute (Simulator)

backend_sim = AerSimulator()
transpiled_qc_sim = transpile(
qc, backend_sim, optimization_level=1, layout_method="trivial"
)
transpiled_obs_sim = obs.apply_layout(layout=transpiled_qc_sim.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_sim, reps=1))
27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (16-depth), Gate breakdown: U3 80, CX 56, R 54, U1 32, U 28

ผู้ใช้รายหนึ่งรัน cell ถัดไปบน MacBook Pro พร้อม Intel Core i7 Processor 2.3 GHz quad-core พร้อม RAM 32GB 3LPDDR4X ที่รัน macOS 14.5 ใช้เวลา wall time 161ms แต่ละเครื่องจะแตกต่างกันเล็กน้อย

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_sim)
pub = (transpiled_qc_sim, transpiled_obs_sim, params)
result_sim = estimator.run([pub]).result()
CPU times: user 231 ms, sys: 186 ms, total: 417 ms
Wall time: 111 ms

4.3 ขั้นตอนที่ 2 และ 3: Optimize และ Execute (fake device)

backend_fake = fake_provider.FakeTorontoV2()
transpiled_qc_fake = transpile(
qc, backend_fake, optimization_level=1, layout_method="trivial"
)
transpiled_obs_fake = obs.apply_layout(layout=transpiled_qc_fake.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_fake, reps=1))
27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (49-depth), Gate breakdown: SDG 324, U1 274, H 162, CX 56, U3 14

เมื่อผู้ใช้รายเดียวกันรัน cell ถัดไปในสภาพแวดล้อมเดียวกัน ใช้เวลา wall time 2 นาที 19 วินาที การรัน Circuit บน fake device เรียกใช้การจำลองแบบมีนอยส์ซึ่งใช้เวลานานกว่าการจำลองแบบ exact มาก เราแนะนำว่าอย่ารัน Circuit ขนาดใหญ่กว่า (เช่น 27 Qubit × 9 layer พร้อม 3 Trotter steps) บน fake device

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_fake)
pub = (transpiled_qc_fake, transpiled_obs_fake, params)
result_fake = estimator.run([pub]).result()
CPU times: user 4min 42s, sys: 9.35 s, total: 4min 51s
Wall time: 38.3 s

4.4 ขั้นตอนที่ 4: Post-process

เราพล็อตผลลัพธ์จากการจำลองแบบ exact และมีนอยส์ คุณจะเห็นผลกระทบจากนอยส์ที่รุนแรงบน FakeToronto

plt.plot(angles, result_fake[0].data.evs, "o", label="Fake Device")
plt.plot(angles, result_sim[0].data.evs, "o", label="AerSimulator")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{13} \\rangle$")
plt.legend()
plt.show()

Output of the previous code cell

5. แก้ปัญหาเวอร์ชัน 127 Qubit

เป้าหมายของคุณคือการรัน utility-scale experiment ตามที่กล่าวไว้ในตอนต้น คุณจะสร้างและรัน Circuit ขนาด 127 Qubit และ 60 layer (20 Trotter steps) โดยมี Z62\langle Z_{62} \rangle เป็น observable เราแนะนำให้ลองทำเองโดยใช้โค้ดเวอร์ชัน 27 Qubit เมื่อเหมาะสม แต่มีเฉลยให้ที่นี่

เฉลย:

5.1 ขั้นตอนที่ 1: Map

# backend_map = service.backend("ibm_brisbane")
backend_map = service.least_busy(operational=True, simulator=False)

num_steps = 20
qc = get_utility_circuit(backend_map, num_steps)
obs = SparsePauliOp.from_sparse_list(
[("Z", [62], 1)], num_qubits=backend_map.num_qubits
) # Eagle
angles = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, np.pi / 2]

5.2 ขั้นตอนที่ 2 และ 3: Optimize และ Execute

โปรดทราบว่า coupling map ของ Eagle processor มี 144 ขอบ

# backend = service.backend("ibm_brisbane")
backend = backend_map

transpiled_qc = transpile(qc, backend, optimization_level=1, layout_method="trivial")
transpiled_obs = obs.apply_layout(layout=transpiled_qc.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc))
156 qubits × 60 layers (221-depth),  Gate breakdown: SDG 7040, UNITARY 7040, CX 3520, R 3120
156 qubits × 60 layers (201-depth), Gate breakdown: RZ 11933, SX 6240, CZ 3520
params = [[p] for p in angles]
estimator = Estimator(mode=backend)
pub = (transpiled_qc, transpiled_obs, params)
job = estimator.run([pub])

job_id = job.job_id()
print(f"job id={job_id}")
job id=d1479n6qf56g0081sxa0

5.3 Post-process

เราให้ค่าสำหรับจุด "mitigated" ใน Figure 4b ของ utility paper พล็อตค่าเหล่านี้ร่วมกับผลลัพธ์ของคุณ

result_paper = [
1.0171,
1.0044,
0.9563,
0.9602,
0.8394,
0.8120,
0.5466,
0.4556,
0.1953,
0.0141,
0.0117,
]

# REPLACE WITH YOUR OWN JOB ID
job = service.job(job_id)

plt.plot(angles, job.result()[0].data.evs, "o", label=f"{job.backend().name}")
plt.plot(angles, result_paper, "o", label="Utility Paper")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{62} \\rangle$")
plt.legend()
plt.show()

Output of the previous code cell

ผลลัพธ์ของคุณคล้ายกับ "unmitigated" ใน Figure 4b ไหม?   อาจแตกต่างกันมาก ขึ้นอยู่กับอุปกรณ์และสภาพของมันในขณะทดลอง ไม่ต้องกังวลกับผลลัพธ์ สิ่งที่เราจะตรวจสอบคือว่าคุณทำโค้ดได้ถูกต้องหรือไม่ ถ้าทำได้ ขอแสดงความยินดี คุณได้ก้าวถึงจุดเริ่มต้นของยุค utility แล้ว

เช่นเดียวกับใน Utility paper นักวิทยาศาสตร์ทั่วโลกได้ทุ่มเทความคิดสร้างสรรค์อย่างมากในการดึงผลลัพธ์ที่มีความหมายแม้จะมีนอยส์ เป้าหมายสูงสุดของความพยายามร่วมกันนี้คือ quantum advantage: สถานะที่คอมพิวเตอร์ควอนตัมสามารถแก้ปัญหาบางอย่างที่มีประโยชน์ในอุตสาหกรรมได้เร็วกว่า แม่นยำกว่า หรือถูกกว่าคอมพิวเตอร์คลาสสิก สิ่งนี้ไม่น่าจะเป็นเหตุการณ์เดียว แต่เป็นยุคสมัยที่การจำลองผลลัพธ์ควอนตัมแบบคลาสสิกใช้เวลานานขึ้นเรื่อย ๆ จนกว่าจะถึงจุดที่ความได้เปรียบของควอนตัมนั้นมีความสำคัญอย่างยิ่ง สิ่งหนึ่งที่ชัดเจนเกี่ยวกับ quantum advantage: เราจะไปถึงจุดนั้นได้ก็ต่อเมื่อผ่านการทดลองระดับ utility-scale เท่านั้น หากคอร์สนี้ทำให้คุณเข้าร่วมการแสวงหาที่เต็มไปด้วยความท้าทายและความสนุก เราจะยินดีมาก

อ้างอิง

Source: IBM Quantum docs — updated 15 ม.ค. 2569
English version on doQumentation — updated 7 พ.ค. 2569
This translation based on the English version of approx. 26 มี.ค. 2569