Snížení hloubky Circuit pomocí doplňku AQC-Tensor pro Qiskit
V tomto notebooku si projdeme kroky Qiskit vzoru a přitom použijeme přibližnou kvantovou kompilaci s tenzorními sítěmi (AQC-Tensor), abychom dosáhli nižší hloubky Circuit, než by bylo jinak potřeba pro provádění Trotterovy evoluce.
Toto jsou kroky, které podnikneme:
- Krok 1: Mapování na kvantový problém
- Inicializace Hamiltoniánu problému a pozorovatelných veličin
- Vygenerování cílového stavu tenzorové sítě pro počáteční část Circuit
- Vygenerování Circuit s nízkou hloubkou, který aproximuje komprimovanou část
- Vygenerování obecného ansatzu z tohoto Circuit
- Optimalizace parametrů tak, aby se ansatz co nejvíce přiblížil cíli
- Přidání následných Trotterových kroků k optimalizovanému ansatzu
- Krok 2: Optimalizace pro cílový hardware
- Transpilace Circuit pro hardware
- Krok 3: Provedení experimentů
- Použití falešného Backend pro jednoduchost
- Krok 4: Rekonstrukce výsledků
- Není potřeba; místo toho pouze vypíšeme naměřenou pozorovatelnou veličinu
Krok 1: Mapování na kvantový Circuit a operátor
Nastavení modelového Hamiltoniánu a pozorovatelné veličiny
V tomto notebooku používáme Isingův model na kružnici o 10 místech:
kde periodické okrajové podmínky znamenají, že pro dostaneme , je síla vazby mezi dvěma místy a je vnější magnetické pole.
# 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),
)
Pozorovatelná veličina, kterou budeme měřit, je celková magnetizace.
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)
Určení, jak velkou část časové evoluce simulovat klasicky
Náš celkový cíl je simulovat časovou evoluci výše uvedeného modelového Hamiltoniánu. Děláme to pomocí Trotterovy evoluce, kterou rozdělíme na dvě části:
- Počáteční část, která je simulovatelná pomocí stavů maticového součinu (MPS). Tuto část „zkompilujeme" pomocí AQC, jak je popsáno v https://arxiv.org/abs/2301.08609.
- Následná část Circuit, která bude provedena na hardware. Plánujeme použít AQC-Tensor ke kompresi našeho Circuit časové evoluce až do času a poté provádět evoluci pomocí běžných Trotterových kroků až do .
Generování Circuits před a po rozdělení
Nyní, když jsme se rozhodli rozdělit v , vygenerujeme dva Circuits:
- „Cílový" Circuit pro část AQC evoluce, od do . Protože je simulován simulátorem tenzorové sítě, počet vrstev ovlivňuje dobu provádění pouze konstantním faktorem, takže s klidným svědomím můžeme použít velkorysý počet vrstev, abychom minimalizovali Trotterovu chybu.
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,
)
- Následný Circuit evoluce, který provádí evoluci od do . Protože bude spuštěn na kvantovém hardware, je žádoucí použít co nejméně Trotterových vrstev.
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,
)
Pro účely pozdějšího srovnání vygenerujeme také třetí Circuit: takový, který provádí evoluci po dobu aqc_evolution_time, ale má stejnou dobu evoluce na Trotterův krok jako následující Circuit. To je Circuit, se kterým bychom pracovali, kdybychom nepoužili velkorysý počet Trotterových kroků pro cílový Circuit. Budeme ho označovat jako srovnávací 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,
)
Generování ansatzu a počátečních parametrů z Trotterova Circuit s menším počtem kroků
Nejprve vytvoříme „dobrý" Circuit, který má stejnou dobu evoluce jako cílový Circuit, ale s menším počtem Trotterových kroků (a tedy menším počtem vrstev).
Poté tento „dobrý" Circuit předáme funkci generate_ansatz_from_circuit z AQC-Tensor. Tato funkce analyzuje dvou-Qubitovou konektivitu Circuit a vrátí dvě věci:
- obecný, parametrizovaný ansatz Circuit se stejnou dvou-Qubitovou konektivitou jako vstupní Circuit; a,
- parametry, které po vložení do ansatzu dají vstupní (dobrý) Circuit.
Tyto parametry brzy vezmeme a iterativně je upravíme, aby se ansatz Circuit co nejvíce přiblížil cílovému 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
Výběr nastavení pro simulaci tenzorové sítě
Zde používáme simulátor tenzorové sítě založený na quimb. V tomto příkladu používáme simulátor stavů maticového součinu (MPS) z quimb a používáme JAX pro automatickou diferenciaci. Více informací o použití simulátoru quimb najdeš v dokumentaci 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",
)
Konstrukce reprezentace cílového stavu AQC jako maticového součinu
Dále sestavíme reprezentaci maticového součinu stavu, který má být aproximován pomocí AQC.
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit
aqc_target_mps = tensornetwork_from_circuit(aqc_target_circuit, simulator_settings)
Všimni si, že protože jsme pro cílový stav zvolili velkorysý počet Trotterových kroků, má ve skutečnosti menší Trotterovu chybu než srovnávací Circuit. Můžeme vypočítat věrnost () stavu připraveného srovnávacím Circuit vůči cílovému stavu:
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
Optimalizace parametrů ansatzu pomocí výpočtů MPS
Zde minimalizujeme nejjednodušší možnou ztrátovou funkci, MaximizeStateFidelity, pomocí optimalizátoru L-BFGS ze scipy.
Zvolíme zastavovací bod pro věrnost tak, aby byla vyšší, než jaké by dosáhl srovnávací Circuit bez použití AQC. Jakmile je tohoto bodu dosaženo, má komprimovaný Circuit menší Trotterovu chybu i menší hloubku než původní Circuit. S více procesním časem lze provést další optimalizační kroky, aby se věrnost ještě zvýšila.
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.
Konstrukce finálního Circuit pro předání Transpileru
final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
final_circuit.compose(subsequent_circuit, inplace=True)
final_circuit.draw("mpl", fold=-1)

Krok 2: Transpilace pro spuštění na cílovém hardware
V kroku 2 Qiskit vzoru transpilujeme tento Circuit a požadované pozorovatelné veličiny pro spuštění na cílovém zařízení. Zde používáme falešný Backend poskytovaný 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)
Výsledný ISA Circuit lze poté odeslat ke spuštění na Backend (krok 3 Qiskit vzoru).
Krok 3: Spuštění na kvantovém hardware
from qiskit_ibm_runtime import EstimatorV2 as Estimator
estimator = Estimator(backend)
job = estimator.run([(isa_circuit, isa_observable)])
pub_result = job.result()[0]
Krok 4: Rekonstrukce
Rekonstrukce v našem případě není nutná. Stačí se podívat na výsledek.
pub_result.data.evs[()]
np.float64(0.047998046875000006)