Skip to main content
IBM Quantum Platform

유틸리티 규모 실험 I

Note

타미야 오노데라 (2024년 7월 5일)

원본 강의의 PDF를 다운로드하세요. 일부 코드 스니펫은 정적 이미지이므로 더 이상 사용되지 않을 수 있습니다.

이 실험을 실행하는 데 걸리는 대략적인 QPU 시간은 45초입니다.


1. 실용 논문 소개

이 단원에서는 2023년 6월 15일자 Nature 618호에 실린 '유틸리티 논문' 에 등장하는 유틸리티 규모의 회로를 실행해봅니다. 이 논문은 2D 횡장 아이싱 모델의 시간 진화에 대해 다룹니다. 특히 해밀턴의 시간 역학을 고려합니다,

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 > 0i<ji < j 과의 가장 가까운 이웃 스핀의 결합이고 hh 은 글로벌 횡단 필드입니다.

시간 진화 연산자의 일차 트로터 분해를 통해 초기 상태의 스핀 역학을 시뮬레이션합니다,

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}

에서 진화 시간 TTT/δtT / \delta t 트로터 스텝으로 이산화되고 RZiZj(θJ)\mathrm{R}_{Z_i Z_j}(\theta_J)RXi(θh)\mathrm{R}_{X_i}(\theta_h) 는 각각 ZZZZXX 로테이션 게이트입니다.

연구진은 헤비헥스 연결성을 갖춘 127큐비트 장치인 IBM Quantum® Eagle 프로세서에서 모든 큐비트에 XX 상호작용을, 커플링 맵의 모든 에지에 ZZZZ 상호작용을 적용하여 실험을 진행했습니다. '데이터 종속성'으로 인해 모든 ZZZZ 상호 작용을 동시에 적용할 수 없습니다. 따라서 커플링 맵에 색상을 지정하여 레이어로 그룹화합니다. 레이어에 있는 것들은 동일한 색상이 할당되며 병렬로 적용할 수 있습니다.

또한 실험의 단순성을 위해 θJ=π/2\theta_J=-\pi /2 사례에 집중했습니다.

이 논문의 새로운 공헌은 상태 벡터 시뮬레이션을 넘어서는 규모의 양자 회로를 구축하고 잡음이 많은 양자 컴퓨터에서 실행하여 신뢰할 수 있는 결과를 추출하는 데 성공했다는 점입니다. 즉, 노이즈가 많은 양자 컴퓨터의 유용성을 입증한 것입니다. 이 과정에서 노이즈가 많은 기기로 인한 오류를 완화하기 위해 확률적 오류 증폭(PEA)과 함께 제로 노이즈 외삽법(ZNE)을 적용했습니다.

그때부터 우리는 이러한 실험과 회로를 '유틸리티 스케일'이라고 불렀습니다.

1.1 당신의 목표

이 레슨의 목표는 유틸리티 규모의 회로를 제작하고 이를 Eagle 프로세서에서 실행하는 것입니다. 부분적으로는 PEA가 현재 키스킷의 실험적인 기능이고, 부분적으로는 ZNE를 PEA와 함께 적용하는 데 상당한 시간이 걸리기 때문에 신뢰할 수 있는 결과를 추출하는 것은 이 노트의 범위를 벗어납니다.

구체적으로, 논문의 그림 4b 에 해당하는 회로를 만들어 실행하고, '완화되지 않은' 점을 직접 그려보라는 요청을 받습니다. 보시다시피, 이것은 관찰 가능한 것으로 Z62\langle Z_{62} \rangle 을 사용하는 127-큐비트 ×\times 60층(트로터 스텝 20개) 회로입니다.

image.png

큰 문제처럼 들리나요? 걱정하지 마십시오. 이 과정의 마지막 세 강의는 디딤돌을 제공합니다. 우선, 가짜 장치에서 27큐비트 ×\times 6층(트로터 스텝 2개) 회로를 구축하고 실행하는 소규모 실험을 시연하고 Z13\langle Z_{13} \rangle 을 관측 장치로 사용합니다.

소개는 여기까지입니다. 유틸리티 규모의 모험을 떠나보겠습니다!

import qiskit

qiskit.__version__

Output:

'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 게이트에는 두 개의 CXCX 게이트가 필요하다는 것을 관찰하세요.

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:

Output of the previous code cell

위에서 언급했듯이, 이 실험에서는 특정 각도( π\pi / 2)의 RZZ 게이트에 초점을 맞춥니다. 백서에서 볼 수 있듯이 CXCX 게이트 하나만으로 구현할 수 있습니다.

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:

Output of the previous code cell

나중에 참조할 수 있도록 이 회로의 관점에서 게이트를 정의합니다.

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:

Output of the previous code cell

이를 더 사용하기 전에 -pi/2 및 새로 정의한 rzz 또는 qc2 게이트에 대한 qc1 (RZZ 게이트)의 논리적 동등성을 확인해 보겠습니다:

from qiskit.quantum_info import Operator

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

op1.equiv(op2)

Output:

True

2.2 커플링 맵 채우기

백엔드의 커플링 맵에 색상을 지정하는 방법에 대해 알아보겠습니다. 이는 ZZZZ 상호작용을 레이어로 그룹화하는 데 필요합니다.

먼저 백엔드의 커플링 맵을 시각화해 보겠습니다. 현재 모든 IBM 퀀텀 디바이스의 커플링 맵은 육각형으로 되어 있습니다.

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

backend.coupling_map.draw()

Output:

Output of the previous code cell

커플링 맵에 색을 입히기 위해 그래프와 복잡한 네트워크 작업을 위한 Python 패키지인 rustworkx를 사용합니다. 여러 색상 지정 알고리즘을 제공하지만 모두 휴리스틱 방식이므로 최소한의 색상을 찾는다는 보장은 없습니다.

이렇게 말했듯이, 굵은 육각형 그래프는 이분 그래프이므로, 이러한 그래프에 대한 최소 색상을 찾아야 하는 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

육각형 그래프는 세 가지 색상으로 칠해야 합니다. 위의 커플링 맵에서 이를 확인해 보겠습니다.

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."
)

Output:

ibm_strasbourg, 127-qubit device, 3 colors assigned.

네, 맞습니다!

재미를 위해 러스트웍스 시각화 기능을 사용하여 얻은 색상에 커플링 맵을 칠해 보겠습니다.

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:

Output of the previous code cell

3. 이중 자유도( 2D ) 이징 모델의 트로터화 시간 진화 방정식을 풀기.

2D Ising 모델의 시간 진화에 대한 유틸리티 페이퍼의 회로를 구성하는 루틴을 정의해 보겠습니다. 이 루틴은 백엔드, 트로터 단계 수를 나타내는 정수, 배리어 삽입을 제어하는 부울의 세 가지 파라미터를 사용합니다.

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

이미 구성된 회로에 대해 큐비트 매핑 및 라우팅을 수동으로 수행했음을 참고하시기 바랍니다. 따라서 나중에 회로를 트랜스파일링할 때 트랜스파일러에 큐비트 매핑과 라우팅을 요청하지 않습니다(하지 않아야 합니다). 곧 보게 되겠지만, 최적화 수준을 1로 설정하고 레이아웃 방법을 "trivial"로 설정하여 호출합니다.

