Перейти до основного вмісту

Зменшення глибини Circuit за допомогою аддону AQC-Tensor для Qiskit

У цьому ноутбуці ми пройдемо кроки патерну Qiskit, використовуючи наближену квантову компіляцію з тензорними мережами (AQC-Tensor), щоб досягти меншої глибини Circuit, ніж зазвичай потрібно для виконання еволюції Троттера.

Ось кроки, які ми виконаємо:

  • Крок 1: Відображення на квантову задачу
    • Ініціалізація гамільтоніана задачі та спостережуваних
    • Генерація цільового стану тензорної мережі для початкової частини Circuit
    • Генерація Circuit з малою глибиною, що апроксимує стиснуту частину
    • Генерація загального ансацу з цього Circuit
    • Оптимізація параметрів для максимального наближення ансацу до цільового стану
    • Додавання наступних кроків Троттера до оптимізованого ансацу
  • Крок 2: Оптимізація для цільового обладнання
    • Транспіляція Circuit для обладнання
  • Крок 3: Виконання експериментів
    • Використання фіктивного Backend для простоти
  • Крок 4: Реконструкція результатів
    • Н/Д; натомість ми просто виводимо виміряну спостережувану

Крок 1: Відображення на квантовий Circuit і оператор

Налаштування модельного гамільтоніана та спостережуваної

У цьому ноутбуці ми використовуємо модель Ізінга на кільці з 10 вузлів:

H^Ising=i=110Ji,(i+1)ZiZ(i+1)+hiXi,\hat{\mathcal{H}}_{\text{Ising}} = \sum_{i=1}^{10} J_{i,(i+1)} Z_i Z_{(i+1)} + h_i X_i \, ,

де граничні умови по колу означають, що при i=10i=10 отримуємо i+1=111i+1=11\rightarrow1, JJ — константа зв'язку між двома вузлами, а hh — зовнішнє магнітне поле.

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

Спостережувана, яку ми вимірюватимемо, — це повна намагніченість.

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)

Визначення обсягу часової еволюції для класичного моделювання

Наша загальна мета — моделювати часову еволюцію наведеного вище модельного гамільтоніана. Ми робимо це за допомогою еволюції Троттера, яку поділяємо на дві частини:

  1. Початкова частина, яка піддається моделюванню за допомогою матрично-добуткових станів (MPS). Ми «скомпілюємо» цю частину за допомогою AQC, як описано у https://arxiv.org/abs/2301.08609.
  2. Наступна частина Circuit, яка виконуватиметься на обладнанні. Плануємо використати AQC-Tensor для стиснення нашого Circuit часової еволюції до моменту t=4t=4, а потім еволюціонувати звичайними кроками Троттера до t=5t=5.

Генерація Circuit до та після розбиття

Тепер, коли ми обрали розбиття при t=4t=4, ми згенеруємо два Circuit:

  1. «Цільовий» Circuit для частини еволюції AQC, від ti=0t_i=0 до tf=4t_f=4. Оскільки він моделюється симулятором тензорних мереж, кількість шарів впливає на час виконання лише як константний множник, тому можна сміливо використовувати велику кількість шарів для мінімізації помилки Троттера.
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,
)
  1. Наступний Circuit еволюції, який еволюціонує від ti=4t_i=4 до tf=5t_f=5. Оскільки він виконується на квантовому обладнанні, бажано використовувати якомога менше шарів Троттера.
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, але з тим самим часом еволюції на крок Троттера, що й наступний Circuit. Це той Circuit, з яким ми б працювали, якби не використовували велику кількість кроків Троттера для цільового Circuit. Будемо називати його порівняльним 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,
)

Генерація ансацу та початкових параметрів із Circuit Троттера з меншою кількістю кроків

Спочатку ми будуємо «хороший» Circuit, що має той самий час еволюції, що й цільовий Circuit, але з меншою кількістю кроків Троттера (і, відповідно, менше шарів).

Потім ми передаємо цей «хороший» Circuit до функції generate_ansatz_from_circuit аддону AQC-Tensor. Ця функція аналізує зв'язність двох-Qubit у Circuit і повертає дві речі:

  1. загальний параметризований Circuit ансацу з тією ж двох-Qubit зв'язністю, що й вхідний Circuit; та
  2. параметри, які при підстановці в ансац відтворюють вхідний (хороший) Circuit.

Незабаром ми візьмемо ці параметри й ітеративно коригуватимемо їх, щоб максимально наблизити 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)

Quantum circuit diagram

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

Вибір налаштувань для моделювання тензорних мереж

Тут ми використовуємо симулятор тензорних мереж на основі quimb. У цьому прикладі ми використовуємо симулятор матрично-добуткових станів (MPS) від quimb та 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)

Зауваж, що оскільки ми обрали велику кількість кроків Троттера для цільового стану, він насправді має меншу похибку Троттера, ніж порівняльний Circuit. Ми можемо розрахувати вірність (ψ1ψ22| \langle \psi_1 | \psi_2 \rangle |^2) стану, підготовленого порівняльним 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

Тут ми мінімізуємо найпростішу можливу функцію витрат, MaximizeStateFidelity, використовуючи оптимізатор L-BFGS з scipy.

Ми обираємо точку зупинки для вірності таку, щоб вона була вищою за ту, яку мав би порівняльний Circuit без використання AQC. Після її досягнення стиснений Circuit матиме меншу похибку Троттера і меншу глибину, ніж оригінальний Circuit. За наявності більшого часу обробки можна виконати додаткові кроки оптимізації для подальшого підвищення вірності.

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.

Побудова фінального Circuit для передачі до Transpiler

final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
final_circuit.compose(subsequent_circuit, inplace=True)
final_circuit.draw("mpl", fold=-1)

Quantum circuit diagram

Крок 2: Транспіляція для виконання на цільовому обладнанні

На кроці 2 патерну Qiskit ми транспілюємо цей Circuit і будь-які бажані спостережувані для виконання на цільовому пристрої. Тут ми використовуємо фіктивний Backend, наданий qiskit-ibm-runtime.

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 (крок 3 патерну Qiskit).

Крок 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)