Приклади та застосування
У цьому уроці ми розглянемо кілька прикладів варіаційних алгоритмів і способи їх застосування:
- Як написати власний варіаційний алгоритм
- Як застосувати варіаційний алгоритм для пошуку мінімальних власних значень
- Як використовувати варіаційні алгоритми для розв'язання прикладних задач
Зверни увагу: фреймворк Qiskit patterns можна засто сувати до всіх задач, які ми тут розглядаємо. Втім, щоб уникнути повторень, ми явно виділятимемо кроки цього фреймворку лише в одному прикладі, що виконується на реальному обладнанні.
Формулювання задач
Уяви, що ми хочемо скористатися варіаційним алгоритмом для знаходження власного значення такого спостережуваного:
Це спостережуване має такі власні значення:
І власні стани:
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime rustworkx scipy
from qiskit.quantum_info import SparsePauliOp
observable_1 = SparsePauliOp.from_list([("II", 2), ("XX", -2), ("YY", 3), ("ZZ", -3)])
Власний VQE
Спочатку розглянемо, як вручну побудувати екземпляр VQE для знаходження найменшого власного значення . Це охопить різноманітні техніки, які ми вивчали впродовж цього курсу.
def cost_func_vqe(params, ansatz, hamiltonian, estimator):
"""Return estimate of energy from estimator
Parameters:
params (ndarray): Array of ansatz parameters
ansatz (QuantumCircuit): Parameterized ansatz circuit
hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
estimator (Estimator): Estimator primitive instance
Returns:
float: Energy estimate
"""
pub = (ansatz, hamiltonian, params)
cost = estimator.run([pub]).result()[0].data.evs
return cost
from qiskit.circuit.library.n_local import n_local
from qiskit import QuantumCircuit
import numpy as np
reference_circuit = QuantumCircuit(2)
reference_circuit.x(0)
variational_form = n_local(
num_qubits=2,
rotation_blocks=["rz", "ry"],
entanglement_blocks="cx",
entanglement="linear",
reps=1,
)
raw_ansatz = reference_circuit.compose(variational_form)
raw_ansatz.decompose().draw("mpl")

Розпочнемо налагодження на локальних симуляторах.
from qiskit.primitives import StatevectorEstimator as Estimator
from qiskit.primitives import StatevectorSampler as Sampler
estimator = Estimator()
sampler = Sampler()
Тепер задамо початковий набір параметрів:
x0 = np.ones(raw_ansatz.num_parameters)
print(x0)
[1. 1. 1. 1. 1. 1. 1. 1.]
Мінімізуємо цю функцію вартості для обчислення оптимальних параметрів
# SciPy minimizer routine
from scipy.optimize import minimize
import time
start_time = time.time()
result = minimize(
cost_func_vqe,
x0,
args=(raw_ansatz, observable_1, estimator),
method="COBYLA",
options={"maxiter": 1000, "disp": True},
)
end_time = time.time()
execution_time = end_time - start_time
Return from COBYLA because the trust region radius reaches its lower bound.
Number of function values = 103 Least value of F = -5.999999998357189
The corresponding X is:
[2.27483579e+00 8.37593091e-01 1.57080508e+00 5.82932911e-06
2.49973063e+00 6.41884255e-01 6.33686904e-01 6.33688223e-01]
result
message: Return from COBYLA because the trust region radius reaches its lower bound.
success: True
status: 0
fun: -5.999999998357189
x: [ 2.275e+00 8.376e-01 1.571e+00 5.829e-06 2.500e+00
6.419e-01 6.337e-01 6.337e-01]
nfev: 103
maxcv: 0.0
Оскільки ця навчальна задача використовує лише два Qubit-и, ми можемо перевірити результат за допомогою вбудованого розв'язувача лінійної алгебри NumPy.
from numpy.linalg import eigvalsh
solution_eigenvalue = min(eigvalsh(observable_1.to_matrix()))
print(f"""Number of iterations: {result.nfev}""")
print(f"""Time (s): {execution_time}""")
print(
f"Percent error: {100*abs((result.fun - solution_eigenvalue)/solution_eigenvalue):.2e}"
)
Number of iterations: 103
Time (s): 0.4394676685333252
Percent error: 2.74e-08
Як бачиш, результат надзвичайно близький до ідеального.
Експерименти для підвищення швидкості та точності
Додаємо референсний стан
У попередньому прикладі ми не використовували жодного референсного оператора . Тепер подумаймо, як можна отримати ідеальний власний стан . Розглянемо наступну схему.
from qiskit import QuantumCircuit
ideal_qc = QuantumCircuit(2)
ideal_qc.h(0)
ideal_qc.cx(0, 1)
ideal_qc.draw("mpl")
Швидко перевіримо, що ця схема дає нам потрібний стан.
from qiskit.quantum_info import Statevector
Statevector(ideal_qc)
Statevector([0.70710678+0.j, 0. +0.j, 0. +0.j,
0.70710678+0.j],
dims=(2, 2))
Тепер, коли ми бачили, як виглядає схема, що підготовлює розв'язок, логічно використати Gate Адамара як референсну схему, тоді повний ансац набуде вигляду:
reference = QuantumCircuit(2)
reference.h(0)
reference.cx(0, 1)
# Include barrier to separate reference from variational form
reference.barrier()
ref_ansatz = variational_form.decompose().compose(reference, front=True)
ref_ansatz.draw("mpl")

