Skip to main content
IBM Quantum Platform

Anbieter-Schnittstelle

qiskit.providers

Dieses Modul enthält die Klassen, die zum Erstellen externer Anbieter für Qiskit verwendet werden. Ein Provider ist alles, was einen externen Dienst für Qiskit bereitstellt. Ein typisches Beispiel hierfür ist ein Backend-Anbieter, der Objekte bereitstellt Backend , die zur Ausführung QuantumCircuit von Objekten verwendet werden können. Dieses Modul enthält die abstrakten Klassen, die zur Definition der Schnittstelle zwischen einem Anbieter und Qiskit verwendet werden.


Versionsunterstützung

Jede abstrakte Klasse der Anbieterschnittstelle wird individuell versioniert. Wenn wir eine Änderung an einer Schnittstelle vornehmen müssen, wird eine neue abstrakte Klasse erstellt, um die neue Schnittstelle zu definieren. Die Abwärtskompatibilität dieser Schnittstellenänderungen zwischen den Versionen ist nicht gewährleistet.

Versionsänderungen

Jede Nebenversion von qiskit kann die Version einer beliebigen Backend-Schnittstelle um eine einzige Versionsnummer erhöhen. Es handelt sich um eine Zusammenfassung aller Schnittstellenänderungen für diese Version auf dieser Schnittstelle.

Versionsunterstützungsrichtlinie

Um den Anbietern Zeit zu geben, sich auf Änderungen an dieser Schnittstelle einzustellen, wird Qiskit mehrere Versionen jeder Klasse auf einmal unterstützen. In Anbetracht der Tatsache, dass es nur eine Version pro Release gibt, ist die Politik der Versionsveralterung etwas konservativer als die Standardpolitik der Veralterung. Qiskit unterstützt eine Version der Anbieterschnittstelle für mindestens 3 Nebenversionen oder die erste Version nach 6 Monaten ab der Version, mit der eine Version eingeführt wurde, je nachdem, welcher Zeitraum länger ist, bevor sie möglicherweise veraltet ist. Danach gilt für diese Schnittstellenversion die standardmäßige Veralterungspolitik. Dies gibt Anbietern und Nutzern ausreichend Zeit, sich auf mögliche Änderungen der Schnittstelle einzustellen. Sagen wir zum Beispiel, dass in 0.19.0 BackendV2 eingeführt wird und in den 3 Monaten nach der Veröffentlichung von 0.19.0 veröffentlichen wir 0.20.0, 0.21.0 und 0.22.0, dann 7 Monate nach 0.19.0 veröffentlichen wir 0.23.0. In 0.23.0 können wir BackendV2 verwerfen, und es muss weiterhin unterstützt werden und kann nicht entfernt werden, bis die Verwerfungspolitik abgeschlossen ist.

Es ist erwähnenswert, dass die Versionsunterstützungspolitik von Qiskit nicht bedeutet, dass die Anbieter selbst dieselbe Unterstützungsgeschichte haben werden. Sie können (und sollten wohl auch) auf neuere Versionen aktualisieren, sobald sie es können, das Unterstützungsfenster gilt nur für die von Qiskit unterstützten Versionen. Ein Teil dieses langen Zeitfensters vor der Verwerfung besteht darin, den Anbietern genügend Zeit zu geben, ihre eigene Verwerfung einer potenziell für den Endbenutzer relevanten Änderung in einem für den Benutzer sichtbaren Teil der Schnittstelle vorzunehmen, bevor sie ihre Version verschieben. Nehmen wir zum Beispiel an, dass wir die Signatur auf Backend.run() in BackendV34 in einer nicht abwärtskompatiblen Weise geändert haben. Bevor Aer seine AerSimulator klasse auf die Version 34 umstellen kann, muss die alte Signatur vor der Umstellung veraltet sein. Die Umstellung von Aer erfolgt nicht garantiert im Gleichschritt mit Qiskit, so dass wir sicherstellen müssen, dass Aer genügend Zeit hat, seinen Abkündigungszyklus abzuschließen, bevor die Version 33 entfernt wird (d. h. Version 34 wird obligatorisch/zur Mindestversion).


Abstrakte Klassen

Back-End

