AQC-Tensor Qiskit 애드온으로 Circuit 깊이 줄이기
이 노트북에서는 tensor 네트워크를 사용한 근사 양자 컴파일(AQC-Tensor) 을 활용하여 일반적으로 Trotter 시간 발전을 수행하는 데 필요한 것보다 더 낮은 Circuit 깊이를 달성하면서 Qiskit 패턴의 각 단계를 차례로 진행합니다.
다음과 같은 단계를 진행합니다:
- 1단계: 양자 문제로 매핑
- 문제의 Hamiltonian과 관측량(observable)을 초기화합니다
- Circuit의 초기 부분에 대한 목표 tensor 네트워크 상태를 생성합니다
- 압축될 부분을 근사하는 낮은 깊이의 Circuit을 생성합니다
- 해당 Circuit으로부터 일반적인 ansatz를 생성합니다
- ansatz가 목표에 최대한 가까워지도록 매개변수를 최적화합니다
- 최적화된 ansatz에 이후의 Trotter 단계를 추가합니다
- 2단계: 목표 하드웨어에 맞게 최적화
- 하드웨어용 Circuit을 Transpile합니다
- 3단계: 실험 실행
- 간편함을 위해 가짜 Backend를 사용합니다
- 4단계: 결과 재구성
- 해당 없음; 대신 측정된 관측량만 출력합니다
1단계: 양자 Circuit 및 연산자로 매핑
모델 Hamiltonian과 관측량 설정
이 노트북에서는 10개 사이트의 원 위에서 Ising 모델을 사용합니다:
여기서 주기적 경계 조건에 의해 일 때 이 되고, 는 두 사이트 간의 결합 강도, 는 외부 자기장입니다.
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-addon-aqc-tensor qiskit-addon-utils qiskit-ibm-runtime quimb scipy
from qiskit.transpiler import CouplingMap
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_heavy_hex(3, bidirectional=False)
# Choose a 10-qubit circle on this coupling map
reduced_coupling_map = coupling_map.reduce([0, 13, 1, 14, 10, 16, 4, 15, 3, 9])
# Get a qubit operator describing the Ising field model
hamiltonian = generate_xyz_hamiltonian(
reduced_coupling_map,
coupling_constants=(0.0, 0.0, 1.0),
ext_magnetic_field=(0.4, 0.0, 0.0),
)
측정할 관측량은 총 자화(total magnetization)입니다.
from qiskit.quantum_info import SparsePauliOp
L = reduced_coupling_map.size()
observable = SparsePauliOp.from_sparse_list([("Z", [i], 1 / L / 2) for i in range(L)], num_qubits=L)
고전적으로 시뮬레이션할 시간 발전의 범위 결정
전체적인 목표는 위 모델 Hamiltonian의 시간 발전을 시뮬레이션하는 것입니다. 이를 Trotter 시간 발전으로 수행하며, 두 부분으로 나눕니다:
- 행렬곱 상태(MPS, matrix product states)로 시뮬레이션 가능한 초기 부분. 이 부분은 https://arxiv.org/abs/2301.08609 에 제시된 바와 같이 AQC를 사용하여 "컴파일"합니다.
- 하드웨어에서 실행될 Circuit의 이후 부분. AQC-Tensor를 사용해 시간 까지의 시간 발전 Circuit을 압축한 다음, 까지는 일반적인 Trotter 단계로 발전시키는 계획을 세워봅시다.
분할 전후의 Circuit 생성
이제 에서 분할하기로 결정했으므로, 두 개의 Circuit을 생성합니다:
- 에서 까지 발전의 AQC 부분에 대한 "목표" Circuit. 이는 tensor 네트워크 시뮬레이터로 시뮬레이션되기 때문에, 레이어 수는 실행 시간에 상수 배 만큼만 영향을 주므로 Trotter 오차를 최소화하기 위해 넉넉한 수의 레이어를 사용하는 것이 좋습니다.
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit
aqc_evolution_time = 4.0
aqc_target_num_trotter_steps = 45
aqc_target_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
time=aqc_evolution_time,
)
- 에서 까지 발전하는 후속 발전 Circuit. 이는 양자 하드웨어에서 실행되므로 Trotter 레이어를 가능한 한 적게 사용하는 것이 바람직합니다.
subsequent_evolution_time = 1.0
subsequent_num_trotter_steps = 5
subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
time=subsequent_evolution_time,
)
나중에 비교할 수 있도록 세 번째 Circuit도 생성합니다: aqc_evolution_time 동안 발전하지만 Trotter 단계당 발전 시간이 후속 Circuit과 동일한 Circuit입니다. 이는 목표 Circuit에 넉넉한 수의 Trotter 단계를 사용하지 않았다면 사용하게 되었을 Circuit입니다. 이를 비교 Circuit(comparison circuit) 이라 부릅니다.
aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps / subsequent_evolution_time * aqc_evolution_time
)
aqc_comparison_num_trotter_steps
20
comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)
더 적은 단계의 Trotter Circuit으로부터 ansatz와 초기 매개변수 생성
먼저, 목표 Circuit과 동일한 발전 시간을 가지지만 더 적은 Trotter 단계(따라서 더 적은 레이어)를 가지는 "좋은" Circuit을 구성합니다.
그런 다음 이 "좋은" Circuit을 AQC-Tensor의 generate_ansatz_from_circuit 함수에 전달합니다. 이 함수는 Circuit의 2-Qubit 연결성을 분석하여 두 가지를 반환합니다:
- 입력 Circuit과 동일한 2-Qubit 연결성을 가진 일반적인 매개변수화된 ansatz Circuit; 그리고,
- ansatz에 대입하면 입력("좋은") Circuit을 산출하는 매개변수.
곧 이 매개변수를 가져와 반복적으로 조정하여 ansatz Circuit을 목표 MPS에 최대한 가깝게 만들 것입니다.
from qiskit_addon_aqc_tensor import generate_ansatz_from_circuit
aqc_ansatz_num_trotter_steps = 5
aqc_good_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),
time=aqc_evolution_time,
)
aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(
aqc_good_circuit, qubits_initially_zero=True
)
aqc_ansatz.draw("mpl", fold=-1)