다음으로 빠른 확인을 위해 구성된 회로에 대한 정보를 가져오는 간단한 루틴을 정의합니다.

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()])}"""
    )

이 루틴을 연습해 보겠습니다. 27 큐비트 ×\times 15 레이어(5 트로터 단계)의 회로가 표시되어야 합니다. 가짜 장치에는 28개의 가장자리가 있으므로 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:

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큐비트 버전의 문제를 해결하라.

이제 소규모 버전의 유틸리티 실험을 시연합니다. Z13\langle Z_{13} \rangle 을 옵저버블로 사용하여 27-큐비트 ×\times 6계층(2 트로터 스텝) 회로를 구축하고 AerSimulator 과 가짜 디바이스에서 실행합니다.

물론 맵, 최적화, 실행, 사후 처리로 구성된 4단계 워크플로인 '키스킷 패턴'을 따르고 있습니다. 좀 더 구체적으로 설명하자면,

  • 기존 입력을 양자 계산에 매핑합니다.
  • 양자 계산을 위해 회로를 최적화하세요.
  • 프리미티브를 사용하여 회로를 실행합니다.
  • 클래식 형식으로 결과를 사후 처리하고 반환합니다.

다음은 소규모 실험을 위한 회로를 만들기 위한 지도 단계입니다. 그런 다음 AerSimulator 에 대한 최적화 및 실행 세트와 가짜 장치에 대한 최적화 및 실행 세트가 하나씩 있습니다. 마지막으로 결과를 플로팅하는 포스트 프로세스 단계가 있습니다.

4.1 1단계: 지도

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: 최적화 및 실행 (시뮬레이터)

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

Output:

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

한 사용자는 2.3 GHz 쿼드 코어 인텔 코어 i7 프로세서와 32GB 3LPDDR4X RAM이 장착된 MacBook Pro를 사용하여 다음 셀을 실행했으며, 이 셀은 macOS 14.5. 벽 시간으로 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()

Output:

CPU times: user 231 ms, sys: 186 ms, total: 417 ms
Wall time: 111 ms

4.3 단계 2와 3: 최적화 및 실행 (가상 기기)

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

Output:

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

동일한 사용자가 위와 동일한 환경에서 다음 셀을 실행했을 때, 월 타임 기준 2min 19s 시간이 걸렸습니다. 가짜 장치에서 회로를 실행하면 노이즈 시뮬레이션이 호출되어 정확한 시뮬레이션보다 훨씬 더 오랜 시간이 걸립니다. 가짜 디바이스에서는 더 큰 회로(예: 27쿼비트 ×\times 9레이어에 트로터 스텝 3개)를 실행하지 않는 것이 좋습니다.

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

Output:

CPU times: user 4min 42s, sys: 9.35 s, total: 4min 51s
Wall time: 38.3 s

4.4 4단계: 후처리

정확하고 노이즈가 적은 시뮬레이션의 결과를 플로팅합니다. 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:

Output of the previous code cell

5. 127큐비트 버전의 문제를 해결하라

처음에 언급한 대로 유틸리티 규모의 실험을 실행하는 것이 목표입니다. Z62\langle Z_{62} \rangle 을 옵저버블로 사용하여 127-큐비트, 60-레이어(트로터 스텝 20개) 회로를 생성하고 실행합니다. 적절한 경우 27-큐비트 버전의 코드를 사용하여 직접 이 작업을 수행하는 것이 좋습니다. 하지만 여기에 해결책이 있습니다.

해결 방법:

5.1 1단계: 지도

# 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: 최적화 및 실행

Eagle 프로세서의 커플링 맵에는 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))

Output:

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}")

Output:

job id=d1479n6qf56g0081sxa0

5.3 후처리

유틸리티 백서의 그림 4b 에서 '완화'된 포인트의 값을 확인할 수 있습니다. 이를 결과와 함께 플로팅하세요.

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:

Output of the previous code cell

결과가 그림 4b 의 "완화되지 않음"과 비슷합니까? 실험 당시의 기기 및 상태에 따라 매우 다를 수 있습니다. 결과 자체에 대해서는 걱정하지 마세요. 코딩을 올바르게 수행했는지 확인합니다. 그렇게 하셨다면, 유틸리티 시대의 출발선에 도달하신 것을 축하드립니다.

유틸리티 논문에서와 같이 전 세계의 과학자들은 노이즈가 있는 상황에서도 의미 있는 결과를 도출하기 위해 엄청난 창의력을 발휘했습니다. 이러한 공동 노력의 최종 목표는 양자 우위, 즉 양자 컴퓨터가 기존 컴퓨터보다 더 빠르고, 더 충실하게, 더 저렴하게 산업에서 사용되는 일부 문제를 해결할 수 있는 상태입니다. 이는 단발적인 사건이 아니라 양자 결과의 고전적 재현이 점점 더 오래 걸리는 시대가 될 것이며, 어느 시점에서는 양자 리드 타임이 매우 중요해질 것입니다. 양자 우위에 대해 한 가지 분명한 것은 유틸리티 규모의 실험을 통해서만 얻을 수 있다는 것입니다. 이 과정을 통해 도전과 재미로 가득한 퀘스트에 참여하게 된다면 더할 나위 없이 기쁠 것입니다.


참조

이 페이지가 도움이 되었습니까?
GitHub에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.