Zum Hauptinhalt springen

Erste Schritte mit approximativer Quantenkompilierung mit Tensornetzwerken (AQC-Tensor)

Paketversionen

Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen, diese oder neuere Versionen zu verwenden.

qiskit[all]~=2.3.0
qiskit-aer~=0.17
qiskit-addon-utils~=0.3.0
qiskit-addon-aqc-tensor[aer,quimb-jax]~=0.2.0; sys.platform != 'darwin'
scipy~=1.16.3

Diese Anleitung demonstriert ein einfaches funktionierendes Beispiel für den Einstieg in AQC-Tensor. In diesem Beispiel nimmst du einen Trotter-Circuit, der die Zeitentwicklung eines transversalen Ising-Modells simuliert, und verwendest die AQC-Tensor-Methode, um die resultierende Circuit-Tiefe zu reduzieren. Außerdem benötigt dieses Beispiel das Paket qiskit-addon-utils für den Problemgenerator, qiskit-aer für die Tensornetzwerk-Simulation und scipy für die Parameteroptimierung.

Zunächst sei daran erinnert, dass der Hamiltonoperator des transversalen Ising-Modells die folgende Form hat:

HIsing=∑i=1NJi,(i+1)ZiZi+1+hiXi\mathcal{H}_{Ising} = \sum_{i=1}^N J_{i,(i+1)}Z_iZ_{i+1} + h_i X_i

wobei wir periodische Randbedingungen annehmen, die implizieren, dass für i=10i=10 gilt i+1=11→1i+1=11\rightarrow 1, JJ die Kopplungsstärke zwischen zwei Gitterpunkten ist und hh die Stärke des externen Magnetfelds.

Der folgende Code-Ausschnitt generiert den Hamiltonoperator einer 10-Gitterpunkt-Ising-Kette mit periodischen Randbedingungen.

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-addon-aqc-tensor qiskit-addon-utils qiskit-aer scipy
from qiskit.transpiler import CouplingMap
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit_addon_aqc_tensor import generate_ansatz_from_circuit
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit
from qiskit_addon_aqc_tensor.simulation import compute_overlap
from qiskit_addon_aqc_tensor.objective import MaximizeStateFidelity
from qiskit_aer import AerSimulator
from scipy.optimize import OptimizeResult, minimize

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

Zeitentwicklung in zwei Teile aufteilen: klassische und Quantenausführung​

Das übergeordnete Ziel dieses Beispiels ist die Simulation der Zeitentwicklung des Modell-Hamiltonoperators. Wir tun dies hier mittels Trotter-Entwicklung, die in zwei Abschnitte aufgeteilt wird.

  1. Ein anfänglicher Abschnitt, der mithilfe von Matrix-Produkt-Zuständen (MPS) simulierbar ist. Dies ist der Teil, der mit AQC-Tensor „kompiliert" wird.
  2. Ein anschließender Abschnitt, der auf Quantenhardware ausgeführt wird.

Wir entwickeln das System bis zur Zeit tf=5t_f=5 und verwenden AQC-Tensor, um die Zeitentwicklung bis t=4t=4 zu komprimieren, und führen dann mit gewöhnlichen Trotter-Schritten bis tft_f weiter.

Im nächsten Schritt generieren wir zwei Circuits: einen, der mit AQC-Tensor komprimiert wird, und einen, der auf einem QPU ausgeführt wird. Da der erste Circuit klassisch mithilfe von Matrix-Produkt-Zuständen simuliert wird, verwenden wir eine großzügige Anzahl von Schichten, um den Trotter-Fehler zu minimieren. Der zweite Circuit, der die Zeitentwicklung von ti=4t_i=4 bis tf=5t_f=5 simuliert, verwendet deutlich weniger Schichten, um die Tiefe zu minimieren.

# Generate circuit to be compressed
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,
)

# Generate circuit to execute on hardware
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,
)

Zu Vergleichszwecken generieren wir außerdem einen dritten Circuit, der bis t=4t=4 entwickelt, aber dieselbe Anzahl an Schichten hat wie der zweite Circuit, der von ti=4t_i=4 bis tf=5t_f=5 entwickelt. Dies ist der Circuit, den wir ohne die AQC-Tensor-Technik ausgeführt hätten. Wir bezeichnen ihn vorerst als Vergleichs-Circuit.

aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps
/ subsequent_evolution_time
* aqc_evolution_time
)
aqc_comparison_num_trotter_steps

comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)

Ansatz generieren und MPS-Simulation aufbauen​

Als nächstes generieren wir den Ansatz, den wir optimieren werden. Er entwickelt bis zur gleichen Evolutionszeit wie unser erster Circuit (von ti=0t_i=0 bis tf=4t_f=4), aber mit weniger Trotter-Schritten.

Sobald der Circuit generiert wurde, übergeben wir ihn an die Funktion generate_ansatz_from_circuit() von AQC-Tensor, die die Zwei-Qubit-Konnektivität analysiert und zwei Dinge zurückgibt. Erstens einen generierten Ansatz-Circuit mit derselben Zwei-Qubit-Konnektivität, und zweitens einen Satz von Parametern, die, wenn sie in den Ansatz eingesetzt werden, den Eingabe-Circuit ergeben.

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)

Output of the previous code cell

Als nächstes erstellen wir die MPS-Darstellung des Zustands, der durch AQC approximiert werden soll. Außerdem berechnen wir die Fidelity ∣⟨ψ1∣ψ2⟩∣2|\langle\psi_1 | \psi_2 \rangle |^2 zwischen dem vom Vergleichs-Circuit erzeugten Zustand und dem Circuit, der den Zielzustand erzeugt (der mehr Trotter-Schritte verwendet).

# Generate MPS simulator settings and obtain the MPS representation of the target state
simulator_settings = AerSimulator(
method="matrix_product_state",
matrix_product_state_max_bond_dimension=100,
)
aqc_target_mps = tensornetwork_from_circuit(
aqc_target_circuit, simulator_settings
)

# Compute the fidelity between the MPS representation of the target state and the state produced by the comparison circuit
comparison_mps = tensornetwork_from_circuit(
comparison_circuit, simulator_settings
)
comparison_fidelity = (
abs(compute_overlap(comparison_mps, aqc_target_mps)) ** 2
)
print(f"Comparison fidelity: {comparison_fidelity}")
Comparison fidelity: 0.9997111919739693

Die Parameter des Ansatzes mithilfe des MPS optimieren​

Abschließend optimieren wir den Ansatz-Circuit so, dass er den Zielzustand mit einer höheren Fidelity erzeugt als unser comparison_fidelity. Die zu minimierende Kostenfunktion ist MaximizeStateFidelity und wird mit dem L-BFGS-Optimierer von scipy optimiert.

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,
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.95084365
Intermediate result: Fidelity 0.98409893
Intermediate result: Fidelity 0.99142033
Intermediate result: Fidelity 0.99521405
Intermediate result: Fidelity 0.99566673
Intermediate result: Fidelity 0.99650054
Intermediate result: Fidelity 0.99683487
Intermediate result: Fidelity 0.99720426
Intermediate result: Fidelity 0.99761726
Intermediate result: Fidelity 0.99809073
Intermediate result: Fidelity 0.99838244
Intermediate result: Fidelity 0.99861841
Intermediate result: Fidelity 0.99874617
Intermediate result: Fidelity 0.99892696
Intermediate result: Fidelity 0.99908129
Intermediate result: Fidelity 0.99917737
Intermediate result: Fidelity 0.99925456
Intermediate result: Fidelity 0.99933134
Intermediate result: Fidelity 0.99947173
Intermediate result: Fidelity 0.99956469
Intermediate result: Fidelity 0.99964488
Intermediate result: Fidelity 0.99967419
Intermediate result: Fidelity 0.99968821
Intermediate result: Fidelity 0.9997448
Done after 24 iterations.

An diesem Punkt haben wir einen Parametersatz, der den Zielzustand mit einer höheren Fidelity erzeugt als der Vergleichs-Circuit ohne den Einsatz von AQC. Mit diesen optimalen Parametern hat der komprimierte Circuit nun sowohl weniger Trotter-Fehler als auch eine geringere Tiefe als der ursprüngliche Circuit.

Als letzten Schritt erstellt der folgende Code-Ausschnitt den vollständigen Zeitentwicklungs-Circuit, der an eine Transpiler-Pipeline übergeben und auf Quantenhardware ausgeführt werden kann.

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

Output of the previous code cell

Nächste Schritte​

Empfehlungen