Kolumne „ 1 “
Kolumne „ 2 “
Backend()Gemeinsamer Basistyp für alle versionierten abstrakten Backend-Klassen.
BackendV2( [Anbieter, Name, Beschreibung,...] )Abstrakte Klasse für Backends
QubitProperties( [t1, t2, Frequenz] )Eine Darstellung eines QubitProperties Objekts.

Optionen

Kolumne „ 1 “
Kolumne „ 2 “
Options(**kwargs)Objekt Basisoptionen

Job

Kolumne „ 1 “
Kolumne „ 2 “
Job()Gemeinsamer Basistyp für alle abstrakten Klassen von Versioned Job.
JobV1(Backend, Job_ID, *kwargs)Klasse zur Bearbeitung von Aufträgen

Jobstatus

Kolumne „ 1 “
Kolumne „ 2 “
JobStatus(*Werte)Klasse für den Jobstatus als Aufzählungstyp.

Ausnahmen

QiskitBackendNotFoundError

exception qiskit.providers.QiskitBackendNotFoundError(*message)

GitHub

Basen: QiskitError

Basisklasse für Fehler, die bei der Suche nach einem Backend auftreten.

Legen Sie die Fehlermeldung fest.

JobError

exception qiskit.providers.JobError(*message)

GitHub

Basen: QiskitError

Basisklasse für von Jobs ausgelöste Fehler.

Legen Sie die Fehlermeldung fest.

JobTimeoutError

exception qiskit.providers.JobTimeoutError(*message)

GitHub

Basen: JobError

Basisklasse für von Jobs ausgelöste Timeout-Fehler.

Legen Sie die Fehlermeldung fest.


Ein neues Backend schreiben

Wenn Sie über ein Quantengerät oder einen Simulator verfügen, den Sie in Qiskit integrieren möchten, müssen Sie ein Backend schreiben. Ein Provider ist eine Sammlung von Backends und stellt Qiskit eine Methode zur Verfügung, um verfügbare BackendV2 Objekte abzurufen. Das BackendV2 Objekt liefert sowohl Informationen zur Beschreibung eines Backends als auch zu dessen Funktionsweise, transpiler sodass Schaltkreise so kompiliert werden können, dass sie optimiert sind und auf dem Backend ausgeführt werden können. Außerdem stellt es die run() Methode bereit, mit der die QuantumCircuit Objekte ausgeführt werden können. Dadurch können Nutzer und andere Qiskit-APIs die Ergebnisse der Ausführung von Schaltkreisen auf Geräten auf einheitliche Weise abrufen, unabhängig davon, wie das Backend implementiert ist. Im Großen und Ganzen sind die grundlegenden Schritte zum Erstellen eines Providers folgende:

  • Implementieren Sie eine Provider Klasse, die den Zugriff auf das/die Backend(s) verwaltet.

  • Implementieren Sie eine BackendV2 Unterklasse und deren run() Methode.

    • Fügen Sie der Sitzungsinstanz EquivalenceLibrary beliebige benutzerdefinierte Gates für die Basis des Backends hinzu.
  • Implementieren Sie eine JobV1 Unterklasse, die die Interaktion mit einem laufenden Job übernimmt.

Ein einfaches Beispiel für einen Provider ist der qiskit-aqt-provider

Einbindung

Eine Anbieterklasse dient einem einzigen Zweck: dem Abruf von Backend-Objekten, die die Ausführung von Schaltungen auf einem Gerät oder Simulator ermöglichen. Es wird erwartet, dass alle erforderlichen Anmeldeinformationen und/oder Authentifizierungen bei der Initialisierung eines Anbieterobjekts behandelt werden. Das Provider-Objekt bietet dann eine Liste von Backends sowie Methoden zum Filtern und Abrufen von Backends (ggf. unter Verwendung der angegebenen Anmeldeinformationen). Ein Beispiel für eine Anbieterklasse sieht so aus:

from qiskit.providers.providerutils import filter_backends

from .backend import MyBackend

class MyProvider:

    def __init__(self, token=None):
        super().__init__()
        self.token = token
        self.backends = [MyBackend(provider=self)]

    def backends(self, name=None, **kwargs):
        if name:
            backends = [
                backend for backend in backends if backend.name() == name]
        return filter_backends(backends, filters=filters, **kwargs)

