Quimb-Layer-basiertes Backend
qiskit_addon_mpf.backends.quimb_layers
Ein schichtweises Zeit-Evolutions-Backend mit quimb.
Dieses Backend ist nur verfügbar, wenn die optionalen Abhängigkeiten installiert wurden:
pip install "qiskit-addon-mpf[quimb]"Kolumne „ 1 “ | Kolumne „ 2 “ |
|---|---|
LayerwiseEvolver | Ein Spezialfall des TEBDEvolver auf der Grundlage schichtweiser Evolutionsmodelle. |
LayerModel | Ein Modell zur Darstellung einer Schicht von Zeit-Evolutions-Interaktionen. |
Zugrunde liegende Methode
Dieses Modul bietet ein Backend für die Zeitevolution, das dem TEBD-basierten Backend des Moduls quimb_tebd modul. Der Hauptunterschied besteht darin, dass dieses Modul dem Benutzer volle Flexibilität bei der Definition seiner Produktformeln bietet und ihn somit nicht auf die in der quimb bibliothek.
Im Kern handelt es sich bei dem von diesem Modul bereitgestellten Algorithmus nach wie vor um einen [TEBD-1-Algorithmus]. Anstatt jedoch die abwechselnden Aktualisierungen der geraden und ungeraden Bindungen des Zeitentwicklungszustands zu erzwingen (siehe auch quimb_tebd.TEBDEvolver.sweep()), überträgt diese Implementierung die Verantwortung für die alternierende Aktualisierung der Bindungen auf die Definition mehrerer Zeitfaltungsebenen.
Dies lässt sich am besten anhand eines Beispiels erklären. Nehmen wir an, wir haben einen generischen Hamiltonian, der auf eine 1-dimensionale Kette von Orten wirkt.
Im Folgenden wird die Reihenfolge der Pauli-Terme des Hamiltonianers sehr genau festgelegt, da sich dies direkt auf die Struktur des späteren Zeitentwicklungskreises auswirkt.
>>> from qiskit.quantum_info import SparsePauliOp
>>> hamil = SparsePauliOp.from_sparse_list(
... [("ZZ", (i, i+1), 1.0) for i in range(1, 9, 2)] +
... [("Z", (i,), 0.5) for i in range(10)] +
... [("ZZ", (i, i+1), 1.0) for i in range(0, 9, 2)],
... num_qubits=10,
... )Untersuchen wir nun den Zeitentwicklungskreis dieses Hamiltonianers mit Hilfe einer Suzuki-Trotter-Formel zweiter Ordnung.
>>> from qiskit.synthesis import SuzukiTrotter
>>> from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit
>>> circuit = generate_time_evolution_circuit(hamil, time=1.0, synthesis=SuzukiTrotter(order=2))
>>> circuit.draw("mpl")
<Figure size 956...x869... with 1 Axes>
In der obigen Schaltung ist der schichtweise Aufbau deutlich zu erkennen. Wir können dies noch weiter unterstreichen, indem wir den Schaltkreis in mehrere Schichten aufteilen, wie unten gezeigt (wir fügen die layers zu einem einzigen Schaltkreis mit Barrieren dazwischen zusammen, um die Visualisierung zu erleichtern).
>>> from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
>>> layers = slice_by_gate_types(circuit)
>>> combine_slices(layers, include_barriers=True).draw("mpl")
<Figure size 1374...x869... with 1 Axes>
Die Asymmetrie der mittleren Schichten ist das Ergebnis der Anwendung der Qiskit-Formel SuzukiTrotter formel. In ihrer Form zweiter Ordnung fasst sie die beiden Halbwertszeiten des letzten Terms der Hamiltonfunktion zu einer einzigen, doppelt so langen Entwicklung zusammen. Wir könnten diese Schaltung so transpilieren, dass alle subequenten Gatter in den beiden mittleren Schichten kollabieren (genau wie bei der letzten Schaltung), aber der Einfachheit dieses Beispiels halber verzichten wir darauf.
Es ist nicht möglich, den TEBD-Algorithmus von Quimb anzuweisen, die genaue Struktur der oben gezeigten Schaltung zu simulieren. Der Grund dafür ist eine Einschränkung in der Schnittstelle, da sie nur den vollständigen Hamiltonian akzeptiert, der dann mit vordefinierten Trotter-Formeln zeitlich entwickelt wird. Dabei wird jedoch die Ordnung der Pauli-Terme in einem Hamiltonianer nicht berücksichtigt (wie wir es hier tun).
Wenn man die dynamischen MPF-Koeffizienten einer Zeitentwicklung mit einer anderen als der in Quimb implementierten Produktformelstruktur (wie im obigen Beispiel) berechnen will, kann man den von diesem Modul bereitgestellten Zeitentwicklungsalgorithmus verwenden. Anstatt einen einzelnen monolithischen Hamiltonianer zu nehmen, dessen zeitliche Entwicklung modelliert werden soll, akzeptiert das LayerwiseEvolver akzeptiert eine Liste von LayerModel objekten, von denen jedes eine einzelne Schicht der Produktformel beschreibt. Dies gibt dem Benutzer volle Flexibilität bei der Definition der Trotter-Zerlegung bis hin zur feinsten Ebene.
Es muss jedoch darauf geachtet werden, dass die Eigenschaft von TEBD, gerade und ungerade Anleihen abwechselnd zu aktualisieren, weiterhin gewährleistet ist. Glücklicherweise ist diese Eigenschaft bei Quantenschaltungen, die aus höchstens zwei Qubit-Gattern bestehen, konstruktionsbedingt gegeben.
Codebeispiel
In diesem Abschnitt bauen wir auf dem obigen Beispiel auf und zeigen, wie eine benutzerdefinierte Trotter-Formel verwendet wird, um eine LayerwiseEvolver zu konstruieren, die verwendet werden kann, um die quimb_tebd.TEBDEvolver im Arbeitsablauf, der in quimb_tebd.
Der allgemeine Arbeitsablauf bei der Verwendung dieses Moduls ist derselbe wie bei dem quimb_tebd modul, daher sollten Sie auch diese Anweisungen lesen.
Einfach ausgedrückt, müssen wir jede der Schaltungen layers (siehe oben) in eine LayerModel instanz umwandeln. Zu diesem Zweck können wir seine from_quantum_circuit() methode verwenden.
>>> from qiskit_addon_mpf.backends.quimb_layers import LayerModel
>>> model0 = LayerModel.from_quantum_circuit(layers[0])
>>> layer_models = [model0]Im obigen Code können Sie sehen, wie einfach die Umwandlung für Schichten ist, die nur Zwei-Qubit-Gatter enthalten, die auf sich gegenseitig ausschließende Qubits wirken (was Schichten der Tiefe 1 garantieren).
Bei Schichten mit Ein-Qubit-Gattern müssen wir jedoch vorsichtiger sein. Der Grund dafür ist, dass der TEBD-Algorithmus, der dem LayerwiseEvolver zugrunde liegende TEBD-Algorithmus abwechselnd gerade und ungerade Bindungen aktualisieren muss. Und da Einzel-Qubit-Gates nicht auf eine Seite angewendet werden, sondern in zwei Hälften geteilt und auf die Bindungen auf beiden Seiten angewendet werden, würde eine Schicht von Einzel-Qubit-Gates, die auf alle Qubits wirken, diese Annahme brechen.
Um dieses Problem zu umgehen, können wir eine beliebige Schicht nehmen, die nur aus Ein-Qubit-Gattern besteht, und sie zweimal anwenden (einmal auf die geraden und einmal auf die ungeraden Bindungen).
>>> model1a = LayerModel.from_quantum_circuit(layers[1], keep_only_odd=True)
>>> model1b = LayerModel.from_quantum_circuit(layers[1], keep_only_odd=False)
>>> layer_models.extend([model1a, model1b])Da wir nun wissen, wie Schichten aus Zwei-Qubit- und Ein-Qubit-Gattern zu behandeln sind, können wir die übrigen Schichten transformieren.
>>> for layer in layers[2:]:
... num_qubits = len(layer.data[0].qubits)
... if num_qubits == 2:
... layer_models.append(LayerModel.from_quantum_circuit(layer))
... else:
... layer_models.append(
... LayerModel.from_quantum_circuit(layer, keep_only_odd=True)
... )
... layer_models.append(
... LayerModel.from_quantum_circuit(layer, keep_only_odd=False)
... )
>>> assert len(layer_models) == 8Am Ende haben wir 8 LayerModeleine für jede der 4 Zwei-Qubit-Schichten und zwei für jede der 2 Ein-Qubit-Schichten.
Schließlich können wir unser ApproxEvolverFactory protokoll für die Verwendung innerhalb der setup_dynamic_lse() funktion.
>>> from functools import partial
>>> from qiskit_addon_mpf.backends.quimb_layers import LayerwiseEvolver
>>> approx_evolver_factory = partial(
... LayerwiseEvolver,
... layers=layer_models,
... split_opts={"max_bond": 10, "cutoff": 1e-5},
... )Es ist zu beachten, dass wir in diesem Arbeitsablauf den Zeitschritt, der von der Trotter-Formel verwendet wird, noch nicht festgelegt haben. Wir haben auch nur eine einzige Wiederholung der Trotter-Formel eingerichtet, da der Rest von dem internen DynamicMPF algorithmus erledigt, der während setup_dynamic_lse().
Natürlich können Sie dies auch verwenden, um eine ExactEvolverFactory. Sie können aber auch ein quimb_layers.LayerwiseEvolver mit einem quimb_tebd.TEBDEvolver.
Ressourcen
[1]: https://en.wikipedia.org/wiki/Time-evolving _block_decimation