Spezialisierungsdeklarationen
Wie im Abschnitt über aufrufbare Deklarationen erläutert, gibt es derzeit keinen Grund, Spezialisierungen für Funktionen explizit zu deklarieren. Dieses Thema bezieht sich auf Vorgänge und erläutert, wie die erforderlichen Spezialisierungen deklariert werden, um bestimmte Funktoren zu unterstützen.
Es ist ein recht häufiges Problem beim Quantencomputing, dass man die Adjunkte einer bestimmten Transformation benötigt. Viele Quantenalgorithmen erfordern sowohl einen Vorgang als auch dessen Adjunkte, um eine Berechnung durchzuführen.
Q# verwendet symbolische Berechnungen, die automatisch die entsprechende Adjunkte-Implementierung für eine bestimmte Text-Implementierung erzeugen können. Diese Generierung ist sogar für Implementierungen möglich, die klassische und Quantenberechnungen beliebig kombinieren. Es gibt jedoch einige Einschränkungen, die in diesem Fall gelten. Beispielsweise wird die automatische Generierung aus Leistungsgründen nicht unterstützt, wenn bei der Implementierung veränderliche Variablen verwendet werden. Darüber hinaus generiert jeder Vorgang, der innerhalb des Textkörpers aufgerufen wird, den entsprechenden Adjunkten, der den Adjoint
Funktor selbst unterstützen muss.
Obwohl Messungen im Fall mit mehreren Qubits nicht einfach rückgängig zu machen sind, ist es möglich, Messungen so zu kombinieren, dass die angewendete Transformation einheitlich ist. In diesem Fall bedeutet dies, dass der Textkörper in seiner Gesamtheit adjungiert werden kann, auch wenn die Implementierung des Textkörpers Messungen enthält, die für sich genommen den Adjoint
Funktor nicht unterstützen. Dennoch schlägt die automatische Generierung der Adjunkten-Implementierung in diesem Fall fehl. Aus diesem Grund ist es möglich, die Implementierung manuell festzulegen.
Der Compiler erzeugt automatisch optimierte Implementierungen für gängige Muster wie Konjugationen.
Eine explizite Spezialisierung kann dennoch sinnvoll sein, um eine optimierte Implementierung von Hand zu definieren. Es ist möglich, eine beliebige Implementierung und eine beliebige Anzahl von Implementierungen explizit festzulegen.
Hinweis
Die Korrektheit einer solchen manuell festgelegten Implementierung wird vom Compiler nicht überprüft.
Im folgenden Beispiel deklariert die Deklaration für einen Vorgang SWAP
, der den Zustand zweier Qubits q1
und q2
austauscht, eine explizite Spezialisierung für seine adjungierte Version und seine kontrollierte Version. Obwohl die Implementierungen für Adjoint SWAP
und Controlled SWAP
somit benutzerdefiniert sind, muss der Compiler dennoch die Implementierung für die Kombination beider Funktoren (Controlled Adjoint SWAP
, die identisch mit Adjoint Controlled SWAP
ist) erzeugen.
operation SWAP (q1 : Qubit, q2 : Qubit) : Unit
is Adj + Ctl {
body ... {
CNOT(q1, q2);
CNOT(q2, q1);
CNOT(q1, q2);
}
adjoint ... {
SWAP(q1, q2);
}
controlled (cs, ...) {
CNOT(q1, q2);
Controlled CNOT(cs, (q2, q1));
CNOT(q1, q2);
}
}
Anweisungen zur automatischen Generierung
Bei der Festlegung, wie eine bestimmte Spezialisierung zu generieren ist, priorisiert der Compiler benutzerdefinierte Implementierungen. Wenn also eine Adjunkte-Spezialisierung benutzerdefiniert ist und eine kontrollierte Spezialisierung automatisch generiert wird, dann wird die kontrollierte Adjunkte-Spezialisierung auf der Grundlage der benutzerdefinierten Adjunkten generiert und umgekehrt. In diesem Fall sind beide Spezialisierungen benutzerdefiniert. Da die automatische Generierung einer Adjunkten-Implementierung stärker eingeschränkt ist, generiert die kontrollierte Adjunkten-Spezialisierung standardmäßig die kontrollierte Spezialisierung der explizit definierten Implementierung der Adjunkten-Spezialisierung.
Im Falle der SWAP
Implementierung ist es besser, die kontrollierte Spezialisierung zu adjungieren, um zu vermeiden, dass die Ausführung des ersten und des letzten CNOT
unnötigerweise vom Zustand der Kontroll-Qubits abhängt.
Das Hinzufügen einer expliziten Deklaration für die kontrollierte Adjunkte-Version, die eine geeignete Anweisung zur Generierung festlegt, zwingt den Compiler, die kontrollierte Adjunkte-Spezialisierung stattdessen auf der Grundlage der manuell festgelegten Implementierung der kontrollierten Version zu generieren. Eine solche explizite Deklaration einer Spezialisierung, die vom Compiler generiert werden soll, hat die Form
controlled adjoint invert;
und wird in die Deklaration von SWAP
eingefügt.
Andererseits wird durch das Einfügen der Zeile
controlled adjoint distribute;
der Compiler gezwungen, die Spezialisierung auf der Grundlage der definierten (oder generierten) Adjunkten Spezialisierung zu generieren. Weitere Einzelheiten finden Sie in diesem Vorschlag zur Inferenz einer partiellen Spezialisierung.
Für den Vorgang SWAP
gibt es eine bessere Option.
SWAP
ist selbstajungiert, d.h. sie ist die eigene Inverse. Die -definierte Implementierung des Adjunkten ruft lediglich den Textkörper von SWAP
auf. Sie drücken dies mit der Anweisung aus
adjoint self;
Wenn Sie die Adjunkte-Spezialisierung auf diese Weise deklarieren, wird sichergestellt, dass die kontrollierte Adjunkte-Spezialisierung, die vom Compiler automatisch eingefügt wird, lediglich die kontrollierte Spezialisierung aufruft.
Die folgenden Anweisungen zur Generierung existieren und sind gültig:
Spezialisierung | Anweisung(en) |
---|---|
body Spezialisierung: |
- |
adjoint Spezialisierung: |
self , invert |
controlled Spezialisierung: |
distribute |
controlled adjoint Spezialisierung: |
self , invert , distribute |
Dass alle Generierungsanweisungen für eine kontrollierte Adjunkte-Spezialisierung gültig sind, ist kein Zufall. Solange Funktoren kommutieren, ist die Menge der gültigen Generierungsanweisungen zur Implementierung der Spezialisierung für eine Kombination von Funktoren immer die Vereinigung der Menge der gültigen Generatoren für jeden einzelnen.
Zusätzlich zu den zuvor aufgeführten Direktiven ist die -Direktive auto
für alle Spezialisierungen gültig, außer body
; sie gibt an, dass der Compiler automatisch eine geeignete Generierungsdirektive auswählen sollte.
Die Deklaration
operation DoNothing() : Unit {
body ... { }
adjoint auto;
controlled auto;
controlled adjoint auto;
}
für die folgende Syntax:
operation DoNothing() : Unit
is Adj + Ctl { }
Die Anmerkung is Adj + Ctl
legt in diesem Beispiel die Merkmale des Vorgangs fest, die die Informationen darüber enthalten, welche Funktoren ein bestimmter Vorgang unterstützt.
Aus Gründen der Lesbarkeit empfiehlt es sich zwar, jeden Vorgang mit einer vollständigen Beschreibung seiner Merkmale zu kommentieren, aber der Compiler fügt die Anmerkung auf der Grundlage von explizit deklarierten Spezialisierungen automatisch ein oder ergänzt sie. Andererseits generiert der Compiler auch Spezialisierungen, die nicht explizit deklariert wurden, die aber aufgrund der annotierten Merkmale existieren müssen. Man sagt, die gegebene Anmerkung hat diese Spezialisierungen implizit deklariert. Der Compiler generiert die notwendigen Spezialisierungen automatisch, sofern er die Möglichkeit dazu hat, indem er eine geeignete Anweisung auswählt. Q# unterstützt somit die Rückschlüsse auf Vorgangsmerkmale und vorhandene Spezialisierungen auf der Grundlage von (partiellen) Anmerkungen und explizit definierten Spezialisierungen.
In gewissem Sinne sind Spezialisierungen vergleichbar mit individuellen Überladungen für dieselbe aufrufbare Funktion, mit dem Vorbehalt, dass bestimmte Einschränkungen dafür gelten, welche Überladungen Sie deklarieren können.