Stellen Sie sicher, dass alle für die Authentifizierung erforderlichen Informationen (falls erforderlich) in der Klasse vorhanden sind und dass die Methode des Backends der erforderlichen Schnittstelle entspricht. Wie dies konkret umgesetzt wird, liegt im Ermessen des jeweiligen Anbieters.

Back-End

Die Backend-Klassen bilden das Herzstück des Providers. Diese Klassen bilden die Schnittstelle zwischen Qiskit und der Hardware oder dem Simulator, auf dem die Schaltungen ausgeführt werden. Dazu gehört auch, dem Compiler die erforderlichen Informationen zur Beschreibung eines Backends bereitzustellen, damit er beliebige Schaltungen für das Backend einbetten und optimieren kann. Jedes Backend-Objekt muss vier Elemente enthalten: eine target Eigenschaft zur Definition des Backend-Modells für den Compiler, eine max_circuits Eigenschaft zur Festlegung einer Obergrenze für die Anzahl der Schaltkreise, die das Backend in einem einzelnen Batch-Job ausführen kann (falls keine Obergrenze None vorhanden ist, kann verwendet werden), eine run() Methode zur Annahme von Job-Einreichungen sowie eine _default_options Methode zur Definition der vom Benutzer konfigurierbaren Optionen und ihrer Standardwerte. Ein einfaches Beispiel, das funktioniert, könnte zum Beispiel so aussehen:

from qiskit.providers import BackendV2 as Backend
from qiskit.transpiler import Target
from qiskit.providers import Options
from qiskit.circuit import Parameter, Measure
from qiskit.circuit.library import PhaseGate, SXGate, UGate, CXGate, IGate


class Mybackend(Backend):

    def __init__(self):
        super().__init__()

        # Create Target
        self._target = Target("Target for My Backend")
        # Instead of None for this and below instructions you can define
        # a qiskit.transpiler.InstructionProperties object to define properties
        # for an instruction.
        lam = Parameter("λ")
        p_props = {(qubit,): None for qubit in range(5)}
        self._target.add_instruction(PhaseGate(lam), p_props)
        sx_props = {(qubit,): None for qubit in range(5)}
        self._target.add_instruction(SXGate(), sx_props)
        phi = Parameter("φ")
        theta = Parameter("ϴ")
        u_props = {(qubit,): None for qubit in range(5)}
        self._target.add_instruction(UGate(theta, phi, lam), u_props)
        cx_props = {edge: None for edge in [(0, 1), (1, 2), (2, 3), (3, 4)]}
        self._target.add_instruction(CXGate(), cx_props)
        meas_props = {(qubit,): None for qubit in range(5)}
        self._target.add_instruction(Measure(), meas_props)
        id_props = {(qubit,): None for qubit in range(5)}
        self._target.add_instruction(IGate(), id_props)

        # Set option validators
        self.options.set_validator("shots", (1, 4096))
        self.options.set_validator("memory", bool)

    @property
    def target(self):
        return self._target

    @property
    def max_circuits(self):
        return 1024

    @classmethod
    def _default_options(cls):
        return Options(shots=1024, memory=False)

    def run(self, circuits, **kwargs):
        # serialize circuits submit to backend and create a job
        for kwarg in kwargs:
            if not hasattr(self.options, kwarg):
                warnings.warn(
                    "Option %s is not used by this backend" % kwarg,
                    UserWarning, stacklevel=2)
        options = {
            'shots': kwargs.get('shots', self.options.shots),
            'memory': kwargs.get('memory', self.options.memory),
        }
        job_json = convert_to_wire_format(circuit, options)
        job_handle = submit_to_backend(job_json)
        return MyJob(self.job_handle, job_json, circuit)

Transpiler-Schnittstelle des Backends

Das Wesentliche an dem Backend Objekt ist, wie es sich dem Compiler gegenüber beschreibt. Dies wird über die Target Klasse abgewickelt, die ein Modell eines Backends für den Transpiler definiert. Ein Backend-Objekt muss über das target Attribut ein Target Objekt zurückgeben, das die transpile() Funktion als Modell für ein Backend-Ziel bei der Kompilierung verwendet.

Benutzerdefinierte Basis-Gates

  1. Falls Ihr Backend keine Gates aus der Qiskit-Schaltkreisbibliothek (qiskit.circuit.library) verwendet, können Sie die entsprechende Unterstützung in Ihren Provider integrieren. Die grundlegende Vorgehensweise hierfür besteht darin, zunächst für jedes benutzerdefinierte Tor im Basissatz eine Gate Unterklasse zu definieren. Zum Beispiel:

    import numpy as np
    
    from qiskit.circuit import Gate
    from qiskit.circuit import QuantumCircuit
    
    class SYGate(Gate):
        def __init__(self, label=None):
            super().__init__("sy", 1, [], label=label)
    
        def _define(self):
            qc = QuantumCircuit(1)
            qc.ry(np.pi / 2, 0)
            self.definition = qc

    Wichtig ist, dass das Namensattribut Ihres benutzerdefinierten Gates (der erste Parameter auf super().__init__() in der obigen __init__ Definition) für alle benutzerdefinierten Gates in Ihrem Backend-Basis-Set nicht mit dem Namen eines anderen Gates kollidiert. Das Attribut name wird verwendet, um das Gatter in der Basismenge für den Transpiler zu identifizieren. Wenn es einen Konflikt gibt, weiß der Transpiler nicht, welches Tor er verwenden soll.

  2. Fügen Sie das benutzerdefinierte Gate zum Ziel für Ihr Backend hinzu. Dies kann mit der Target.add_instruction() Methode erfolgen. Sie müssen eine Instanz von SYGate und deren Parameter zum Ziel hinzufügen, damit der Transpiler weiß, dass sie existiert. Nehmen wir zum Beispiel an, dies ist Teil Ihrer BackendV2 Implementierung für Ihr Backend:

    from qiskit.transpiler import InstructionProperties
    
    sy_props = {
        (0,): InstructionProperties(duration=2.3e-6, error=0.0002),
        (1,): InstructionProperties(duration=2.1e-6, error=0.0001),
        (2,): InstructionProperties(duration=2.5e-6, error=0.0003),
        (3,): InstructionProperties(duration=2.2e-6, error=0.0004),
    }
    self.target.add_instruction(SYGate(), sy_props)

    Die Schlüssel in sy_props definieren die Qubits, auf denen das Backend SYGate verwendet werden kann, und die Werte definieren die Eigenschaften von SYGate auf diesem Qubit. Bei Multiqubit-Gattern enthalten die Tupel-Schlüssel alle Qubit-Kombinationen, auf die das Gatter wirkt (die Reihenfolge ist wichtig, d. h. (0, 1) unterscheidet sich von (1, 0)).

  3. Nachdem Sie die benutzerdefinierten Gates festgelegt haben, die für den Basissatz des Backends verwendet werden sollen, müssen Sie der Standard-Äquivalenzbibliothek Äquivalenzregeln hinzufügen, damit die transpile() Funktion und transpiler das Modul eine beliebige Schaltung unter Verwendung des benutzerdefinierten Basissatzes konvertieren können. Dies lässt sich erreichen, indem man für Standardgatter äquivalente Schaltungen in Bezug auf das benutzerdefinierte Gatter definiert. In der Regel sollte es ausreichen, wenn Sie von einem CXGate (sofern Ihre Basis kein Standard-2-Qubit-Gatter enthält) sowie einigen häufig verwendeten Einzel-Qubit-Rotationsgattern wie dem HGate und UGate dem umwandeln können, damit der Transpiler jede Schaltung in die Gatter der benutzerdefinierten Basis übersetzen kann. Je mehr Äquivalenzregeln jedoch von Standardgattern auf Ihre Basis definiert werden, desto effizienter wird die Übersetzung von einer beliebigen Schaltung in die Zielbasis sein (wenn auch nicht immer, und der Ertragsrückgang nimmt ab).

    HGateWenn Sie beispielsweise einige Regeln für die oben genannte benutzerdefinierte SYGate Einstellung hinzufügen möchten, könnten wir die U2Gate und definieren:

    from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
    from qiskit.circuit.library import HGate
    from qiskit.circuit.library import ZGate
    from qiskit.circuit.library import RZGate
    from qiskit.circuit.library import U2Gate
    
    
    # H => Z SY
    q = qiskit.QuantumRegister(1, "q")
    def_sy_h = qiskit.QuantumCircuit(q)
    def_sy_h.append(ZGate(), [q[0]], [])
    def_sy_h.append(SYGate(), [q[0]], [])
    SessionEquivalenceLibrary.add_equivalence(
        HGate(), def_sy_h)
    
    # u2 => Z SY Z
    phi = qiskit.circuit.Parameter('phi')
    lam = qiskit.circuit.Parameter('lambda')
    q = qiskit.QuantumRegister(1, "q")
    def_sy_u2 = qiskit.QuantumCircuit(q)
    def_sy_u2.append(RZGate(lam), [q[0]], [])
    def_sy_u2.append(SYGate(), [q[0]], [])
    def_sy_u2.append(RZGate(phi), [q[0]], [])
    SessionEquivalenceLibrary.add_equivalence(
        U2Gate(phi, lam), def_sy_u2)

    Sie sollten dafür sorgen, dass dies beim Import ausgeführt wird, damit es sofort nach dem Import des Pakets des Anbieters ausgeführt wird. Dadurch wird sichergestellt, dass bei jeder Ausführung des BasisTranslator Durchlaufs mit den benutzerdefinierten Gates die Äquivalenzregeln definiert sind.

    Es ist außerdem zu beachten, dass je nach der von Ihnen verwendeten Basis einige Optimierungsschritte im Transpiler, wie beispielsweise Optimize1qGatesDecomposition, möglicherweise nicht mit Ihrer benutzerdefinierten Basis funktionieren. In unserem SYGate Beispiel wird es Optimize1qGatesDecomposition nicht möglich sein, Folgen von Einzel-Qubit-Gattern in die SY-Basis zu vereinfachen. Das liegt daran, dass die OneQubitEulerDecomposer Klasse nicht weiß, wie sie in der SY-Basis arbeiten soll. SYGateUm dieses Problem zu lösen, müsste die SYGate Klasse zu Qiskit hinzugefügt und OneQubitEulerDecomposer so aktualisiert werden, dass sie die Zerlegung in die unterstützt. Langfristig ist dies wahrscheinlich der bessere Weg für benutzerdefinierte Basisgatter, und durch die Bereitstellung der Definitionen und der Unterstützung im Transpiler wird sichergestellt, dass diese auch in Zukunft von Qiskit gut unterstützt werden.

Benutzerdefinierte Transpiler-Durchläufe

Der Transpiler unterstützt die Möglichkeit, dass Backends eigene Implementierungen von Transpiler-Stufen bereitstellen, um hardwarespezifische Optimierungen und Schaltungsumformungen zu ermöglichen. Derzeit werden zwei Phasen unterstützt, get_translation_stage_plugin() und get_scheduling_stage_plugin() , die es einem Backend ermöglichen, Zeichenfolgen als Plugin-Namen anzugeben, die jeweils als Standardphase für die Übersetzung bzw. die Planung verwendet werden sollen. Diese Hook-Punkte in einer BackendV2 Klasse können verwendet werden, wenn Ihr Backend Anforderungen an die Kompilierung stellt, die vom aktuellen Backend bzw.Target der aktuellen Schnittstelle nicht erfüllt werden. Bitte erwägen Sie auch, ein GitHub-Issue einzureichen, in dem Sie Ihren Anwendungsfall beschreiben, da Interesse daran besteht, diese Schnittstellen zu verbessern, um weitere Hardwarearchitekturen detaillierter abbilden zu können.

Um diese Hakenpunkte zu nutzen, musst du lediglich die Methoden zu deiner BackendV2 Implementierung hinzufügen und dafür sorgen, dass sie einen Plugin-Namen als String zurückgeben. Zum Beispiel:

class Mybackend(BackendV2):

    def get_scheduling_stage_plugin(self):
        return "SpecialDD"

    def get_translation_stage_plugin(self):
        return "BasisTranslatorWithCustom1qOptimization"

Dieser Ausschnitt einer Backend-Implementierung lässt nun die transpile() funktion das Plugin SpecialDD für die Planungsphase und das Plugin BasisTranslatorWithCustom1qOptimization für die Übersetzungsphase verwenden, wenn das Ziel auf Mybackend gesetzt ist. Beachten Sie, dass Benutzer diese Auswahl überschreiben können, indem sie ausdrücklich einen anderen Plugin-Namen auswählen. Damit diese Schnittstelle funktioniert, müssen für den zurückgegebenen Plugin-Namen Transpiler-Stage-Plugins implementiert sein. Sie können sich in der qiskit.transpiler.preset_passmanagers.plugin moduldokumentation finden Sie Einzelheiten zur Implementierung von Plugins. Wenn Ihr Backend benutzerdefinierte Übergänge als Teil einer Kompilierungsphase benötigt, wird das Anbieterpaket in der Regel die Plugins für die Transpilerphase enthalten, die diese Übergänge verwenden. Dies ist jedoch nicht erforderlich, und es kann jede gültige Methode (von einer eingebauten Methode oder einem externen Plugin) verwendet werden.

