Transpiler
qiskit.transpiler
Übersicht
Wenn Sie bereits mit den Konzepten der Schaltkreistranspilierung/-kompilierung vertraut sind, können Sie zu diesem Abschnitt übergehen:
- Beschreibungen der vorkonfigurierten Passmanager. Dazu gehören Beschreibungen der Stufen und aller verfügbaren integrierten Plugins.
- Dokumentation zur Erstellung eines benutzerdefinierten Transpiler-Passes.
Bei der Transpilierung wird eine gegebene Eingabeschaltung umgeschrieben, um sie an die Topologie eines bestimmten Quantengeräts anzupassen und/oder die Schaltung für die Ausführung auf Quantensystemen zu optimieren.
Die meisten Schaltungen müssen eine Reihe von Transformationen durchlaufen, die sie mit einem bestimmten Zielgerät kompatibel machen und sie optimieren, um die Auswirkungen von Rauschen auf die resultierenden Ergebnisse zu verringern. Das Umschreiben von Quantenschaltungen, um sie an die Hardwarebedingungen anzupassen, und die Optimierung der Leistung können alles andere als trivial sein. Der Logikfluss in der Rewriting-Toolkette muss nicht linear sein, sondern kann oft iterative Unterschleifen, bedingte Verzweigungen und andere komplexe Verhaltensweisen aufweisen. Der Standardkompilierungsfluss folgt jedoch der unten angegebenen Struktur:
QuantumCircuitQiskit verwendet im gesamten Transpiler-Stack die graphbasierte DAGCircuit Zwischenrepräsentation (IR) einer Schaltung anstelle der baumbasierten. DAGCircuitEine Transpiler-Pipeline ist ein PassManager Objekt, dessen PassManager.run() Methode ein QuantumCircuit Objekt entgegennimmt, es in ein IR umwandelt, das IR anschließend einer Abfolge von Durchläufen unterzieht und schließlich wieder ein QuantumCircuit Objekt zurückgibt. PropertySetEin Durchlauf ist entweder ein AnalysisPass, der Eigenschaften des Schaltkreises berechnet und im zustandsbehafteten speichert, oder ein TransformationPass, der den IR modifiziert, um ein bestimmtes Einzelziel zu erreichen. Man kann sich eine Pipeline so vorstellen, dass sie in „Stufen“ unterteilt ist, wobei jede Stufe für eine übergeordnete Transformation zuständig ist.
generate_preset_pass_manager()Qiskit stellt mithilfe der Funktion einen Standard-Pipeline-Builder für die Transpilierung bereit. Dies liefert eine korrekt konfigurierte Pipeline für die vollständige Transpilierung bei einem gewählten optimization_level Wert (zwischen 0 und 3, einschließlich). Sofern Sie nicht nach etwas ganz Speziellen suchen, ist dies mit ziemlicher Sicherheit der richtige Einstieg für Sie. Eine Beispiel-Transpilierung sieht wie folgt aus:
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Any abstract circuit you want:
abstract = QuantumCircuit(2)
abstract.h(0)
abstract.cx(0, 1)
# Any method you like to retrieve the backend you want to run on:
backend = QiskitRuntimeService().backend("some-backend")
# Create the pass manager for the transpilation ...
pm = generate_preset_pass_manager(backend=backend)
# ... and use it (as many times as you like).
physical = pm.run(abstract)Bei frühen Experimenten zur Fehlertoleranz rufen die Funktionen generate_preset_pass_manager() und transpile() eine spezielle Transpilations-Pipeline auf, wenn die Zielbasis aus Clifford+T-Gattern besteht; siehe generate_preset_clifford_t_pass_manager() für weitere Informationen. Für eine detaillierte Konfiguration von Clifford+T-Pipelines wird empfohlen, Letzteres zu verwenden. "approximation_degree"Beispielsweise lässt sich die Synthesepräzision von „ “ nicht über "unitary_synthesis_method" in generate_preset_pass_manager() festlegen, sondern kann nur global über die eingestellt werden. Allerdings generate_preset_clifford_t_pass_manager() bringt "rz_synthesis_config" dies in dieser Angelegenheit ans Licht.
Zum Beispiel:
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import QFTGate
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import get_clifford_gate_names
# Any abstract circuit you want:
abstract = QuantumCircuit(4)
abstract.append(QFTGate(4), [0, 1, 2, 3])
# Use all Clifford+T basis gates
basis_gates = get_clifford_gate_names() + ["t", "tdg"]
# Create and run the pass manager
pm = generate_preset_pass_manager(basis_gates=basis_gates)
transpiled = pm.run(abstract)Für die meisten Anwendungsfälle ist dies alles, was Sie brauchen. Die gesamte Transpiler-Infrastruktur von Qiskit ist jedoch in hohem Maße erweiterbar und konfigurierbar. Der Rest dieser Seite beschreibt, wie die Low-Level-Fähigkeiten des Transpiler-Stacks genutzt werden können.
Voreingestellte Passmanager
Die Funktion generate_preset_pass_manager() erstellt die „voreingestellten Pass-Manager“. Dies sind alles Instanzen von PassManager, die verwendet werden, indem ein QuantumCircuit an die PassManager.run() Methode übergeben wird. Genauer gesagt sind die voreingestellten Pass-Manager Instanzen von StagedPassManager, was eine umfassendere Konfiguration der einzelnen Phasen einer Transpilation ermöglicht, einschließlich Hooks vor und nach den einzelnen Phasen.
Ein voreingestellter Passmanager hat bis zu sechs benannte Stufen. Diese werden im Folgenden in der Reihenfolge ihrer Ausführung zusammengefasst und in den folgenden Unterabschnitten ausführlicher erläutert.
init
Abstrakte Schaltungsoptimierungen und Reduzierung von Multi-Qubit-Operationen auf Ein- und Zwei-Qubit-Operationen. Siehe Initialisierungsphase für weitere Einzelheiten.
layout
Wählen Sie eine anfängliche Zuordnung von virtuellen Qubits zu physikalischen Qubits, einschließlich der Erweiterung des Schaltkreises, um explizite Ancillas zu enthalten. Dieses Stadium wird manchmal unter routing zusammengefasst. Weitere Einzelheiten finden Sie im Abschnitt Layout.
routing
Fügen Sie Gatter in die Schaltung ein, um sicherzustellen, dass sie den Verbindungsbedingungen der Target. entspricht. Die eingefügten Gates müssen noch nicht mit der Ziel-ISA übereinstimmen, daher handelt es sich oft lediglich swap um Befehle. Dieser Schritt wird manchmal übersprungen, wenn der layout Schritt seine Aufgabe erfüllt. Weitere Informationen finden Sie unter „Routing-Phase “.
translation
Wandle alle Gatter in der Schaltung so um, dass sie der ISA des Target. entsprechen. Weitere Informationen finden Sie unter „Übersetzungsphase “.
optimization
Optimierungen auf niedriger Ebene, die die Hardware berücksichtigen. Im Gegensatz zu den abstrakten Optimierungen der Stufe init wirkt diese Stufe auf einen physischen Schaltkreis ein. Siehe Optimierungsphase für weitere Einzelheiten.
scheduling
Einfügen Delay anweisungen, um das Wall-Clock-Timing einer Schaltung eindeutig zu machen. Dazu können auch hardwaregestützte Online-Fehlerreduzierungstechniken wie die dynamische Entkopplung gehören, die von der Kenntnis der Wandtaktzeiten abhängig sind. Weitere Einzelheiten finden Sie unter Planungsphase.
Die voreingestellten Transpiler-Pipelines können auch auf einer höheren Ebene konfiguriert werden, indem ein optimization_level eingestellt wird. Hierbei handelt es sich um eine ganze Zahl von 0 bis einschließlich 3, die den relativen Aufwand angibt, der bei der Optimierung der Schaltung für die Hardware betrieben werden soll. Stufe 0 schaltet alle unnötigen Optimierungen aus; es werden nur die Transformationen verwendet, die erforderlich sind, um die Schaltung lauffähig zu machen. Auf der anderen Seite ermöglicht Stufe 3 eine ganze Reihe von Optimierungstechniken, von denen einige in der Kompilierungszeit sehr teuer sein können. Ähnlich wie bei klassischen Compilern ist auch bei Optimierungsstufe 3 nicht immer gewährleistet, dass die besten Ergebnisse erzielt werden. Qiskit ist standardmäßig auf die Optimierungsstufe 2 eingestellt, um einen Kompromiss zwischen der Kompilierungszeit und dem erwarteten Umfang der Optimierung zu finden.
generate_preset_pass_manager()Die Optimierungsstufe bestimmt, welche Implementierungen standardmäßig für eine bestimmte Stufe verwendet werden; dies kann jedoch durch die Übergabe expliziter <stage>_method="<choice>" Argumente an überschrieben werden.
Reproduzierbarkeit der voreingestellten Pipelines
Bei der Quantenkompilierung geht es häufig um die Lösung von Problemen, deren Komplexität bekanntermaßen nicht-polynomial ist und die daher global nicht optimierbar sind. In solchen Fällen sind stochastische und heuristische Algorithmen oft besser geeignet. Dies führt jedoch zu Problemen bei der Reproduzierbarkeit.
Die voreingestellten Pass-Manager enthalten fast immer stochastische, heuristisch basierte Pässe. Wenn Sie die Reproduzierbarkeit einer Kompilierung sicherstellen müssen, übergeben Sie eine bekannte ganze Zahl an das Argument seed_transpiler der Generatorfunktionen.
Alle in Qiskit integrierten Plugins müssen ihre Analysen erstellen und die DAGCircuit auf deterministische Weise ändern, sofern ihre Randomisierung (falls vorhanden) mit einem Startwert versehen ist, damit eine Kompilierung später wiederholt werden kann. Hier gibt es jedoch Einschränkungen:
- Alle eingebauten Durchläufe mit stochastischen Komponenten müssen eine Möglichkeit bieten, die Randomisierung zu setzen, und wenn sie gesetzt sind, müssen sie die Regeln der deterministischen Ausgabe einhalten.
- Alle integrierten Funktionen ohne stochastische Komponenten müssen die Regeln der deterministischen Ausgabe bei identischer Eingabe erfüllen. Es ist zulässig, aus Effizienzgründen einen Cache zu führen, doch bei identischen Eingabedaten müssen die Ergebnisse des Durchlaufs gleich sein, wenn dieser mehrfach aufgerufen wird – es sei denn, etwas außerhalb des Einflussbereichs des Durchlaufs verändert eine seiner Eingaben direkt an Ort und Stelle (beispielsweise verwendet der
BasisTranslatorstandardmäßig dieSessionEquivalenceLibraryin den voreingestellten Durchlauf-Managern, und es ist kein Fehler, wenn unterschiedliche Ergebnisse erzielt werden, wenn der Äquivalenzbibliothek neue Einträge hinzugefügt werden). Ein „Output“ ist alles, was der Pass zur weiteren Verwendung ausgibt; dies kann der explizitereturnWert des Passes sein, umfasst aber auch Eigenschaften, die für die spätere Verwendung in derPropertySet… vorgesehen sind. - Die Ausgabe eines Durchlaufs sollte auf einer bestimmten Maschine für eine bestimmte Version von Qiskit und eine eingefrorene Umgebung deterministisch sein, unabhängig davon, wie viele Threads für den Durchlauf verfügbar sind. Viele eingebaute Qiskit-Durchläufe verwenden Thread-Gleichzeitigkeit, und es ist nicht zulässig, dass sie je nach Anzahl der Threads ein unterschiedliches Verhalten aufweisen.
- Die Ausgabe eines Durchlaufs für einen festen Seed muss nicht gleich sein, wenn sich ein Teil der zugrundeliegenden Python Umgebung ändert (z. B. Aktualisierung eines abhängigen Pakets) oder wenn sich die mathematischen Systembibliotheken ändern (z. B. eine andere Implementierung von BLAS verfügbar ist).
- Die Ausgabe eines Durchlaufs für einen festen Seed muss nicht zwischen verschiedenen Betriebssystemen gleich sein (obwohl normalerweise die Implementierung der mathematischen Systembibliothek die Ursache für betriebssystembedingte Unterschiede ist).
- Es ist nicht erforderlich, dass die Ausgabe eines Durchlaufs für einen festen Seed auf zwei Rechnern mit unterschiedlichen CPU-Befehlen gleich ist; es wird erwartet, dass unterschiedliche Implementierungen von mathematischen Kerneln zu einem unterschiedlichen Verhalten führen können, wenn unterschiedliche CPU-Befehle verfügbar sind, wie z. B. fusionierte Multiplikations-Additions-Befehle mit unterschiedlichen Rundungseigenschaften im Vergleich zu zwei separaten Gleitkomma-Multiplikations- und Additionsbefehlen.
- Die Ausgabe eines Durchlaufs für einen festen Seed muss gleich sein, unabhängig von der Anzahl der Threads, die er verwenden darf, es sei denn, der Benutzer entscheidet sich ausdrücklich gegen dieses Verhalten. In den voreingestellten Passmanagern beispielsweise müssen die Sabre-Layout- und Routing-Methoden standardmäßig die gleiche Anzahl von Versuchen ausführen, unabhängig davon, ob nur ein einziger Thread erlaubt ist oder sogar mehr Threads als Versuche. Dieses Verhalten kann jedoch explizit außer Kraft gesetzt werden, indem die Umgebungsvariable
QISKIT_SABRE_ALL_THREADSso eingestellt wird, dass sie auf die Anzahl der Threads reagiert. - Alle oben genannten Regeln gelten auch zwischen getrennten Python Interpreter-Sitzungen, auch wenn
PYTHONHASHSEEDnicht explizit eingestellt wurde.
Im Allgemeinen sollte ein Nutzer der DAGCircuit davon ausgehen können, dass nach der Ausführung einer beliebigen Kombination aus integrierten, gegebenenfalls mit Startwerten versehenen Qiskit-Durchläufen mit festen Eingaben die genaue Ausgabe aller DAGCircuit() Methoden deterministisch ist. Dies gilt auch für die Reihenfolge der Ausgabe von Methoden, die keine Aussage über die Reihenfolge treffen; zwar kann man sich nicht auf die Semantik und die genaue Reihenfolge verlassen, wohl aber auf deren Determinismus bei festen Eingaben.
Autoren von Transpiler-Pässen sollten sich mit Zufälligkeit und Determinismus beschäftigen, um zu erfahren, wie man einen Transpiler-Pass deterministisch macht.
Auswahl voreingestellter Bühnenimplementierungen
Qiskit enthält mehrere Implementierungen der oben genannten Stufen, und weitere können als separate "Plugins" installiert werden. Um zu steuern, welche Implementierung einer Stufe verwendet wird, übergeben Sie deren Namen an das Schlüsselwortargument <stage>_method der beiden Funktionen, z. B. translation_method="translator". Weitere Informationen über die Implementierung solcher externen Plugins für eine Bühne finden Sie unter qiskit.transpiler.preset_passmanagers.plugin.
Um beispielsweise einen voreingestellten Durchlaufmanager auf Optimierungsebene 1 zu erzeugen, der explizit die Methode trivial für das Layout und die Methode sabre für das Routing verwendet, würden wir dies tun:
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.providers.fake_provider import GenericBackendV2
# Whatever backend you like:
backend = GenericBackendV2(num_qubits=5)
pass_manager = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
layout_method="trivial",
routing_method="sabre",
)Die für jede Stufe integrierten Plugins sind Teil der öffentlichen API von Qiskit und unterliegen allen Stabilitätsgarantien. Dazu gehören die übergeordneten logischen Auswirkungen dieser Methode (beispielsweise routing_method="sabre" wird stets ein von Sabre abgeleiteter Algorithmus verwendet). Der genaue interne Aufbau der Phase PassManager , die die Bühne darstellt, ist jedoch nicht festgelegt; die Reihenfolge der Durchläufe kann sich zwischen den Nebenversionen ändern, oder es können neue Durchläufe eingeführt werden.
Für jede Stufe, die über eine solche verfügt, ist die Methode mit der Bezeichnung "default" diejenige, die am meisten Änderungen unterworfen ist. Qiskit nimmt in der Regel nur vollständige algorithmische Änderungen an der Standardmethode über eine Hauptversionsgrenze hinweg vor, aber es kann Heuristiken neu ausbalancieren und neue Übergänge zu Standardmethoden zwischen Unterversionen hinzufügen.
StagedPassManagerDa die Ausgabe von generate_preset_pass_manager() ein ist, können Sie den Pass-Manager auch nach seiner Erstellung anpassen, um eine vollständig benutzerdefinierte Stage-Implementierung bereitzustellen. Wenn Sie beispielsweise eine benutzerdefinierte Planungsstufe mit dynamischer Entkopplung (unter Verwendung des PadDynamicalDecoupling pass) ausführen und zusätzlich vor dem Routing eine anfängliche logische Optimierung hinzufügen möchten, würden Sie (aufbauend auf dem vorherigen Beispiel) etwa wie folgt vorgehen:
import numpy as np
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.circuit import library as lib
from qiskit.transpiler import PassManager, generate_preset_pass_manager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
InverseCancellation,
PadDynamicalDecoupling,
)
backend = GenericBackendV2(num_qubits=5)
dd_sequence = [lib.XGate(), lib.XGate()]
scheduling_pm = PassManager(
[
ALAPScheduleAnalysis(target=backend.target),
PadDynamicalDecoupling(target=backend.target, dd_sequence=dd_sequence),
]
)
inverse_gate_list = [
lib.CXGate(),
lib.HGate(),
(lib.RXGate(np.pi / 4), lib.RXGate(-np.pi / 4)),
(lib.PhaseGate(np.pi / 4), lib.PhaseGate(-np.pi / 4)),
(lib.TGate(), lib.TdgGate()),
]
logical_opt = PassManager([InverseCancellation(inverse_gate_list)])
pass_manager = generate_preset_pass_manager(optimization_level=0)
# Add pre-layout stage to run extra logical optimization
pass_manager.pre_layout = logical_opt
# Set scheduling stage to custom pass manager
pass_manager.scheduling = scheduling_pmWenn nun der gestufte Pass-Manager über die run() Methode aufgerufen wird, wird der logical_opt Pass-Manager vor der layout Stufe aufgerufen, und für die scheduling Stufe wird der scheduling_pm Pass-Manager anstelle des Standard-Pass-Managers verwendet.
Wenn Sie benutzerdefinierte Stufen für die voreingestellten Pass-Manager konstruieren, können Sie einige der Hilfsfunktionen auf niedriger Ebene in qiskit.transpiler.preset_passmanagers nützlich sein.
Initialisierungsphase
Übergeordnete, benutzerorientierte Erklärung der init-Stufe im IBM Quantum-Leitfaden.
Diese init Stufe ist für übergeordnete, logische Optimierungen an abstrakten Schaltkreisen zuständig und dafür, Operationen mit mehreren Qubits (3+) auf eine Abfolge von Operationen mit einem oder zwei Qubits herunterzubrechen. Da es sich um den ersten Durchlauf handelt, ist die Eingabe eine vollständig abstrakte Schaltung. Die init Plattform muss benutzerdefinierte Gates sowie alle abstrakten Objekte zur Schaltungsbeschreibung auf hoher Ebene unterstützen, wie beispielsweise AnnotatedOperation.
Der Ausgang der Stufe init ist eine abstrakte Schaltung, die nur Ein- und Zwei-Qubit-Operationen enthält.
Beim Schreiben von Stage-Plugins ist der Einstiegspunkt für init qiskit.transpiler.init. Die eingebauten Plugins sind:
Methode | Zusammenfassung |
|---|---|
| Standard | Eingebautes Unrolling von Multi-Qubit-Operationen und abstrakte Optimierungen. |
Integriertes default Plugin
Auf Optimierungsstufe 0 wird keine abstrakte Optimierung durchgeführt. Das Standard-Plugin „entfaltet“ Operationen mit mehr als drei Qubits einfach, indem es auf deren hierarchische definition Felder zugreift.
Auf den Optimierungsebenen 1 und höher führt das Standard-Plugin auch eine einfache Annullierung benachbarter inverser Gatter durch, z. B. zweier hintereinanderliegender "cx"-Gatter.
Auf den Optimierungsstufen 2 und 3 ermöglicht das Standard-Plugin eine viel größere Bandbreite an abstrakten Optimierungen. Diese beinhalten:
- „Virtuelle Permutationsauslassung“ (siehe
ElidePermutations), bei der explizite, Permutationen hervorrufende Operationen entfernt und stattdessen als Neuzuordnung virtueller Qubits durchgeführt werden. - Analyse der Kommutierungsstruktur des IR, um Paare von Gattern zu finden, die sich aufheben lassen.
- Numerische Aufteilung von Zwei-Qubit-Operationen, die als eine Reihe von trennbaren Ein-Qubit-Operationen ausgedrückt werden können.
- Entfernung von nicht wahrnehmbaren Operationen, wie z. B. Pauli-Drehungen mit kleinem Winkel und diagonale Operationen unmittelbar vor den Messungen.
Layoutphase
Übergeordnete benutzerseitige Erklärung der Layout-Stufe im IBM Quantum-Leitfaden.
In der Layout-Phase wird eine erste Zuordnung zwischen den virtuellen Qubits der Eingangsschaltung und den Hardware-Qubits des Ziels vorgenommen. Dazu gehört die Erweiterung des Eingangsschaltkreises mit expliziten Ancillas, damit er so viele Qubits hat wie das Ziel, und die Umschreibung aller Operationen in Form von Hardware-Qubits. In anderen Toolkits oder in der Literatur wird dieses Problem auch als "Platzierungsproblem" bezeichnet.
PropertySetIn der Layout-Phase müssen die Eigenschaften layout und original_qubit_indices in der Pipeline festgelegt werden.
transpile()Alle integrierten Plugins für die Layoutphase geben einem expliziten Layout Vorrang, das über das initial_layout Argument von generate_preset_pass_manager() oder ausgewählt wurde.
An jedem beliebigen Punkt eines Schaltkreises können wir eine Zuordnung zwischen den derzeit aktiven "virtuellen" Qubits des Eingangsschaltkreises und den Hardware-Qubits des Backends herstellen. Ein Hardware-Qubit kann immer nur ein einziges virtuelles Qubit an einem bestimmten Punkt repräsentieren, aber die Zuordnung kann sich im Laufe des Schaltkreises ändern. Im Prinzip könnten einige virtuelle Qubits nicht an allen Punkten der Schaltungsausführung abgebildet werden, wenn die Lebensdauer eines virtuellen Qubit-Zustands verkürzt werden kann, obwohl die in Qiskit eingebauten Pipelines dies derzeit nicht nutzen.
Die Layout-Stufe ist nicht dafür verantwortlich, dass die Konnektivität des Ziels auf dem gesamten Weg durch die Schaltung eingehalten wird oder dass alle Operationen für die direkte Ausführung auf dem Ziel gültig sind; dies sind die Aufgaben der Routing- bzw. Übersetzungsstufen.
Die Wahl des Ausgangslayouts ist einer der wichtigsten Faktoren, die sich auf die Qualität der Ausgangsschaltung auswirken. Die Layout-Phase ist oft die rechenintensivste Phase in den Standard-Pipelines; das Standard-Plugin für Layout probiert sogar mehrere verschiedene Algorithmen aus (ausführlicher beschrieben in Eingebautes Standard-Plugin ).
Im Idealfall sollte in der Layout-Phase ein „perfektes“ Layout gefunden werden, bei dem alle Operationen die Konnektivitätsbeschränkungen des Schaltkreises Target einhalten, sodass die Routing-Phase entfällt. Bei beliebigen Eingangsschaltungen ist dies in der Regel nicht möglich; ist es jedoch der Fall, kann der VF2Layout Durchlauf genutzt werden, um ein gültiges Anfangslayout zu finden. Werden mehrere perfekte Layouts gefunden, wird anhand einer auf geschätzten Fehlerquoten basierenden Bewertungsheuristik entschieden, welches davon verwendet wird.
In allen integrierten Plugins führt die Übergabe des generate_preset_pass_manager() Arguments initial_layout dazu, dass das angegebene Layout unverändert verwendet wird, wobei die individuelle „Auswahl“-Logik übersprungen wird. Alle integrierten Plugins übernehmen zudem die Einbettung der Schaltung über die gesamte Breite des Geräts, einschließlich der Zuweisung von Ancillas.
Wenn Sie Ihr eigenes Layout-Plugin schreiben, finden Sie vielleicht generate_embed_passmanager() nützlich sein, um die "Einbettungsphase" der Layoutanwendung zu automatisieren.
Beim Schreiben von Stage-Plugins ist der Einstiegspunkt für layout qiskit.transpiler.layout. Die eingebauten Plugins sind:
Methode | Zusammenfassung |
|---|---|
| Standard | Auf der höchsten Optimierungsstufe wird versucht, ein perfektes Layout zu finden, und dann ein kombinierter Durchlauf von Layout und Routing auf Sabre-Basis versucht. |
| dicht | Findet den dichtesten Untergraphen (in Bezug auf die Qubit-Verbindungsgrade) des Backends, der als Anfangsqubits verwendet werden soll. |
| Unbedeutend | Ordnet das virtuelle Qubit 0 dem physischen Qubit 0 zu und so weiter. |
| säbel | Verwendet den erweiterten Sabre-Layout-Algorithmus von Qiskit. |
Auf allen Optimierungsebenen ist die Standard-Layout-Methode default, obwohl sich die Struktur dieser Phase je nach Ebene drastisch ändert.
Integriertes default Plugin
Eine Verschmelzung mehrerer verschiedener Layouttechniken.
Auf Optimierungsebene 0 wird das triviale Layout gewählt.
Bei Optimierungsgraden über 0 gibt es einen zweistufigen Prozess:
- Versuchen Sie zunächst, mithilfe
VF2Layoutvon ein „perfektes“ Layout zu finden. Die maximale Anzahl der Aufrufe des Isomorphismus-Auswerters steigt mit dem Optimierungsgrad. Bei sehr großen, komplexen Zielen können wir nicht garantieren, dass wir perfekte Layouts finden, selbst wenn diese existieren; die Wahrscheinlichkeit steigt jedoch mit dem Optimierungsgrad. - Wenn kein optimales Layout gefunden werden kann, wählen Sie
SabreLayoutmit ein Anfangslayout aus, wobei die Anzahl der Versuche für das Anfangslayout, die Swap-Map-Versuche und die Vorwärts-Rückwärts-Iterationen mit steigendem Optimierungsgrad zunimmt.
Darüber hinaus wird in der Optimierungsstufe 1 aus Gründen der historischen Rückwärtskompatibilität auch das triviale Layout vor der Version VF2-based ausprobiert.
Integriertes dense Plugin
Verwendet den DenseLayout Pass, um das Layout auszuwählen. Dieser Durchlauf ermittelt den am dichtesten verbundenen Teilgraphen des vollständigen Zielkonnektivitätsgraphen, wobei „am dichtesten“ bedeutet, dass Hardware-Qubits mit der größten Anzahl verfügbarer Verbindungen bevorzugt werden. Die Zuordnung von virtuellen zu Hardware-Qubits wird dadurch vervollständigt, dass die virtuellen Qubits mit dem höchsten Grad den Hardware-Qubits mit dem höchsten Grad zugeordnet werden.
Dies ist eine relativ kostengünstige Heuristik zur Auswahl eines Anfangslayouts, liefert jedoch in der Regel eine deutlich schlechtere Ausgabequalität als Sabre-basierte Methoden. Das Standard-Layout-Plugin verwendet die von DenseLayout ausgewählte anfängliche Zuordnung als eines seiner Anfangslayouts, um den Sabre-Algorithmus zu initialisieren.
Integriertes trivial Plugin
Verwendet den TrivialLayout Pass, um das Layout auszuwählen. Dies ist die einfachste Zuordnung, bei der jedes virtuelle Qubit dem Hardware-Qubit mit dem gleichen Index zugeordnet wird, sodass das virtuelle Qubit 0 dem Hardware-Qubit 0 zugeordnet wird und so weiter.
Diese Methode ist am nützlichsten für Hardware-Charakterisierungsexperimente, bei denen die eingehende "abstrakte" Schaltung bereits in voller Breite auf dem Gerät vorhanden ist, ihre Operationen physikalischen Operationen entsprechen und der Transpiler nur aufgerufen wird, um die Erstellung einer physikalischen Schaltung zu formalisieren QuantumCircuit.
Integriertes sabre Plugin
Verwendet das SabreLayout zur Auswahl eines Ausgangslayouts, wobei der von Qiskit modifizierte Sabre-Routing-Algorithmus als Unterprogramm verwendet wird, um die in Frage kommende Schaltung sowohl vorwärts als auch rückwärts zu swap-mappen.
Zusammenfassend lässt sich sagen, dass die Layoutkomponente des ursprünglichen Sabre-Algorithmus ein Ausgangslayout willkürlich auswählt und dann versucht, es zu "verbessern", indem sie das Routing auf der Schaltung ausführt, die Schaltung umkehrt und das Routing auf der umgekehrten Schaltung mit der vorherigen "endgültigen" Virtual-Hardware-Zuordnung als Ausgangszustand ausführt. Die konfigurierte Optimierungsebene entscheidet, wie viele Iterationen dieses Hin und Her durchgeführt werden und wie viele verschiedene zufällige Ausgangslayouts ausprobiert werden.
Der Hauptunterschied zur Standardstufe bei anderen Optimierungsstufen als 0 besteht darin, dass dieses Plugin nur den Sabre-basierten Algorithmus ausführt. Es wird weder versucht, ein perfektes Layout zu finden, noch ein triviales Layout zu erstellen.
Routing-Phase
Übergeordnete, benutzerseitige Erklärung der Routing-Stufe im IBM Quantum-Leitfaden.
Die Routing-Stufe stellt sicher, dass der virtuelle Konnektivitätsgraph der Schaltung mit dem Hardware-Konnektivitätsgraph des Ziels kompatibel ist. Vereinfacht ausgedrückt, sorgt die Routing-Stufe dafür, dass alle Zwei-Qubit-Gatter in der Schaltung auf Hardware-Qubits abgebildet werden, die eine definierte Zwei-Qubit-Operation im Ziel-ISA haben. In anderen Toolkits oder in der Literatur wird dieses Problem auch als "Mapping"- oder "Swap-Mapping"-Problem bezeichnet.
Routing-Algorithmen tun dies in der Regel, indem sie swap Gatter in die Schaltung einfügen und die virtuelle Zuordnung der Qubits zur Hardware im Laufe der Schaltungsausführung ändern.
In der Routing-Phase muss nicht sichergestellt werden, dass alle Gatter in der Schaltung für die Ziel-ISA gültig sind. SwapGateBeispielsweise kann ein Routing-Plugin wörtliche swap Gatter in der Schaltung belassen, auch wenn diese Target keine enthält. Es muss jedoch für jedes Paar von Hardware-Qubits, auf das im Schaltkreis ein Gatter angewendet wird, mindestens ein Zwei-Qubit-Gatter im Schaltkreis Target definiert sein.
Die Routing-Phase muss die final_layout Eigenschaften und virtual_permutation_layout in der PropertySet festlegen, falls ein Routing stattgefunden hat.
Alle in Qiskit integrierten Routing-Stufen führen nach dem Routing zusätzlich den VF2PostLayout „pass“-Schritt aus. Dies könnte zu einer Neuzuweisung des ursprünglichen Layouts führen, sofern Qubits mit geringerer Fehlerrate gefunden werden können. Dieser Durchlauf ähnelt stark der VF2Layout Klasse, die das Standard-Layout-Plugin verwendet, mit dem Unterschied, dass VF2PostLayout wir hier garantieren können, dass es mindestens einen isomorphen induzierten Teilgraphen der Zieltopologie gibt, der mit der Schaltungstopologie übereinstimmt.
Die in Qiskit eingebauten Routing-Plugins gehen im Allgemeinen davon aus, dass alle Qubit-Paare mit einer definierten Zwei-Qubit-Verbindung einen universellen Satz von Gattern haben, die für diese beiden Qubits definiert sind. Die Hardware muss sich nicht unbedingt daran halten (wenn z. B. das einzige definierte Zwei-Qubit-Gatter swap ist, dann können Verschränkungsoperationen wie cx nicht realisiert werden), aber Qiskit berücksichtigt diese Möglichkeit noch nicht.
Es ist bekannt, dass die Bestimmung der minimalen Anzahl einzufügender Vertauschungen ein nicht-polynomiales Problem darstellt. Das bedeutet, dass der Versuch unerschwinglich teuer wäre; daher sind viele der in Qiskit integrierten Algorithmen stochastisch, und es kann zu starken Abweichungen zwischen verschiedenen Kompilierungen kommen. transpile()Wenn Sie Reproduzierbarkeit benötigen, stellen Sie sicher, dass Sie das seed_transpiler Argument auf generate_preset_pass_manager() oder setzen.
Beim Schreiben von Stage-Plugins ist der Einstiegspunkt für routing qiskit.transpiler.routing. Die eingebauten Plugins sind:
Methode | Zusammenfassung |
|---|---|
| Standard | Verwenden Sie eine von Qiskit gewählte Standard-Routing-Methode. |
| säbel | Standard. Verwendet den modifizierten Sabre-Routing-Algorithmus von Qiskit zum Tauschen von Karten. |
| keine | Routing deaktivieren. Gibt einen Fehler aus, wenn Routing erforderlich ist. |
| grundlegend | Greedy Swap-Insertion, um jeweils nur einen einzigen Vorgang weiterzuleiten. |
| Vorgriff | Breadth-First-Suche mit heuristischem Pruning, um Swaps zu finden, die Gates ausführbar machen. |
Integriertes default Plugin
Verwenden Sie eine von Qiskit ausgewählte Standardmethode für das Routing. VF2PostLayoutAb Qiskit 2.0 entspricht der gewählte Algorithmus dem des integrierten Sabre-Plugins, obwohl in der Praxis in der Regel das integrierte Standard-Plugin für die Layout-Phase den Sabre-basierten Routing-Algorithmus ausführt und die Routing-Phase lediglich zur Ausführung von … verwendet wird.
Integriertes none Plugin
Ein Dummy-Plugin, mit dem das Routing vollständig deaktiviert werden kann. Dies kann gelegentlich für Experimente zur Hardwarekonfiguration oder in bestimmten Sonderfällen der Teilkompilierung nützlich sein.
Integriertes basic Plugin
Verwendet den BasisSwap greedy swap-insertion-Algorithmus. Dies ist konzeptionell sehr einfach: Für jeden Vorgang in topologischer Reihenfolge sind die kürzesten Wege einzufügen, die erforderlich sind, um die Verbindung auf dem Gerät ausführbar zu machen.
Der Optimierungsgrad wirkt sich lediglich auf den Arbeitsaufwand aus, den der VF2PostLayout Schritt betreibt, um das ursprüngliche Layout nach der Routing-Phase zu verbessern.
Diese Methode hat in der Regel eine schlechte Ausgabequalität.
Integriertes lookahead Plugin
Verwendet den LookaheadSwap Algorithmus zur Routenberechnung. Im Wesentlichen handelt es sich hierbei um eine Breitensuche zur Erzeugung eines Tauschnetzwerks, bei der der zu durchsuchende Baum auf jeder Ebene auf eine kleine Anzahl von Tauschkandidaten reduziert wird.
Dieser Algorithmus ähnelt der basic Heuristik des "sabre"-Plugins, mit dem Unterschied, dass er auch die folgenden Auswirkungen jedes Tausches bis zu einer geringen Tiefe berücksichtigt.
Der Optimierungsgrad beeinflusst die Suchtiefe, den Umfang des „Per-Depth-Pruning“ sowie den Arbeitsaufwand, der VF2PostLayout zur Nachoptimierung des anfänglichen Layouts erforderlich ist.
In der Praxis läuft das "sabre"-Plugin um mehrere Größenordnungen schneller und liefert bessere Ergebnisse.
Integriertes sabre Plugin
Verwendet den SabreSwap Algorithmus zur Routenberechnung. Hierbei kommt die von Qiskit weiterentwickelte Version des ursprünglichen Sabre-Routing-Algorithmus zum Einsatz.
Dieser Routing-Algorithmus läuft mit Thread-Parallelität, um mehrere verschiedene Möglichkeiten für das Routing zu prüfen und diejenige zu wählen, die die Anzahl der eingefügten Swaps minimiert.
Der Optimierungsgrad beeinflusst, wie viele verschiedene Zufallsstartwerte für die vollständige Routing-Berechnung ausprobiert werden, sowie den Aufwand, der VF2PostLayout für die Nachoptimierung des ursprünglichen Layouts betrieben wird.
Dies ist fast immer das leistungsfähigste integrierte Plugin und dasjenige, das Qiskit standardmäßig in allen Fällen verwendet, in denen ein Routing erforderlich ist.
Übersetzungsphase
Erklärung der Übersetzungsphase
Übergeordnete, benutzerorientierte Erklärung der Übersetzungsphase im IBM Quantum-Leitfaden.
Die Übersetzungsphase ist dafür verantwortlich, alle Gatter in der Schaltung in solche umzuschreiben, die vom Ziel-ISA unterstützt werden. Wenn beispielsweise für die Hardware-Qubits 0 und 1 eine cx angefordert wird, der ISA aber nur eine cz Operation für diese Qubits enthält, muss die Übersetzungsstufe einen Weg finden, das cx Gatter mit Hilfe des cz und der verfügbaren Ein-Qubit-Gatter darzustellen.
Die Übersetzungsphase wird aufgerufen, bevor die Optimierungsphase beginnt. Optimierungs-Plugins (einschließlich der in Qiskit integrierten Plugins) können die Übersetzungsstufe auch als "Korrektur"-Stufe nach der Optimierungsschleife verwenden, wenn die Optimierungsschleife eine Schaltung liefert, die Nicht-ISA-Gatter enthält. Letztere Situation ist ziemlich häufig; die Optimierungsschleife ist möglicherweise nur mit der Minimierung von Eigenschaften wie "Anzahl der Zwei-Qubit-Gatter" beschäftigt und hinterlässt ihre Ausgabe in Form von lokal äquivalenten Gattern, die die Übersetzungsstufe leicht umschreiben kann, ohne die Zieleigenschaften der Optimierung zu beeinflussen. Dies ermöglicht eine einfachere Trennung der Anliegen zwischen den beiden Phasen. Einige Optimierungs-Plugins sind möglicherweise strenger in ihrer Ausgabe, so dass diese Nachbearbeitung der Übersetzungsphase möglicherweise nicht mehr notwendig ist.
Beim Schreiben von Stage-Plugins ist der Einstiegspunkt für translation qiskit.transpiler.translation. Die eingebauten Plugins sind:
Methode | Zusammenfassung |
|---|---|
| Standard | Verwenden Sie eine von Qiskit gewählte Standardübersetzungsmethode. |
| Umsetzer | Symbolische Übersetzung von Gattern in die Zielbasis unter Verwendung bekannter Äquivalenzen. |
| synthese | Sammeln Sie jeden Lauf von Ein- und Zwei-Qubit-Gattern in einer Matrixdarstellung und resynthetisieren Sie von dort aus. |
Integriertes default Plugin
Verwenden Sie eine von Qiskit gewählte Standardmethode für die Übersetzung. Ab Qiskit 2.0 ist dies dasselbe wie das Plugin "Eingebauter Übersetzer", aber der gewählte Algorithmus kann sich während der 2.x Serie ändern, entweder für alle Ziele oder nur für bestimmte Klassen von Zielen.
Integriertes synthesis Plugin
Fassen Sie die Gate-Sequenzen auf denselben Qubits in Matrixform zusammen und führen Sie anschließend eine Neusynthese unter Verwendung des UnitarySynthesis Passes (mit der konfigurierten unitary_synthesis_method) durch. Dies entspricht weitgehend der Optimierungsschleife selbst bei hohen Optimierungsstufen.
Die Erfassung in Matrizen ist in der Regel teurer als matrixfreie Übersetzungen, aber die Qualität der Übersetzungen kann im Prinzip besser sein. In der Praxis erfordert dies einen auf die Ziel-ISA zugeschnittenen Synthesealgorithmus, was diese Methode weniger allgemein macht als andere Methoden. Es kann qualitativ hochwertigere Ergebnisse liefern, wenn es auf einfache ISAs abzielt, die mit den Syntheseroutinen in Qiskit übereinstimmen.
Wenn diese Methode verwendet wird, brauchen Sie die Optimierungsschleife möglicherweise nicht.
Die Optimierungsstufe hat keinen Einfluss auf dieses Plugin.
Integriertes translator Plugin
Verwendet den BasisTranslator Algorithmus, um Gatter symbolisch in die Zielbasis zu übersetzen. Grob gesagt geht man dabei von der Menge der von der Schaltung benötigten Gatter aus und wendet Regeln aus einer vorgegebenen EquivalenceLibrary (in der Regel der SessionEquivalenceLibrary) an, um zur ISA zu gelangen.
Dies ist die Standardübersetzungsmethode.
Die Optimierungsstufe hat keinen Einfluss auf dieses Plugin.
Optimierungsphase
Erklärung der Optimierungsphase
Übergeordnete, benutzerorientierte Erklärung der Optimierungsphase im IBM Quantum-Leitfaden.
Die Optimierungsphase dient der hardwarenahen Low-Level-Optimierung. Im Gegensatz zur Init-Stufe ist der Eingang zu dieser Stufe eine Schaltung, die bereits ISA-kompatibel ist, so dass ein Low-Level-Optimierungs-Plugin für einen bestimmten ISA zugeschnitten werden kann.
An ein Optimierungs-Plugin werden nur sehr wenige Anforderungen gestellt, abgesehen davon, dass es ISA-unterstützte Schaltungen als Eingabe entgegennimmt und ISA-unterstützte Schaltungen als Ausgabe zurückgibt. Ein Optimierungs-Plugin enthält häufig eine Schleife, wie beispielsweise die DoWhileController, und kann die konfigurierte Übersetzungsstufe als Korrektur-Pipeline einbeziehen.
Die in Qiskit eingebauten Optimierungs-Plugins sind allgemein und lassen sich gut auf die meisten realen ISAs für nicht-fehlerkorrigierte Geräte anwenden. Die eingebauten Plugins sind weniger gut für ISAs geeignet, die kein kontinuierlich parametrisiertes Ein-Qubit-Gate haben.
Beim Schreiben von Stage-Plugins ist der Einstiegspunkt für optimization qiskit.transpiler.optimization. Die eingebauten Plugins sind:
Methode | Zusammenfassung |
|---|---|
| Standard | Ein Standardsatz von Optimierungsdurchläufen. Dies ist je nach Optimierungsgrad sehr unterschiedlich. |
Integriertes default Plugin
Dies hängt stark vom Optimierungsgrad ab.
Die Einzelheiten dieser Pipeline können sich zwischen den verschiedenen Qiskit-Versionen ändern. Die allgemeinen Grundsätze werden im Folgenden beschrieben.
Bei Optimierungsstufe 0 ist die Bühne leer.
Auf der Optimierungsstufe 1 führt die Stufe eine matrixbasierte Resynthese von Läufen von Ein-Qubit-Gattern und eine sehr einfache symbolische inverse Annullierung von Zwei-Qubit-Gattern durch, wenn diese nacheinander auftreten. Dies läuft in einer Schleife ab, bis die Größe und Tiefe des Schaltkreises festgelegt sind.
Auf der Optimierungsebene 2 enthält die Schleife zusätzlich zu den Optimierungen der Ebene 1 eine Kommutierungsanalyse von Gattersätzen, um den Bereich der Gatter, die für eine Annullierung in Frage kommen, zu erweitern. Vor der Schleife werden die Läufe von Ein- und Zwei-Qubit-Gattern einer einzigen matrixbasierten Resynthese unterzogen.
Auf Optimierungsebene 3 läuft die Zwei-Qubit-Matrix-basierte Resynthese innerhalb der Optimierungsschleife. Die Bedingung der Optimierungsschleife probiert auch mehrere Durchläufe aus und wählt bei schwankendem Output den Minimalpunkt; dies ist notwendig, weil die matrixbasierte Resynthese in Bezug auf konkrete Gates relativ instabil ist.
Die Optimierungsstufe 3 ist in der Regel sehr teuer für große Schaltungen.
Planungsphase
Ein Leitfaden zur Erläuterung von Planungskonzepten.
Die Scheduling-Stufe ist, falls gewünscht, für die Einfügung expliziter Delay anweisungen, um Leerlaufzeiten von Qubits explizit zu machen. Plugins können optional wandzeitabhängige Transformationen durchführen, wie z. B. das Einfügen dynamischer Entkopplungssequenzen.
Der Eingang zur Scheduling-Stufe ist eine ISA-kompatible Schaltung. Der Ausgang der Scheduling-Stufe muss ebenfalls eine ISA-kompatible Schaltung sein, mit expliziten Delay anweisungen, die gegebenenfalls die Timing-Informationen der Hardware erfüllen.
PropertySetIn der Planungsphase sollte die node_start_time Eigenschaft in der Pipeline festgelegt werden.
Beim Schreiben von Stage-Plugins ist der Einstiegspunkt für scheduling qiskit.transpiler.scheduling. Die eingebauten Plugins sind:
Methode | Zusammenfassung |
|---|---|
| Standard | Es wird versucht, die Anforderungen an die zeitliche Abstimmung zu erfüllen, ohne dass eine andere Terminierung erfolgt. |
| alap | Planen Sie die Schaltung so, dass der Betrieb so spät wie möglich erfolgt. |
| asap | Planen Sie die Schaltung so, dass sie so schnell wie möglich durchgeführt werden kann. |
Integriertes default Plugin
Nichts tun, es sei denn, die Schaltung enthält bereits Anweisungen mit eindeutigen Zeitangaben. Wenn es in der Schaltung explizit zeitgesteuerte Operationen gibt, fügen Sie zusätzliche Auffüllungen ein, um sicherzustellen, dass diese Timings den Ausrichtungs- und anderen Hardwarebeschränkungen entsprechen.
Integriertes alap Plugin
Planen Sie alle Vorgänge explizit nach dem Prinzip „so spät wie möglich“ ein. Hierbei wird der ALAPScheduleAnalysis Algorithmus verwendet, um zu entscheiden, wo die Gatter platziert werden sollen.
Integriertes asap Plugin
Planen Sie alle Vorgänge explizit nach der Strategie „so bald wie möglich“ ein. Hierbei wird der ASAPScheduleAnalysis Algorithmus verwendet, um zu entscheiden, wo die Gatter platziert werden sollen.
Benutzerdefinierte Passmanager
Neben der Anpassung voreingestellter Pass-Manager ist es auch möglich, einen eigenen Pass-Manager zu erstellen, um eine vollständig benutzerdefinierte Pipeline zur Transformation von Eingangsschaltungen aufzubauen. Dazu kannst du die StagedPassManager Klasse direkt verwenden. Sie können beliebige Stufennamen definieren und diese mit einer PassManager Instanz füllen. Der folgende Code erstellt beispielsweise ein neues StagedPassManager Element mit zwei Phasen: init und translation.
from qiskit.transpiler.passes import (
UnitarySynthesis,
Collect2qBlocks,
ConsolidateBlocks,
UnitarySynthesis,
Unroll3qOrMore,
)
from qiskit.transpiler import PassManager, StagedPassManager
basis_gates = ["rx", "ry", "rxx"]
init = PassManager([UnitarySynthesis(basis_gates, min_qubits=3), Unroll3qOrMore()])
translate = PassManager(
[
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates),
UnitarySynthesis(basis_gates),
]
)
staged_pm = StagedPassManager(
stages=["init", "translation"], init=init, translation=translate
)Die Anzahl der Stufen, die Sie in eine StagedPassManager. einfügen können, ist unbegrenzt. Die Stufen müssen nicht mit den Stufen übereinstimmen, die in den voreingestellten Pipelines von Qiskit verwendet werden.
Die Funktionen des Stage-Generators können bei der Erstellung benutzerdefinierter StagedPassManager Instanzen hilfreich sein. Sie generieren Pass-Manager, die allgemeine Funktionen bereitstellen, die in vielen Phasen zum Einsatz kommen. Beispielsweise erzeugt dies ein PassManager , generate_embed_passmanager() um ein ausgewähltes Initial Layout aus einem Layout-Durchlauf in das angegebene Zielgerät „einzubetten“.
Schreiben von benutzerdefinierten Transpiler-Durchläufen
Qiskit ist so konzipiert, dass es mit benutzerdefinierten, spezialisierten Transpiler-Passes erweitert werden kann.
DAGCircuit``PropertySetEs gibt zwei Arten von Transpiler-Durchläufen: „Analyse“-Durchläufe (AnalysisPass), die eine Schaltung einlesen und globale Analyseeigenschaften in die schreiben; sowie „Transformations“-Durchläufe (TransformationPass), die entweder eine DAGCircuit an Ort und Stelle ändern oder eine neue zurückgeben. In der Vergangenheit hat Qiskit versucht, diese beiden Arten strikt voneinander zu trennen. TransformationPassIm modernen Qiskit ist es jedoch eher üblich, sowohl Analysen als auch Modifikationen in einem eigenständigen Programm zusammenzufassen, anstatt zu versuchen, alles aufzuteilen. Wenn Ihr Ansatz rein analytisch ist, ist die Verwendung von AnalysisPass… dennoch angebracht.
Allgemeine Grundsätze für die Urheberschaft von Pässen
TransformationPassDAGCircuitWenn Sie eine Datei ändern oder eine neue erstellen möchten, müssen Sie eine Datei schreiben. AnalysisPassDAGCircuitWenn Sie nur in die PropertySet schreiben und die nicht ändern möchten, sollten Sie eine schreiben. Wenn du beides machen möchtest, schreibe ein TransformationPass. BasePass.run()In beiden Fällen ist die einzige erforderliche Methode, die den Kern Ihres Durchlaufs bildet. Dies sollte ein einzelnes Argument (außer self) akzeptieren, dag: DAGCircuit. NoneAnalysisPassTranspilerPassWenn a gilt, sollte die Funktion a DAGCircuit zurückgeben (was die Eingabe sein kann, falls sie an Ort und Stelle geändert wurde), während sie bei a zurückgeben sollte.
Wenn Ihr Pass einen Initialisierer hat, müssen Sie super().__init__() aufrufen.
In der Regel sollte Ihr Pass in seinem Initialisierer einen Target akzeptieren, der die Quantenhardware beschreibt, für die Sie kompilieren. Von der Verwendung „lockerer“ Einschränkungen wie einer separaten Kopplungskarte und einer Liste von Basisgates wird abgeraten; die Target beschreibt allgemeine heterogene Hardware zutreffender.
Während der Ausführung einer PassManager Pipeline können Sie, wenn die run() Methode Ihres Durchlaufs aufgerufen wird, auf das Attribut self.property_set zugreifen, um den aktuellen PropertySet Status der Transpilierung abzurufen. Sie sollten die Daten direkt daraus lesen und dorthin schreiben. Ihr Pass sollte eindeutig dokumentieren, welche Attribute – falls vorhanden – in der Eigenschaftsmenge enthalten sind, aus der er liest und in die er schreibt.
Zufälligkeit und Determinismus
Bei der Quantenkompilierung werden häufig Probleme gelöst, die sich nur schwer global optimieren lassen. In diesen Fällen sind stochastische und heuristische Algorithmen oft besser geeignet. Dies führt jedoch zu Problemen bei der Reproduzierbarkeit.
Es gibt keine formale Anforderung, dass ein benutzerdefinierter Pass deterministisch sein muss, und zwar nach genau denselben Regeln, die auch für integrierte Qiskit-Pässe gelten. Wir empfehlen Ihnen jedoch dringend, diese Regeln in Ihren eigenen Pässen zu befolgen. Die Wissenschaft lebt von der Reproduzierbarkeit, und die Fehlersuche ist ein Albtraum, wenn man ein zuvor beobachtetes Verhalten nicht reproduzieren kann.
Beim Schreiben eines Transpiler-Passes können Sie sich darauf verlassen, dass die folgenden (repräsentativen und nicht vollständigen) Beispiele deterministisch sind, auch wenn die genaue Semantik der Reihenfolge möglicherweise nicht vollständig spezifiziert ist und die Passes sich nicht auf eine bestimmte Reihenfolge verlassen sollten:
- Die Befehlsknoten kommen in
DAGCircuit.op_nodes()… vor. Im Gegensatz dazutopological_op_nodes()enthält standardmäßig einen Sortierschlüssel, durch den die Reihenfolge völlig unabhängig von der Reihenfolge der Knotenentfernungen bzw. -einfügungen bleibt; somit ist vollständig deterministisch, sofern dieselbe Menge an Knoten mit demselben Datenfluss angegeben wird, selbst wenn diese in einer anderen Reihenfolge aufgebaut wurde. - Die Reihenfolgenränder treten in
DAGCircuit.edges()… auf. DAGCircuit.collect_2q_runs()Die Reihenfolge, in der die Durchläufe zurückgegeben werden, und die genaue Reihenfolge, in der die Knoten in einem Durchlauf auftauchen.- Die Reihenfolge, in der Knoten bei ordnungsentarteten Verfahren wie
predecessors(),bfs_successors(), usw. auftauchen.
Generell gilt, dass alle Änderungen an den Schaltkreisen in genau derselben Reihenfolge vorgenommen werden müssen. Wenn beispielsweise Knoten hinzugefügt, verkleinert oder entfernt werden sollen, muss die Reihenfolge dieser Änderungen deterministisch sein, und die Ersetzungen müssen deterministisch angegeben werden.
Einige Tipps, um dies zu gewährleisten, sind:
-
Seien Sie sehr vorsichtig, wenn Sie über Hash-basierte Container iterieren. Die Iteration über Python 's
setist aufgrund der Hash-Seed-Randomisierung nicht-deterministisch. In Rust ist die Iteration über die Standard-Bibliotheks-Hash-basierten Container, einschließlichhashbrownÄquivalente mit ihren Standard-Hashern nicht-deterministisch.AnmerkungDie Iteration über Python 's
dictist deterministisch und garantiert in der Reihenfolge des Einfügens, wenn keine Entnahmen stattgefunden haben, und in beliebiger, aber immer noch deterministischer Reihenfolge, wenn deterministische Entnahmen stattgefunden haben.Wenn Sie in Python eine
setzu erstellen und dann darüber zu iterieren, sollten Sie stattdessen einedictzu verwenden, wobei alle Werte durchNoneersetzt werden. Die Verwendung einessetfür reine Mitgliedschaftstests ist kein Problem.In Rust verwenden Sie
indexmapund seine StrukturenIndexMapundIndexSetals Ersatz fürHashMapbzw.HashSet; sie haben ähnliche deterministische Iterationseigenschaften wie die von Pythondict. -
Wenn Ihr Pass stochastische Komponenten enthält, stellen Sie sicher, dass Sie eine
seedEingabe akzeptieren, und sorgen Sie dafür, dass Ihre Ausgabe rein ist, wenn diese als Ganzzahl übergeben wird. In der Regel bedeutet dies, den Startwert zu speichern und zu Beginn jedes Aufrufs vonBasePass.run().ein neuespRNG-Objekt aus diesem Startwert zu instanziieren. -
Wenn Sie Parallelität mit Threads verwenden, achten Sie darauf, dass Ihre Ausgabe nicht von der Reihenfolge abhängt, in der die Threads ihre Arbeit erledigen oder ihre Teilergebnisse zurückgeben. Wenn Sie z. B. die Arbeit auf einen Thread-Pool verteilen und die Ergebnisse am Ende sammeln, stellen Sie sicher, dass die Ausgabe in einer der Eingabe entsprechenden Reihenfolge angeordnet ist. In Python stellen Funktionen wie
concurrent.futures.ThreadPoolExecutor.map()dies sicher. In ähnlicher Weise sammeln in Rust die parallelen Iteratoren vonrayonihre Ausgabe in der gleichen Reihenfolge wie die Eingabe.Beachten Sie, dass parallele _reductions_, wie z. B. "wende eine Funktion auf jedes Element in diesem Iterator an und wähle dasjenige, das eine bestimmte Metrik minimiert", im Falle von Entartungen in der Metrik typischerweise sehr anfällig für threaded non-determinism sind. Wenn z. B. zwei Elemente im Iterator eine ungleiche Ausgabe erzeugen, die jedoch denselben Vergleichsschlüssel hat, ist die in einer threaded Umgebung gewählte Ausgabe nicht deterministisch. Um dies zu vermeiden, sollten Sie einen deterministischen Tie-Breaker anwenden, um die Entartung aufzuheben, z. B. durch Aufzählung der Eingaben und Verwendung der Sequenznummer als Tie-Breaker-Schlüssel, so dass bei zwei Elementen mit der gleichen Punktzahl zuverlässig dasjenige ausgewählt wird, das einer früheren Eingabe entspricht.
Quantum Computer darstellen
Um einen Code QuantumCircuit für ein bestimmtes Backend kompilieren zu können, benötigt der Transpiler eine spezielle Darstellung dieses Backends, einschließlich seiner Einschränkungen, seines Befehlssatzes, der Eigenschaften der Qubits und weiterer Aspekte, um den Code effektiv kompilieren und optimieren zu können. Die BackendV2 Klasse definiert zwar eine Schnittstelle für die Abfrage von und die Interaktion mit Backends, ihr Anwendungsbereich geht jedoch über die reinen Anforderungen des Transpilers hinaus und umfasst unter anderem die Verwaltung der Jobübermittlung sowie möglicherweise die Anbindung an Remote-Dienste. Die vom Transpiler benötigten spezifischen Informationen werden durch die Target Klasse beschrieben
Um beispielsweise ein einfaches Target Objekt zu erstellen, kann man schrittweise Beschreibungen der von ihm unterstützten Befehle hinzufügen:
from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate
target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
{
(0,): InstructionProperties(error=.00001, duration=5e-8),
(1,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RZGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RYGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RXGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
CZGate(),
{
(1, 2): InstructionProperties(error=.0001, duration=5e-7),
(2, 0): InstructionProperties(error=.0001, duration=5e-7)
}
)
target.add_instruction(
Measure(),
{
(0,): InstructionProperties(error=.001, duration=5e-5),
(1,): InstructionProperties(error=.002, duration=6e-5),
(2,): InstructionProperties(error=.2, duration=5e-7)
}
)
print(target)Target
Number of qubits: 3
Instructions:
cx
(0, 1):
Duration: 5e-07 sec.
Error Rate: 0.0001
u
(0,):
Duration: 5e-08 sec.
Error Rate: 1e-05
(1,):
Duration: 6e-08 sec.
Error Rate: 2e-05
rz
(1,):
Duration: 5e-08 sec.
Error Rate: 1e-05
(2,):
Duration: 6e-08 sec.
Error Rate: 2e-05
ry
(1,):
Duration: 5e-08 sec.
Error Rate: 1e-05
(2,):
Duration: 6e-08 sec.
Error Rate: 2e-05
rx
(1,):
Duration: 5e-08 sec.
Error Rate: 1e-05
(2,):
Duration: 6e-08 sec.
Error Rate: 2e-05
cz
(1, 2):
Duration: 5e-07 sec.
Error Rate: 0.0001
(2, 0):
Duration: 5e-07 sec.
Error Rate: 0.0001
measure
(0,):
Duration: 5e-05 sec.
Error Rate: 0.001
(1,):
Duration: 6e-05 sec.
Error Rate: 0.002
(2,):
Duration: 5e-07 sec.
Error Rate: 0.2Dies Target stellt ein 3-Qubit-Backend dar, das zwischen den Qubits 0 und 1, UGate auf den Qubits 0 und 1, RZGate, RXGate, sowie RYGate auf den Qubits 1 und 2, CZGate zwischen den Qubits 1 und 2 sowie zwischen den Qubits 2 und 0 und Measure auf allen Qubits unterstützt CXGate .
Es gibt außerdem spezielle Datenstrukturen, um eine bestimmte Teilmenge von Informationen aus der Target… darzustellen. Beispielsweise dient die CouplingMap Klasse ausschließlich dazu, die Konnektivitätsbeschränkungen eines Backends als gerichteten Graphen darzustellen. Eine Kopplungskarte kann aus einem Target mithilfe der Target.build_coupling_map() Methode erstellt werden. Diese Datenstrukturen stammen in der Regel aus der Zeit vor der Einführung Target der Klasse, werden jedoch nach wie vor von einigen Transpiler-Durchläufen verwendet, die noch nicht nativ mit einer Target Instanz dieser Klasse arbeiten, oder im Umgang mit Backends, die nicht die neueste BackendV2 Schnittstelle nutzen.
Wenn wir beispielsweise die CouplingMap für das oben genannte 3-Qubit-Beispiel Target visualisieren wollten:
from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate
target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
{
(0,): InstructionProperties(error=.00001, duration=5e-8),
(1,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RZGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RYGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RXGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
CZGate(),
{
(1, 2): InstructionProperties(error=.0001, duration=5e-7),
(2, 0): InstructionProperties(error=.0001, duration=5e-7)
}
)
target.add_instruction(
Measure(),
{
(0,): InstructionProperties(error=.001, duration=5e-5),
(1,): InstructionProperties(error=.002, duration=6e-5),
(2,): InstructionProperties(error=.2, duration=5e-7)
}
)
target.build_coupling_map().draw()CZGateDies verdeutlicht die globale Konnektivität von, Target die sich aus der Kombination der unterstützten Qubits für CXGate und ergibt. CouplingMap.build_coupling_map()Um die einzelnen Verbindungen anzuzeigen, können Sie den Namen der Operation an folgende Funktion übergeben:
from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate
target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
{
(0,): InstructionProperties(error=.00001, duration=5e-8),
(1,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RZGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RYGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RXGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
CZGate(),
{
(1, 2): InstructionProperties(error=.0001, duration=5e-7),
(2, 0): InstructionProperties(error=.0001, duration=5e-7)
}
)
target.add_instruction(
Measure(),
{
(0,): InstructionProperties(error=.001, duration=5e-5),
(1,): InstructionProperties(error=.002, duration=6e-5),
(2,): InstructionProperties(error=.2, duration=5e-7)
}
)
target.build_coupling_map('cx').draw()from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate
target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
{
(0,): InstructionProperties(error=.00001, duration=5e-8),
(1,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RZGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RYGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RXGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
CZGate(),
{
(1, 2): InstructionProperties(error=.0001, duration=5e-7),
(2, 0): InstructionProperties(error=.0001, duration=5e-7)
}
)
target.add_instruction(
Measure(),
{
(0,): InstructionProperties(error=.001, duration=5e-5),
(1,): InstructionProperties(error=.002, duration=6e-5),
(2,): InstructionProperties(error=.2, duration=5e-7)
}
)
target.build_coupling_map('cz').draw()Planung von Schaltkreisen
So konfigurieren Sie die Planungsstufen der voreingestellten Passmanager.
Nachdem die Schaltung in die Zielbasis übersetzt, auf das Gerät abgebildet und optimiert wurde, kann eine Planungsphase durchgeführt werden, um optional die gesamte Leerlaufzeit der Schaltung zu berücksichtigen. Auf einer hohen Ebene kann man sich das Scheduling so vorstellen, dass Verzögerungen in den Schaltkreis eingefügt werden, um die Leerlaufzeit der Qubits zwischen der Ausführung von Befehlen zu berücksichtigen. Wenn wir zum Beispiel mit einer Schaltung wie der folgenden beginnen:
können wir dann transpile() mit der Einstellung scheduling_method aufrufen:
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2
backend = GenericBackendV2(5)
ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0,range(1,5))
circ = transpile(ghz, backend, scheduling_method="asap")
circ.draw(output='mpl')
Sie können hier sehen, dass der Transpiler Delay anweisungen eingefügt hat, um die Leerlaufzeit auf jedem Qubit zu berücksichtigen. Um eine bessere Vorstellung vom Timing der Schaltung zu bekommen, können wir sie auch mit der Funktion timeline.draw() betrachten:
Die Planung einer Schaltung umfasst zwei Teile: die Analyse und die Zuordnung der Randbedingungen, gefolgt von einem Auffüllungsdurchlauf. Im ersten Schritt muss ein Scheduling-Analyse-Durchlauf wie ALAPSchedulingAnalysis oder ASAPSchedulingAnalysis durchgeführt werden, der die Schaltung analysiert und die Startzeit jedes Befehls in der Schaltung unter Verwendung eines Scheduling-Algorithmus („so spät wie möglich“ für ALAPSchedulingAnalysis und „so früh wie möglich“ für ASAPSchedulingAnalysis) aus dem Eigenschaftssatz aufzeichnet. Sobald für die Schaltung eine erste Terminierung vorliegt, können weitere Durchläufe durchgeführt werden, um etwaige zeitliche Einschränkungen des Ziel-Backends, wie beispielsweise Ausrichtungsbeschränkungen, zu berücksichtigen. Dies geschieht in der Regel mit dem ConstrainedReschedule Pass, der die im Eigenschaftssatz festgelegte Planung an die Einschränkungen des Ziel-Backends anpasst. Sobald die gesamte Planung sowie alle Anpassungen und Umplanungen abgeschlossen sind, wird ein Padding-Durchlauf, wie beispielsweise PadDelay oder PadDynamicalDecoupling , durchgeführt, um die Befehle in die Schaltung einzufügen, womit die Planung abgeschlossen ist.
Transpiler-API
Hardwarebeschreibung
Kolumne „ 1 “ | Kolumne „ 2 “ |
|---|---|
Target( [Beschreibung, num_qubits, dt,...] ) | Der Zweck des Target Objekts ist es, den Compiler von Qiskit über die Einschränkungen eines bestimmten Backends zu informieren, damit der Compiler eine Eingabeschaltung zu etwas kompilieren kann, das funktioniert und für ein Gerät optimiert ist. |
InstructionProperties( [Dauer, Fehler] ) | Eine Darstellung der Eigenschaften einer Gatterimplementierung. |
WrapAngleRegistry() | Registrierung der Funktion Angle Wrapping |
Definition des Pass-Managers
Kolumne „ 1 “ | Kolumne „ 2 “ |
|---|---|
StagedPassManager( [Phasen] ) | Eine aus einzelnen Stufen aufgebaute Passmanager-Pipeline. |
PassManager( [Durchläufe, max_iteration] ) | Manager für eine Reihe von Pässen und deren Planung während der Umsetzung. |
PassManagerConfig( [initial_layout,...] ) | Pass Manager Konfiguration. |
PassManagerCliffordTConfig( [initial_layout,...] ) | Pass-Manager-Konfiguration für die Clifford+T-Transpilierung. |
generate_preset_pass_manager([...]) | Voreinstellung erstellen PassManager |
generate_preset_clifford_t_pass_manager([...]) | StagedPassManagerEine Clifford+T-Voreinstellung erstellen. |
generate_preset_pbc_pass_manager([...]) | StagedPassManagerEine voreingestellte PBC erstellen. |
Layout und Topologie
Kolumne „ 1 “ | Kolumne „ 2 “ |
|---|---|
Layout( [input_dict] ) | Zwei-Wege-Diktat zur Darstellung eines Layouts. |
CouplingMap( [Kopplungsliste, Beschreibung] ) | Gerichtetes Diagramm, das die feste Kopplung angibt. |
TranspileLayout(initial_layout,...[,...] ) | Layout-Attribute für die Ausgangsschaltung des Transpilers. |
Zeitplanung
Kolumne „ 1 “ | Kolumne „ 2 “ |
|---|---|
InstructionDurations( [Unterrichtsdauer, dt] ) | Helper-Klasse zur Bereitstellung der Dauer von Anweisungen für die Terminplanung. |
Abstrakte Pässe
Kolumne „ 1 “ | Kolumne „ 2 “ |
|---|---|
TransformationPass(*args, **kwargs) | Ein Transformationsdurchgang: DAG ändern, nicht Eigenschaftssatz. |
AnalysisPass(*args, **kwargs) | Ein Analysedurchlauf: Eigenschaftssatz ändern, nicht DAG. |
Ausnahmen
TranspilerError
exception qiskit.transpiler.TranspilerError(*message)
Basen: TranspilerAccessError
Ausnahmen, die bei der Transpilierung auftreten.
Legen Sie die Fehlermeldung fest.
TranspilerAccessError
exception qiskit.transpiler.TranspilerAccessError(*message)
Basen: PassManagerError
DEPRECATED: Ausnahme des Zugriffsfehlers in den Transpilerpässen.
Legen Sie die Fehlermeldung fest.
CouplingError
exception qiskit.transpiler.CouplingError(*msg)
Basen: QiskitError
Basisklasse für Fehler, die durch das Kopplungsgraphenobjekt ausgelöst werden.
Legen Sie die Fehlermeldung fest.
LayoutError
exception qiskit.transpiler.LayoutError(*msg)
Basen: QiskitError
Fehler, die durch das Layout-Objekt ausgelöst werden.
Legen Sie die Fehlermeldung fest.
CircuitTooWideForTarget
exception qiskit.transpiler.CircuitTooWideForTarget(*message)
Basen: TranspilerError
Fehler wird ausgelöst, wenn der Kreislauf zu breit für das Ziel ist.
Legen Sie die Fehlermeldung fest.
InvalidLayoutError
exception qiskit.transpiler.InvalidLayoutError(*message)
Basen: TranspilerError
Fehler, der ausgelöst wird, wenn ein vom Benutzer angegebenes Layout ungültig ist.
Legen Sie die Fehlermeldung fest.
Optimierungsmetrik
Kolumne „ 1 “ | Kolumne „ 2 “ |
|---|---|
OptimizationMetric(*Werte) | Bei der Transpilierung berücksichtigte Optimierungsmetrik. |