Singleton-Anweisungen
qiskit.circuit.singleton
Die Mechanismen in diesem Modul dienen dazu, Unterklassen von Instruction und Gate zu definieren, die bei der Instanziierung vorzugsweise eine gemeinsam genutzte, unveränderliche Singleton-Instanz zurückgeben. Am Beispiel von XGate… sieht das Endergebnis für den Nutzer wie folgt aus:
GateEs gibt eine reguläre Klasse namensXGate, die von abgeleitet ist.XGateWenn man etwas wieXGate(label="my_gate")ausführt, entsteht ein Objekt, dessen Typ genau ist, und die Veränderbarkeit funktioniert vollkommen wie erwartet; alle Methoden verweisen genau auf jene, die durchXGate,Gateoder übergeordnete Klassen definiert sind.- Dadurch
XGate()wird ein Singleton-Objekt erzeugt, dessen Typ eine synthetische_SingletonXGateKlasse ist, die von abgeleitetXGateist, jedoch überschreibt__setattr__(), um sich selbst unveränderlich zu machen. Das Objekt selbst verfügt über genau dieselben Instanzattribute, wieXGate()es sie hätte, wenn keine Singleton-Verarbeitung stattfinden würde.copy()Dieses Objekt gibt sich selbst unter zurück unddeepcopy()durchläuft einen Roundtrip durchpickle.
GateDas Gleiche gilt beispielsweise auch für, Measure mit dem Unterschied, dass es sich dabei nur um eine Unterklasse von Instruction handelt und nicht um.
Die Klassen in diesem Modul sind für Fortgeschrittene gedacht, da sie eng mit dem Kern des Datenmodells von Qiskit für Schaltkreise verbunden sind.
Aus Sicht eines Bibliotheksautors reicht es als Mindestvoraussetzung aus, um ein Gate oder Instruction um dieses Verhalten zu erweitern, von SingletonGate (SingletonInstruction) statt von Gate (Instruction) zu erben und dafür zu sorgen, dass die __init__ Methode für alle ihre Argumente Standardwerte enthält (diese entsprechen dem Zustand der Singleton-Instanz). Zum Beispiel:
class XGate(SingletonGate):
def __init__(self, label=None):
super().__init__("x", 1, [], label=label)
assert XGate() is XGate()Schnittstelle
Die öffentlichen Klassen entsprechen jeweils den Standardklassen Instruction und Gate und sind Unterklassen dieser.
SingletonInstruction
class qiskit.circuit.singleton.SingletonInstruction(*args, _force_mutable=False, **kwargs)
Basen: Instruction, _SingletonBase
Eine Basisklasse für Instruction Objekte, die standardmäßig als Singleton-Instanzen vorliegen.
Diese Klasse sollte für Instruktionsklassen verwendet werden, die feste Definitionen haben und keinen eindeutigen Zustand enthalten. Das kanonische Beispiel für so etwas ist Measure das eine unveränderliche Definition hat und jede Instanz von Measure ist die gleiche. Die Verwendung von Singleton-Befehlen als Basisklasse für diese Art von Gatterklassen bietet einen großen Vorteil in Bezug auf den Speicherbedarf von Mehrfachbefehlen.
Eine Ausnahme, die man bei dieser Klasse jedoch beachten muss, sind die Instruction Attribute label , die für bestimmte Instanzen von Gates unterschiedlich festgelegt werden können. to_mutable()Um SingletonInstruction eine korrekte Verwendung zu gewährleisten, ist das Setzen dieser Attribute nicht möglich; sie können nur bei der Erstellung oder bei einem Objekt gesetzt werden, das mithilfe von ausdrücklich als veränderbar markiert wurde. Wird bei der Erstellung eines dieser Attribute verwendet, wird anstelle einer einzigen gemeinsamen globalen Instanz desselben Gates eine neue, separate Instanz erstellt.
SingletonGate
class qiskit.circuit.singleton.SingletonGate(*args, _force_mutable=False, **kwargs)
Basen: Gate, _SingletonBase
Eine Basisklasse für Gate Objekte, die standardmäßig als Singleton-Instanzen vorliegen.
SingletonInstructionDiese Klasse ist der Klasse sehr ähnlich, mit dem Unterschied, dass sie zusätzlich eine unitäre Gate Semantik impliziert. Die gleichen Einschränkungen hinsichtlich der Festlegung von Attributen in dieser Klasse gelten auch hier.
SingletonControlledGate
class qiskit.circuit.singleton.SingletonControlledGate(*args, _force_mutable=False, **kwargs)
Basen: ControlledGate, _SingletonBase
Eine Basisklasse für ControlledGate Objekte, die standardmäßig als Singleton-Instanzen vorliegen
SingletonInstructionDiese Klasse ist der Klasse sehr ähnlich, mit dem Unterschied, dass sie zusätzlich eine unitäre ControlledGate Semantik impliziert. Die gleichen Einschränkungen hinsichtlich der Festlegung von Attributen in dieser Klasse gelten auch hier.
Wenn man von einer dieser Klassen erbt, hat die erzeugte Klasse eine eagerly erstellte Singleton-Instanz, die zurückgegeben wird, wenn die Klasse mit Argumenten konstruiert wird, die als Singletons definiert wurden. In der Regel sind dies die Standardeinstellungen. Diese Instanzen sind unveränderlich; Versuche, ihre Eigenschaften zu ändern, führen zu TypeError.
Alle Unterklassen von Instruction verfügen über eine mutable Eigenschaft. False``TrueBei den meisten Befehlen ist dies, bei den Singleton-Instanzen hingegen. Mit dieser to_mutable() Methode lässt sich eine Version der Anweisung abrufen, die im eigenen Besitz ist und deren Änderung unbedenklich ist.
Die Singleton-Instanzen sind keine exakten Instanzen ihrer Basisklasse; sie sind spezielle Unterklassen, die keine neuen Objekte konstruieren können. Dies bedeutet Folgendes:
type(XGate()) is not XGateSie sollten sich nicht darauf type verlassen, dass ein exakter Wert vorliegt; verwenden Sie isinstance() stattdessen zur Typüberprüfung. InstructionWenn Sie die Basisklasse zuverlässig aus einem abrufen müssen, sehen Sie sich das Instruction.base_class Attribut an; Singleton-Instanzen setzen dieses korrekt. In den meisten Fällen bei der Verwendung von Qiskit ist ein geeigneterer Maßstab dafür, Instruction.name was eine Anweisung in einer Schaltung „bedeutet“.
Ableitung neuer Singletons
Das einfachste Beispiel für die Ableitung einer neuen Singleton-Anweisung besteht darin, von der richtigen Basis zu erben und eine __init__() methode zu liefern, die unveränderliche Standardwerte für alle Argumente hat. Zum Beispiel:
from qiskit.circuit.singleton import SingletonInstruction
class MyInstruction(SingletonInstruction):
def __init__(self, label=None):
super().__init__("my_instruction", 1, 0, label=label)
assert MyInstruction() is MyInstruction()
assert MyInstruction(label="some label") is not MyInstruction()
assert MyInstruction(label="some label").mutableDie Singleton-Instanz verwendet alle Standardeinstellungen des Konstruktors.
Sie können auch von einer Anweisung ableiten, die selbst ein Singleton ist. Der Singleton-Charakter der Klasse wird vererbt, obwohl die Singleton-Instanzen der beiden Klassen unterschiedlich sein werden:
class MyOtherInstruction(MyInstruction):
pass
assert MyOtherInstruction() is MyOtherInstruction()
assert MyOtherInstruction() is not MyInstruction()Wenn Sie aus irgendeinem Grund eine Ableitung von SingletonInstructionoder einer der verwandten oder Unterklassen ableiten wollen, aber nicht möchten, dass die Standard-Singleton-Instanz erzeugt wird, z. B. wenn Sie eine neue abstrakte Basisklasse definieren, können Sie das Schlüsselwortargument create_default_singleton=False in der Klassendefinition festlegen:
class NotASingleton(SingletonInstruction, create_default_singleton=False):
def __init__(self):
return super().__init__("my_mutable", 1, 0, [])
assert NotASingleton() is not NotASingleton()Wenn Ihr Konstruktor nicht über Standardwerte für alle seine Argumente verfügt, müssen Sie create_default_singleton=False setzen.
Unterklassen von SingletonInstruction und der anderen zugehörigen Klassen können steuern, wie die Argumente ihres Konstruktors interpretiert werden, damit die Singleton-Mechanik das Singleton auch dann zurückgibt, wenn ein optionales Argument explizit auf seinen Standardwert gesetzt wird.
_singleton_lookup_key
static SingletonInstruction._singleton_lookup_key(*_args, **_kwargs)
Gibt in Anbetracht der Argumente für den Konstruktor ein Schlüsseltupel zurück, das die abzurufende Singleton-Instanz identifiziert, oder None , wenn die Argumente implizieren, dass ein veränderbares Objekt erstellt werden muss.
Aus Leistungsgründen wird diese Methode nicht aufgerufen, wenn dem Klassenkonstruktor null Argumente übergeben wurden (z. B. ruft die Konstruktion XGate() diese Methode nicht auf, aber XGate(label=None) ), und es wird sofort das Standard-Singleton zurückgegeben.
Diese statische Methode kann (und sollte wahrscheinlich) von Unterklassen überschrieben werden. Die abgeleitete Signatur sollte mit der der Klasse __init__ übereinstimmen. Diese Methode sollte dann die Argumente untersuchen, um festzustellen, ob sie Veränderbarkeit erfordert oder was der Cache-Schlüssel (falls vorhanden) sein sollte.
Die Funktion sollte entweder None oder einen gültigen dict Schlüssel zurückgeben (d.h. hashable und implementiert Gleichheit). Die Rückgabe von None bedeutet, dass die erstellte Instanz veränderbar sein muss. Es wird keine weitere Singleton-basierte Verarbeitung durchgeführt, und die Klassenerstellung wird so fortgesetzt, als ob es keine Singleton-Behandlung gäbe. Andernfalls kann der zurückgegebene Schlüssel ein beliebiger Hashwert sein, und ihm wird keine besondere Bedeutung beigemessen. Immer wenn diese Methode den gleichen Schlüssel zurückgibt, wird die gleiche Singleton-Instanz zurückgegeben. Wir schlagen vor, dass Sie ein Tupel der Werte aller Argumente verwenden, die gesetzt werden können, wobei der Singleton-Charakter erhalten bleibt.
Nur Schlüssel, die mit den Standardargumenten oder Argumenten übereinstimmen, die bei der Klassenerstellung an additional_singletons übergeben werden, geben tatsächlich Singletons zurück; andere Werte geben eine veränderbare Standardinstanz zurück.
Die Singleton-Maschinerie wird eine unhashable Rückkehr von dieser Funktion anständig behandeln, indem sie eine veränderbare Instanz zurückgibt. Unterklassen sollten sicherstellen, dass ihr Schlüssel im glücklichen Pfad hashbar ist, aber sie müssen nicht manuell überprüfen, ob die vom Benutzer gelieferten Argumente hashbar sind. Zum Beispiel ist es sicher, dies zu implementieren als:
@staticmethod
def _singleton_lookup_key(*args, **kwargs):
return None if kwargs else argsauch wenn ein Benutzer einen nicht verschlüsselbaren Typ als eine der args angeben könnte.
Dies wird von allen Gates der Qiskit-Standardbibliothek so eingestellt, dass die Argumente label und ähnliche Schlüsselwörter bei der Schlüsselberechnung ignoriert werden, wenn sie ihre Standardwerte sind, oder eine veränderbare Instanz zurückgegeben wird, wenn sie es nicht sind.
Sie können auch andere Kombinationen von Konstruktorargumenten angeben, für die Singleton-Instanzen erzeugt werden sollen, indem Sie das Argument additional_singletons in der Klassendefinition verwenden. Dies nimmt eine Iterable von (args, kwargs) Tupeln und erzeugt Singletons, die cls(*args, **kwargs) entsprechen. Der Fall der Standardargumente muss dabei nicht behandelt werden. Zum Beispiel bei einer Klassendefinition:
class MySingleton(SingletonGate, additional_singletons=[((2,), {"label": "two"})]):
def __init__(self, n=1, label=None):
super().__init__("my", n, [], label=label)
@staticmethod
def _singleton_lookup_key(n=1, label=None):
return (n, label)werden zwei Singleton-Instanzen instanziiert. Einer entspricht n=1 und label=None, der andere n=2 und label="two". Wann immer MySingleton mit Argumenten konstruiert wird, die einem dieser beiden Fälle entsprechen, wird das entsprechende Singleton zurückgegeben. Zum Beispiel:
assert MySingleton() is MySingleton(1, label=None)
assert MySingleton(2, "two") is MySingleton(n=2, label="two")Der Fall, dass die Klasse mit Null-Argumenten instanziiert wird, wird speziell behandelt, um einen absolut schnellen Weg für die Leistung der inneren Schleife zu ermöglichen (obwohl die allgemeine Maschinerie ohnehin nicht verzweifelt langsam ist).
Bereitstellung
Dieser Abschnitt ist in erster Linie Entwicklerdokumentation für den Code; keine der hier beschriebenen Maschinen ist öffentlich, und es ist nicht sicher, davon direkt zu erben.
Hier gibt es mehrere Aspekte, die es zu klären gilt. type.__call__Das Verhalten, bei dem XGate() ein Singleton-Objekt zurückgibt, das eine (ungenaue) Instanz von XGate ist*, ohne* jedoch aufzurufen __init__ , erfordert, dass wir überschreiben. Das bedeutet, dass XGate über eine Metaklasse verfügen muss, die so definiert __call__ , dass die Singleton-Instanz zurückgegeben wird.
Als nächstes müssen wir sicherstellen, dass es eine Singleton-Instanz gibt, die XGate() zurückgibt. Dies kann bei jedem Aufruf dynamisch geschehen (d.h. prüfen, ob die Instanz existiert und sie erstellen, wenn nicht), aber da wir auch wollen, dass diese Instanz sehr speziell ist, ist es einfacher, sie während der Definition des XGate type-Objekts zu erstellen. Dies hat auch den Vorteil, dass das Singleton-Objekt nicht gepickelt werden muss; wir müssen nur angeben, woher es während des Entpickelns abgerufen werden soll, da die Erstellung des Objekts vom Basistyp das Singleton neu erzeugt.
Wir wollen die Singleton-Instanz zu:
- unveränderlich sein; sie sollte alle Versuche zurückweisen, sich selbst zu verändern.
- haben genau denselben Zustand, den ein
XGate()hätte, wenn es keine Singleton-Behandlung gäbe.
Wir tun dies in einem dreistufigen Verfahren:
- Bevor wir Singletons erstellen, definieren wir zunächst separat die erforderlichen Überschreibungen, um ein
Instructionund einGateunveränderlich zu machen. Das sind_SingletonInstructionOverridesund die anderen_*OverridesKlassen. - Während wir das Objekt des Typs
XGateerstellen, erstellen wir dynamisch auch eine Unterklasse davon, die die unveränderlichen Überschreibungen in der Reihenfolge ihrer Methodenauflösung an der richtigen Stelle hat. Diese überschreiben die Standardmethoden und -eigenschaften, die für das veränderliche Gatter definiert sind (wir versuchen nicht, alle Fälle zu überschreiben, in denen das von uns erstellte Typobjekt zusätzliche Inplace-Methoden hat). - Wir können diese neue Unterklasse nicht instanziieren, denn wenn sie
XGate.__init__aufruft, wird sie versuchen, einige Attribute zu setzen, und diese werden von der Unveränderlichkeit zurückgewiesen. Stattdessen erstellen wir zunächst eine ganz normale Instanz vonXGateund ändern dann dynamisch ihren Typ in die Singleton-Klasse und frieren sie ein.
Wir könnten dies vollständig innerhalb der Metaklassen-Maschinerie tun, aber das würde erfordern, dass XGate als etwas wie definiert wird:
class XGate(Gate, metaclass=_SingletonMeta, overrides=_SingletonGateOverrides): ...was extrem umständlich ist (sonst müssten wir _SingletonMeta eine Menge heikler Introspektionen durchführen). Stattdessen verwenden wir das abc.ABCMetaabc.ABC /-Muster, bei dem eine konkrete Mittelklasse (SingletonGate in diesem XGate Fall) definiert wird, die die Metaklasse festlegt, die anzuwendenden Überschreibungen auswählt und über eine __init_subclass__() Methode verfügt, die die oben beschriebenen Schritte zur Erstellung einer Singleton-Unterklasse ausführt. Die Überschreibungen befinden sich in separaten Klassen, damit *veränderbare *XGate Instanzen diese nicht in ihrer eigenen Methodenauflösungsreihenfolge enthalten; dies ist zwar einfacher zu implementieren, erfordert jedoch, dass alle Setter und Prüffunktionen zur Laufzeit hin und her springen, um zu überprüfen, ob eine Änderung der Instanz zulässig ist.
Instruction_SingletonMetaUm all diese Mechanismen schließlich tatsächlich aufzubauen, dient als Grundlage, eine Metaklasse, die mit jeder Metaklasse von kompatibel ist. Hiermit wird die __call__() Mechanik definiert, die die Rückgabe der Singleton-Instanzen überschreibt type.__call__ . Ein weiterer Bestandteil davon ist die Funktion __new__(), die (nicht trivialerweise) bei der Erstellung von SingletonGate und SingletonInstruction aufgerufen wird, wobei ihr overrides Schlüsselwortargument so gesetzt ist, dass die __init_subclass__ dieser Klassen mit den oben genannten Eigenschaften definiert werden. superWir verwenden die Metaklasse, um diese Methode dynamisch hinzuzufügen, da die __init_subclass__() Mechanik abstrakt sein soll, wobei sie die overrides und die Basisklasse einbezieht, aber dennoch in der Lage sein muss, aufzurufen. superEs ist praktischer, dies dynamisch zu tun, indem man die gewünschte Klassenvariable einbezieht und die Form mit zwei Argumenten verwendet, da die Form ohne Argumente eine „magische“ Introspektion durchführt, die davon abhängt, wo die übergeordnete Funktion definiert wurde.
Um mit mehreren Singletons umgehen zu können, müssen die Initialisierungsargumente in irgendeiner Form gespeichert werden, damit die to_mutable() Methode und das Pickling definiert werden können. Wir implementieren dies als Nachschlagewörterbuch für das Objekt vom Typ „Singleton“. Logischerweise handelt es sich hierbei um ein Instanzattribut, doch da wir den dynamischen Typ _Singleton dynamisch auf eine Instanz des Basistyps anwenden müssen, wird das Ganze ziemlich komplex; entweder müssen wir voraussetzen, dass der Basistyp bereits über ein Instanzwörterbuch verfügt, oder wir riskieren, dass das __slots__ Layout während der Umschaltung beschädigt wird. Da die Singletons so lange bestehen, bis das Typobjekt ihrer Basisklasse von der Garbage Collection entfernt wird, können wir dieses Instanz-Wörterbuch mithilfe eines Typobjekt-Wörterbuchs simulieren, das Instanzzeiger auf die Daten abbildet, die wir speichern möchten. Eine Alternative wäre, für jedes einzelne Singleton ein neues Typobjekt zu erstellen, das die Initialisierungsargumente einbezieht (oder speichert), aber Typobjekte sind recht ressourcenintensiv, und das Prinzip ist ohnehin weitgehend dasselbe.