Wenn diese beiden Kompilierungsschritte für die Ausführung oder eine effiziente Ausgabe auf Mybackend erforderlich sind, kann der Transpiler diese benutzerdefinierten Schritte ohne manuelle Benutzereingabe durchführen.

Echtzeitvariablen

Der Transpiler behandelt automatisch in Echtzeit typisierte klassische Variablen (siehe qiskit.circuit.classical) und behandelt die Store anweisung als eine eingebaute "Direktive" behandeln, ähnlich wie Barrier. Hierfür ist keine besondere Behandlung durch Backends erforderlich.

Falls Ihr Backend keine klassischen Variablen und Speicher verwalten kann, empfehlen wir Ihnen, dies in Ihrer Dokumentation zu vermerken und eine Überprüfung in Ihre run() Methode einzufügen (siehe „ Backend.run -Methode“ ), um Schaltkreise, die diese enthalten, sofort abzulehnen. Sie können prüfen QuantumCircuit.num_vars , ob Variablen auf der obersten Ebene vorhanden sind. QuantumCircuit.num_declared_varsWenn Sie Kontrollflussoperationen zulassen, müssen Sie möglicherweise das Innere blocks jeder einzelnen rekursiv nach lokal im Geltungsbereich definierten Variablen durchsuchen.

Zum Beispiel eine Funktion, die das Vorhandensein von manuellen Speicherplätzen oder manuelle Speicherungen im Speicher überprüft:

from qiskit.circuit import Store, ControlFlowOp, QuantumCircuit

def has_realtime_logic(circuit: QuantumCircuit) -> bool:
    if circuit.num_vars:
        return True
    for instruction in circuit.data:
        if isinstance(instruction.operation, Store):
            return True
        elif isinstance(instruction.operation, ControlFlowOp):
            for block in instruction.operation.blocks:
                if has_realtime_logic(block):
                    return True
    return False

Winkelgrenzen für Gates

TargetFalls Ihr Backend Einschränkungen hinsichtlich der zulässigen Parameterwerte für ein beliebiges Gatter im Zielsystem vorsieht, können Sie dies durch Winkelgrenzen für das modellieren. Wenn Sie die Anweisung mit dem add_instruction() hinzufügen, können Sie das angle_bounds Schlüsselwort-Argument verwenden, das eine Liste von Tupeln für die obere und untere Grenze des Parameters eines Gatters entgegennimmt.

Dieser Codeausschnitt beispielsweise anstelle des Beispiels, bei dem im obigen Beispiel das PhaseGate hinzugefügt wird:

lam = Parameter("λ")
p_props = {(qubit,): None for qubit in range(5)}
self._target.add_instruction(PhaseGate(lam), p_props, angle_bounds=[(0, math.pi)])

legt die Grenzen von PhaseGate auf den Bereich zwischen 0 und π\pi (einschließlich) fest. PhaseGateDies modelliert die Winkelbeschränkung in Bezug Target auf die Winkelwerte für den lam Parameter. Der WrapAngles Transpiler-Durchlauf dient dazu, alle Werte PhaseGate außerhalb der festgelegten Winkelgrenzen umzuwandeln. DAGCircuitSie müssen eine Funktion schreiben, die die Winkelwerte für das Tor als Eingabe entgegennimmt und einen Wert zurückgibt. Zum Beispiel:

from qiskit.transpiler.passes import WrapAngles

def fold_phase(angles: List[float], qubits: List[int]) -> DAGCircuit:
    angle = angles[0]
    if angle > 0:
        number_of_gates = angle / math.pi
    else:
        number_of_gates = (6.28 - angle) / math.pi
    dag = DAGCircuit()
    dag.add_qubits([Qubit()])
    for _ in range(int(number_of_gates)):
        dag.apply_operation_back(PhaseGate(math.pi), [dag.qubits[0]])
    return dag

WrapAngles.DEFAULT_REGISTRY.add_wrapper("phase", fold_phase)

Diese Funktion wandelt die "out of bounds"-Tore in ein Tor um, das die Winkelbegrenzungen im Ziel und die anderen Beschränkungen des Ziels respektiert (wenn auch nicht besonders gut).

Backend.run Verfahren

Von entscheidender Bedeutung ist die run() Methode, mit der Schaltkreise tatsächlich an ein Gerät oder einen Simulator übermittelt werden. Die Methode „run“ übernimmt die Übermittlung der Schaltkreise an das Backend zur Ausführung und gibt ein Job Objekt zurück. Je nach Art des Backends umfasst dies in der Regel die Serialisierung des Schaltungsobjekts in das vom Backend verwendete API-Format. Da die Anforderungen an die Serialisierung im Backend unterschiedlich sein können (und im Falle lokaler Simulatoren eine Serialisierung möglicherweise gar nicht erforderlich ist), wird erwartet, dass die entsprechende run Methode des Backends diese Konvertierung übernimmt.

Ein Beispiel für eine Ausführungsmethode wäre etwa so:

def run(self, circuits, **kwargs):
    for kwarg in kwargs:
        if not hasattr(self.options, kwarg):
            warnings.warn(
                "Option %s is not used by this backend" % kwarg,
                UserWarning, stacklevel=2)
    options = {
        'shots': kwargs.get('shots', self.options.shots),
        'memory': kwargs.get('memory', self.options.memory),
    }
    job_json = convert_to_wire_format(circuit, options)
    job_handle = submit_to_backend(job_json)
    return MyJob(self.job_handle, job_json, circuit)

Backend-Optionen

Häufig gibt es mehrere Optionen für ein Backend, die bestimmen, wie ein Schaltkreis ausgeführt wird. Ein typisches Beispiel hierfür ist etwa die Zahl, shots die angibt, wie oft der Ablauf ausgeführt werden soll. Die für ein Backend verfügbaren Optionen werden mithilfe eines Options Objekts definiert. Dieses Objekt wird zunächst durch die _default_options Methode einer Backend-Klasse erstellt. Die Standardoptionen geben ein initialisiertes Options Objekt zurück, das alle Standardwerte für alle Optionen enthält, die ein Backend unterstützt. Wenn das Backend beispielsweise nur die _default_options Methode unterstützt shots , würde diese wie folgt aussehen:

@classmethod
def _default_options(cls):
    return Options(shots=1024)

Sie können außerdem Validatoren für ein Options Objekt festlegen, um für die vom Benutzer eingegebenen Werte Einschränkungen und Validierungen festzulegen, je nachdem, was für Ihr Backend zulässig ist. Wenn beispielsweise die "shots" oben definierte Option auf einen beliebigen Wert zwischen 1 und 4096 gesetzt werden kann, können Sie den Validator im Optionsobjekt für Ihr Backend wie folgt festlegen:

self.options.set_validator("shots", (1, 4096))

Eine vollständige Liste der Validierungsoptionen finden Sie in der set_validator() Dokumentation.

Job

Die Ausgabe der run Methode ist ein JobV1 Objekt. Von jedem Anbieter wird erwartet, dass er eine eigene Job-Unterklasse implementiert, die das Verhalten des Anbieters definiert. Je nach Ausführungsmethode des Backends gibt es zwei Arten von Aufgaben: synchrone und asynchrone. Backend.run()Standardmäßig werden Jobs als asynchron betrachtet, und es wird davon ausgegangen, dass sie einen Zugriff auf die asynchrone Ausführung der mit übermittelten Schaltkreise darstellen. Ein Async-Job-Objekt bietet Benutzern die Möglichkeit, den Status der Ausführung abzufragen, einen laufenden Job abzubrechen und zu warten, bis die Ausführung abgeschlossen ist. Die result ist die wichtigste Methode für den Benutzer, die so lange blockiert, bis die Ausführung abgeschlossen ist, und anschließend ein Result Objekt mit den Ergebnissen des Auftrags zurückgibt.

