Freigeben über


Spezialisierungsdeklarationen

Wie im Abschnitt zu aufrufbaren Deklarationenerlä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 Functors zu unterstützen.

Es ist ein ganz häufiges Problem bei der Quantenberechnung, um den Angrenzenden einer bestimmten Transformation zu erfordern. Viele Quantenalgorithmen erfordern sowohl eine Operation als auch ihre angrenzende Operation, um eine Berechnung durchzuführen. Q# verwendet symbolische Berechnung, die automatisch die entsprechende angrenzende Implementierung für eine bestimmte Bodyimplementierung generieren kann. Diese Generation ist auch bei Implementierungen möglich, die klassische und Quantenberechnungen frei mischen. Es gibt jedoch einige Einschränkungen, die in diesem Fall gelten. Die automatische Generierung wird beispielsweise aus Leistungsgründen nicht unterstützt, wenn die Implementierung veränderbare Variablen verwendet. Darüber hinaus generiert jeder innerhalb des Körpers aufgerufene Vorgang den entsprechenden angrenzenden Muss, um den Adjoint Functor selbst zu unterstützen.

Auch wenn man messungen im Multi-Qubit-Fall nicht einfach rückgängig machen kann, ist es möglich, Messungen zu kombinieren, sodass die angewendete Transformation einheitlich ist. In diesem Fall bedeutet dies, dass der Körper, auch wenn die Körperimplementierung Maßangaben enthält, die den Adjoint Functor nicht unterstützen, der Körper in seiner Gesamtheit angrenzend ist. Dennoch schlägt die automatische Generierung der angrenzenden Implementierung in diesem Fall fehl. Aus diesem Grund ist es möglich, die Implementierung manuell anzugeben. Der Compiler generiert automatisch optimierte Implementierungen für gängige Muster wie Konjugationen. Dennoch kann eine explizite Spezialisierung wünschenswert sein, um eine optimierte Implementierung manuell zu definieren. Es ist möglich, eine beliebige Implementierung und eine beliebige Anzahl von Implementierungen explizit anzugeben.

Hinweis

Der Compiler überprüft nicht die Richtigkeit einer solchen manuell angegebenen Implementierung.

Im folgenden Beispiel deklariert die Deklaration für einen Vorgang SWAP, der den Zustand von zwei Qubits q1 und q2austauscht, eine explizite Spezialisierung für seine angrenzende Version und seine kontrollierte Version. Während die Implementierungen für Adjoint SWAP und Controlled SWAP somit benutzerdefiniert sind, muss der Compiler weiterhin die Implementierung für die Kombination beider Functors generieren (Controlled Adjoint SWAP, die mit Adjoint Controlled SWAPidentisch ist).

    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);            
        } 
    }

Direktiven der automatischen Generierung

Bei der Bestimmung, wie eine bestimmte Spezialisierung generiert werden soll, priorisiert der Compiler benutzerdefinierte Implementierungen. Dies bedeutet, dass, wenn eine angrenzende Spezialisierung benutzerdefinierte und eine kontrollierte Spezialisierung automatisch generiert wird, die kontrollierte angrenzende Spezialisierung basierend auf dem benutzerdefinierten angrenzenden und umgekehrt generiert wird. In diesem Fall sind beide Spezialisierungen benutzerdefinierter. Da die automatische Generierung einer angrenzenden Implementierung mehr Einschränkungen unterliegt, setzt die kontrollierte angrenzende Spezialisierung standardmäßig die kontrollierte Spezialisierung der explizit definierten Implementierung der angrenzenden Spezialisierung ein.

Im Falle der SWAP Implementierung besteht die bessere Option darin, an die kontrollierte Spezialisierung anzugrenzen, um unnötige Konditionierung der Ausführung der ersten und der letzten CNOT für den Zustand der Kontroll qubits zu vermeiden. Hinzufügen einer expliziten Deklaration für die kontrollierte angrenzende Version, die eine geeignete Generation Direktive angibt, erzwingt den Compiler, die kontrollierte angrenzende Spezialisierung basierend auf der manuell angegebenen Implementierung der kontrollierten Version zu generieren. Eine solche explizite Deklaration einer spezialisierung, die vom Compiler generiert wird, nimmt die Form

    controlled adjoint invert;

und wird in die Deklaration von SWAPeingefügt. Auf der anderen Seite fügen Sie die Zeile ein

    controlled adjoint distribute;

erzwingt den Compiler, die Spezialisierung basierend auf der definierten (oder generierten) angrenzenden Spezialisierung zu generieren. Weitere Informationen finden Sie in diesem Teilspezialisierungsableitung Vorschlag für weitere Details.

Für den Vorgang SWAPgibt es eine bessere Option. SWAP ist selbst angrenzende, d. r., es ist seine eigene Umkehrung; die definierte Umsetzung des angrenzenden Gremiums ruft lediglich den Textkörper SWAP auf und wird mit der Richtlinie ausgedrückt.

    adjoint self;

Durch das Deklarieren der angrenzenden Spezialisierung auf diese Weise wird sichergestellt, dass die kontrollierte angrenzende Spezialisierung, die der Compiler automatisch einfügt, lediglich die kontrollierte Spezialisierung aufruft.

Die folgenden Generierungsdirektiven sind vorhanden und gültig:

Spezialisierung Direktiven
body Spezialisierung: -
adjoint Spezialisierung: self, invert
controlled Spezialisierung: distribute
controlled adjoint Spezialisierung: self, invert, distribute

Dass alle Generationendirektiven für eine kontrollierte angrenzende Spezialisierung gültig sind, ist kein Zufall; Solange Functors pendeln, ist der Satz gültiger Generation-Richtlinien für die Implementierung der Spezialisierung für eine Kombination von Functoren immer die Vereinigung der gültigen Generatoren für jeden.

Zusätzlich zu den zuvor aufgeführten Richtlinien gilt die Richtlinie auto für alle Spezialisierungen außer body; es gibt an, dass der Compiler automatisch eine geeignete Generationdirektive auswählen soll. Die Deklaration

    operation DoNothing() : Unit {
        body ... { }
        adjoint auto;
        controlled auto;
        controlled adjoint auto;
    }

ist gleichbedeutend mit

    operation DoNothing() : Unit 
    is Adj + Ctl { }

Die Anmerkung is Adj + Ctl in diesem Beispiel gibt die Vorgangsmerkmalean, die die Informationen darüber enthalten, welche Functors ein bestimmter Vorgang unterstützt.

Aus Gründen der Lesbarkeit wird empfohlen, jeden Vorgang mit einer vollständigen Beschreibung seiner Merkmale zu kommentieren, der Compiler fügt die Anmerkung automatisch ein oder schließt die Anmerkung basierend auf explizit deklarierten Spezialisierungen ein. Umgekehrt generiert der Compiler auch Spezialisierungen, die nicht explizit deklariert werden, sondern basierend auf den kommentierten Merkmalen vorhanden sein müssen. Wir sagen, dass die angegebene Anmerkung implizit diese Spezialisierungen deklariert. Der Compiler generiert automatisch die erforderlichen Spezialisierungen, wenn dies möglich ist, und wählt eine geeignete Direktive aus. Q# unterstützt somit die Ableitung von Betriebseigenschaften und vorhandenen Spezialisierungen basierend auf (teilweisen) Anmerkungen und explizit definierten Spezialisierungen.

In einem Sinne ähneln Spezialisierungen einzelnen Überladungen für die gleiche aufrufbare, mit der Einschränkung, dass bestimmte Einschränkungen gelten, für welche Überladungen Sie deklarieren können.