print(f"Comparison circuit: depth {comparison_circuit.depth()}")
print(f"Target circuit: depth {aqc_target_circuit.depth()}")
print(f"Ansatz circuit: depth {aqc_ansatz.depth()}, with {len(aqc_initial_parameters)} parameters")
Comparison circuit: depth 120
Target circuit: depth 270
Ansatz circuit: depth 23, with 515 parameters
tensor 네트워크 시뮬레이션 설정 선택
여기서는 quimb 기반의 tensor 네트워크 시뮬레이터를 사용합니다. 이 예제에서는 quimb의 행렬곱 상태(MPS) 시뮬레이터를 사용하고, 자동 미분을 위해 JAX를 사용합니다. quimb 시뮬레이터 사용 방법에 대한 자세한 내용은 API 문서를 참조하세요.
from functools import partial
import quimb.tensor
from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator
simulator_settings = QuimbSimulator(
partial(quimb.tensor.CircuitMPS, max_bond=100, cutoff=1e-8),
autodiff_backend="jax",
)
AQC 목표 상태의 행렬곱 상태 표현 구성
다음으로, AQC로 근사할 상태의 행렬곱 표현을 구축합니다.
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit
aqc_target_mps = tensornetwork_from_circuit(aqc_target_circuit, simulator_settings)
목표 상태에 넉넉한 수의 Trotter 단계를 선택했기 때문에, 실제로 비교 Circuit보다 Trotter 오차가 더 적습니다. 비교 Circuit이 준비한 상태와 목표 상태의 충실도()를 계산할 수 있습니다:
from qiskit_addon_aqc_tensor.simulation import compute_overlap
comparison_mps = tensornetwork_from_circuit(comparison_circuit, simulator_settings)
comparison_fidelity = abs(compute_overlap(comparison_mps, aqc_target_mps)) ** 2
comparison_fidelity
0.9996761790297157
MPS 계산을 사용한 ansatz 매개변수 최적화
여기서는 scipy의 L-BFGS 옵티마이저를 사용하여 가장 간단한 비용 함수인 MaximizeStateFidelity를 최소화합니다.
AQC를 사용하지 않았을 때 비교 Circuit이 도달했을 값 이상이 되도록 충실도의 중단 지점을 선택합니다. 이 값에 도달하면 압축된 Circuit은 원래 Circuit보다 Trotter 오차가 더 적고 깊이도 더 얕습니다. 처리 시간이 더 주어진다면, 충실도를 더욱 높이기 위해 추가적인 최적화 단계를 수행할 수 있습니다.
from scipy.optimize import OptimizeResult, minimize
from qiskit_addon_aqc_tensor.objective import MaximizeStateFidelity
objective = MaximizeStateFidelity(aqc_target_mps, aqc_ansatz, simulator_settings)
stopping_point = 1 - comparison_fidelity
def callback(intermediate_result: OptimizeResult):
print(f"Intermediate result: Fidelity {1 - intermediate_result.fun:.8}")
if intermediate_result.fun < stopping_point:
# Good enough for now
raise StopIteration
result = minimize(
objective.loss_function,
aqc_initial_parameters,
method="L-BFGS-B",
jac=True,
options={"maxiter": 100},
callback=callback,
)
if result.status not in (
0,
1,
99,
): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration
raise RuntimeError(f"Optimization failed: {result.message} (status={result.status})")
print(f"Done after {result.nit} iterations.")
aqc_final_parameters = result.x
Intermediate result: Fidelity 0.95080335
Intermediate result: Fidelity 0.98408927
Intermediate result: Fidelity 0.99140876
Intermediate result: Fidelity 0.9951876
Intermediate result: Fidelity 0.99563147
Intermediate result: Fidelity 0.99646297
Intermediate result: Fidelity 0.99679298
Intermediate result: Fidelity 0.99715793
Intermediate result: Fidelity 0.99756604
Intermediate result: Fidelity 0.99804283
Intermediate result: Fidelity 0.99832283
Intermediate result: Fidelity 0.99856583
Intermediate result: Fidelity 0.99868698
Intermediate result: Fidelity 0.998867
Intermediate result: Fidelity 0.99902237
Intermediate result: Fidelity 0.99912174
Intermediate result: Fidelity 0.99919705
Intermediate result: Fidelity 0.99926724
Intermediate result: Fidelity 0.99938605
Intermediate result: Fidelity 0.99951297
Intermediate result: Fidelity 0.99956172
Intermediate result: Fidelity 0.99962274
Intermediate result: Fidelity 0.99963919
Intermediate result: Fidelity 0.99967423
Intermediate result: Fidelity 0.9997101
Done after 25 iterations.
Transpiler에 전달할 최종 Circuit 구성
final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
final_circuit.compose(subsequent_circuit, inplace=True)
final_circuit.draw("mpl", fold=-1)

2단계: 목표 하드웨어에서 실행하기 위해 Transpile
Qiskit 패턴의 2단계에서는 이 Circuit과 원하는 관측량을 목표 장치에서 실행하기 위해 Transpile합니다. 여기서는 qiskit-ibm-runtime이 제공하는 가짜 Backend를 사용합니다.
from qiskit import transpile
from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2
backend = FakeMelbourneV2()
isa_circuit = transpile(final_circuit, backend)
isa_observable = observable.apply_layout(isa_circuit.layout)
그 결과로 얻은 ISA Circuit은 Backend에서 실행하기 위해 전송될 수 있습니다(Qiskit 패턴의 3단계).
3단계: 양자 하드웨어에서 실행
from qiskit_ibm_runtime import EstimatorV2 as Estimator
estimator = Estimator(backend)
job = estimator.run([(isa_circuit, isa_observable)])
pub_result = job.result()[0]
4단계: 재구성
우리의 경우 재구성이 필요하지 않습니다. 단지 결과를 확인하면 됩니다.
pub_result.data.evs[()]
np.float64(0.047998046875000006)