Для цієї нової схеми ідеальний розв'язок можна отримати при всіх параметрах, рівних , що підтверджує розумність вибору референсної схеми.
Тепер порівняємо кількість обчислень функції вартості, ітерацій оптимізатора та витрачений час із результатами попередньої спроби.
import time
start_time = time.time()
ref_result = minimize(
cost_func_vqe, x0, args=(ref_ansatz, observable_1, estimator), method="COBYLA"
)
end_time = time.time()
execution_time = end_time - start_time
Використовуємо оптимальні параметри для обчислення мінімального власного значення:
experimental_min_eigenvalue_ref = cost_func_vqe(
ref_result.x, ref_ansatz, observable_1, estimator
)
print(experimental_min_eigenvalue_ref)
-5.999999996759607
print("ADDED REFERENCE STATE:")
print(f"""Number of iterations: {ref_result.nfev}""")
print(f"""Time (s): {execution_time}""")
print(
f"Percent error: {100*abs((experimental_min_eigenvalue_ref - solution_eigenvalue)/solution_eigenvalue):.2e}"
)
ADDED REFERENCE STATE:
Number of iterations: 127
Time (s): 0.5620882511138916
Percent error: 5.40e-08
Залежно від конкретної системи, це може або не може покращити швидкість чи точність у цьому дуже маленькому прикладі. Головне те, що використання фізично вмотивованих референсних станів стає дедалі важливішим для підвищення швидкості та точності в міру масштабування задач.
Змінюємо початкову точку
Тепер, коли ми побачили ефект додавання референсного стану, розглянемо, що відбувається при виборі різних початкових точок . Зокрема, ми використаємо та .
Пам'ятай, що, як обговорювалось при введенні референсного стану, ідеальний розв'язок знаходиться при всіх параметрах, рівних , тому перша початкова точка повинна давати менше обчислень.
import time
start_time = time.time()
x0 = [0, 0, 0, 0, 6, 0, 0, 0]
x0_1_result = minimize(
cost_func_vqe, x0, args=(raw_ansatz, observable_1, estimator), method="COBYLA"
)
end_time = time.time()
execution_time = end_time - start_time
print("INITIAL POINT 1:")
print(f"""Number of iterations: {x0_1_result.nfev}""")
print(f"""Time (s): {execution_time}""")
INITIAL POINT 1:
Number of iterations: 108
Time (s): 0.4492197036743164
Змінюємо початкову точку на :
import time
start_time = time.time()
x0 = 6 * np.ones(raw_ansatz.num_parameters)
x0_2_result = minimize(
cost_func_vqe, x0, args=(raw_ansatz, observable_1, estimator), method="COBYLA"
)
end_time = time.time()
execution_time = end_time - start_time
print("INITIAL POINT 2:")
print(f"""Number of iterations: {x0_2_result.nfev}""")
print(f"""Time (s): {execution_time}""")
INITIAL POINT 2:
Number of iterations: 107
Time (s): 0.40889453887939453
Експериментуючи з різними початковими точками, ти можеш досягти збіжності швидше та з меншою кількістю обчислень функції.
Експерименти з різними оптимізаторами
Оптимізатор можна змінити через аргумент method функції minimize з SciPy; більше варіантів можна знайти тут. Спочатку ми використовували обмежений мінімізатор (COBYLA). У цьому прикладі спробуємо необмежений мінімізатор (BFGS).
import time
start_time = time.time()
result = minimize(
cost_func_vqe, x0, args=(raw_ansatz, observable_1, estimator), method="BFGS"
)
end_time = time.time()
execution_time = end_time - start_time
print("CHANGED TO BFGS OPTIMIZER:")
print(f"""Number of iterations: {result.nfev}""")
print(f"""Time (s): {execution_time}""")
CHANGED TO BFGS OPTIMIZER:
Number of iterations: 117
Time (s): 0.31656408309936523
Приклад VQD
Тут ми явно реалізуємо фреймворк Qiskit patterns.
Крок 1: Відображення класичних вхідних даних на квантову задачу
Тепер замість пошуку лише найменшого власного значення наших спостережуваних ми шукатимемо всі (де ).
Пригадай, що функції вартості VQD мають вигляд:
Це особливо важливо, оскільки вектор (у цьому випадку ) має передаватись як аргумент при визначенні об'єкта VQD.
Крім того, в реалізації VQD у Qiskit замість розгляду ефективних спостережуваних, описаних у попередньому зошиті, точності