Freigeben über


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 SWAPist) 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 SWAPeingefü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 SWAPgibt 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.