Bei einigen Backends (vor allem lokalen Simulatoren) erfolgt die Ausführung von Schaltkreisen synchron, sodass es nicht erforderlich ist, ein Handle an einen an anderer Stelle laufenden Job zurückzugeben. Bei Synchronisierungsaufträgen wird erwartet, dass die run Methode auf der Backend-Seite so lange blockiert, bis ein Result Objekt generiert wurde, und dass der Synchronisierungsauftrag mit diesem inneren Result Objekt zurückkehrt.

Eine Beispiel-Auftragsklasse für ein asynchrones API-basiertes Backend würde etwa so aussehen:

from qiskit.providers import JobV1 as Job
from qiskit.providers import JobError
from qiskit.providers import JobTimeoutError
from qiskit.providers.jobstatus import JobStatus
from qiskit.result import Result


class MyJob(Job):
    def __init__(self, backend, job_id, job_json, circuits):
        super().__init__(backend, job_id)
        self._backend = backend
        self.job_json = job_json
        self.circuits = circuits

    def _wait_for_result(self, timeout=None, wait=5):
        start_time = time.time()
        result = None
        while True:
            elapsed = time.time() - start_time
            if timeout and elapsed >= timeout:
                raise JobTimeoutError('Timed out waiting for result')
            result = get_job_status(self._job_id)
            if result['status'] == 'complete':
                break
            if result['status'] == 'error':
                raise JobError('Job error')
            time.sleep(wait)
        return result

    def result(self, timeout=None, wait=5):
        result = self._wait_for_result(timeout, wait)
        results = [{'success': True, 'shots': len(result['counts']),
                    'data': result['counts']}]
        return Result.from_dict({
            'results': results,
            'backend_name': self._backend.configuration().backend_name,
            'backend_version': self._backend.configuration().backend_version,
            'job_id': self._job_id,
            'success': True,
        })

    def status(self):
        result = get_job_status(self._job_id)
        if result['status'] == 'running':
            status = JobStatus.RUNNING
        elif result['status'] == 'complete':
            status = JobStatus.DONE
        else:
            status = JobStatus.ERROR
        return status

    def submit(self):
        raise NotImplementedError

und für einen Synchronisierungsauftrag:

class MySyncJob(Job):
    _async = False

    def __init__(self, backend, job_id, result):
        super().__init__(backend, job_id)
        self._result = result

    def submit(self):
        return

    def result(self):
        return self._result

    def status(self):
        return JobStatus.DONE

Basiselemente

Das Modul ist zwar nicht direkt Teil der Anbieterschnittstelle, aber qiskit.primitives modul ist eng mit den Anbietern gekoppelt. Insbesondere die primitiven Schnittstellen, wie BaseSampler und BaseEstimator, sind so konzipiert, dass Anbieterimplementierungen benutzerdefinierte Implementierungen bereitstellen können, die für die Backends des Anbieters optimiert sind. Dies kann Anpassungen wie Schaltkreistransformationen, zusätzliche Vor- und Nachbearbeitungen, Stapelverarbeitung, Zwischenspeicherung, Fehlerbegrenzung usw. umfassen. Das Konzept des qiskit.primitives moduls ist es, dies explizit zu ermöglichen, da die primitiven Objekte Abstraktionen auf höherer Ebene sind, um verarbeitete Ausgaben auf höherer Ebene (wie Wahrscheinlichkeitsverteilungen und Erwartungswerte) zu erzeugen, die die Mechanik der Erzielung des besten Ergebnisses effizient abstrahieren, um sich auf Anwendungen auf höherer Ebene zu konzentrieren, die diese Ausgaben verwenden.

Wenn Ihre Backends z. B. gut geeignet sind, um die Messungsminderung von mthree zu nutzen, um die Qualität der Ergebnisse zu verbessern, könnten Sie eine anbieterspezifische Sampler -Implementierung implementieren, die die Klasse M3Mitigation intern nutzt, um die Schaltungen auszuführen und Quasi-Wahrscheinlichkeiten direkt von mthree im Ergebnis zurückzugeben. Dies würde es den Algorithmen ermöglichen, die besten Ergebnisse zu erzielen, da die Schadensbegrenzung direkt von Ihren Backends aus erfolgt. Sie können sich auf die Dokumentation in qiskit.primitives nachlesen, wie man eigene Implementierungen schreibt. Auch die eingebauten Implementierungen: Sampler, Estimator, BackendSampler und BackendEstimator können ebenfalls als Referenz/Modell für die Implementierung dienen.

War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.