Příklady a aplikace
V této lekci prozkoumáme několik příkladů variačních algoritmů a způsoby jejich použití:
- Jak napsat vlastní variační algoritmus
- Jak použít variační algoritmus k nalezení minimálních vlastních hodnot
- Jak využít variační algoritmy k řešení praktických aplikačních případů
Všimni si, že rámec Qiskit patterns lze aplikovat na všechny problémy, které zde představíme. Abychom se však vyhnuli opakování, budeme kroky rámce explicitně uvádět pouze v jednom příkladě, spuštěném na skutečném hardwaru.
Definice problémů
Představ si, že chceme použít variační algoritmus k nalezení vlastní hodnoty následujícího pozorovatelné veličiny:
Tato pozorovatelná veličina má následující vlastní hodnoty:
A vlastní stavy:
# 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)])
Vlastní VQE
Nejprve prozkoumáme, jak ručně sestavit instanci VQE k nalezení nejnižší vlastní hodnoty pro . Využijeme přitom různé techniky, které jsme probírali v průběhu tohoto kurzu.
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")

Začneme laděním na lokálních simulátorech.
from qiskit.primitives import StatevectorEstimator as Estimator
from qiskit.primitives import StatevectorSampler as Sampler
estimator = Estimator()
sampler = Sampler()
Nyní nastavíme počáteční sadu parametrů:
x0 = np.ones(raw_ansatz.num_parameters)
print(x0)
[1. 1. 1. 1. 1. 1. 1. 1.]
Minimalizací této účelové funkce můžeme vypočítat optimální parametry
# 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
Protože tento jednoduchý problém používá pouze dva qubity, můžeme výsledek ověřit pomocí NumPy lineárního algebraického řešiče vlastních hodnot.
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
Jak vidíš, výsledek je velmi blízký ideální hodnotě.
Experimentování pro zlepšení rychlosti a přesnosti
Přidání referenčního stavu
V předchozím příkladu jsme nepoužili žádný referenční operátor . Teď se zamysli nad tím, jak lze získat ideální vlastní stav . Uvažuj následující obvod.
from qiskit import QuantumCircuit
ideal_qc = QuantumCircuit(2)
ideal_qc.h(0)
ideal_qc.cx(0, 1)
ideal_qc.draw("mpl")
Můžeme rychle ověřit, že tento obvod nám dává požadovaný stav.
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))
Nyní, když jsme viděli, jak vypadá obvod připravující řešení, zdá se rozumné použít jako referenční obvod Hadamardovu bránu, takže celý ansatz se stane:
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")

Pro tento nový obvod by bylo možné dosáhnout ideálního řešení, když jsou všechny parametry nastaveny na , což potvrzuje, že volba referenčního obvodu je rozumná.
Nyní porovnejme počet vyhodnocení účelové funkce, iterací optimalizátoru a čas potřebný oproti předchozímu pokusu.
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
Pomocí optimálních parametrů vypočítáme minimální vlastní hodnotu:
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
V závislosti na tvém konkrétním systému to může nebo nemusí vést ke zlepšení rychlosti či přesnosti v tomto velmi malém příkladu. Podstatné je, že začínání s fyzikálně motivovanými referenčními stavy je čím dál důležitější pro zlepšení rychlosti a přesnosti, jak problémy rostou.
Změna počátečního bodu
Nyní, když jsme viděli vliv přidání referenčního stavu, se podíváme na to, co se stane, když zvolíme různé počáteční body . Konkrétně použijeme a .
Pamatuj, že — jak bylo popsáno při představení referenčního stavu — ideální řešení by bylo nalezeno, když jsou všechny parametry , takže první počáteční bod by měl vést k menšímu počtu vyhodnocení.
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
Nastavení počátečního bodu na :
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
Experimentováním s různými počátečními body můžeš dosáhnout konvergence rychleji a s menším počtem vyhodnocení funkce.
Experimentování s různými optimalizátory
Optimalizátor můžeme upravit pomocí argumentu method funkce minimize ze SciPy, přičemž více možností najdeš zde. Původně jsme použili omezený minimalizátor (COBYLA). V tomto příkladu prozkoumáme použití neomezeného minimalizátoru (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 příklad
Zde implementujeme framework Qiskit patterns explicitně.
Krok 1: Mapování klasických vstupů na kvantový problém
Nyní místo hledání pouze nejnižší vlastní hodnoty našich pozorovatelných veličin budeme hledat všechny , (kde ).
Pamatuj si, že nákladové funkce VQD jsou:
To je obzvláště důležité, protože vektor (v tomto případě ) musí být předán jako argument při definování objektu VQD.
Také v implementaci VQD v Qiskitu se místo uvažování efektivních pozorovatelných veličin popsaných v předchozím notebooku věrnosti počítají přímo pomocí algoritmu ComputeUncompute, který využívá primitiv Sampler k vzorkování pravděpodobnosti získání pro obvod
. To funguje právě proto, že tato pravděpodobnost je
ansatz = n_local(
num_qubits=2,
rotation_blocks=["ry", "rz"],
entanglement_blocks="cz",
# entanglement="linear",
reps=1,
)
ansatz.decompose().draw("mpl")

Začněme tím, že se podíváme na následující pozorovatelnou veličinu:
Tato pozorovatelná veličina má následující vlastní hodnoty:
A vlastní stavy: