Compartir a través de


Declaraciones de especialización

Como se explicó en la sección de declaraciones invocables, actualmente no hay razón para declarar explícitamente las especializaciones para las funciones. Este tema se aplica a las operaciones y explica cómo declarar las especializaciones necesarias para soportar ciertos funtores.

Es un problema bastante común en la computación cuántica requerir el adjunto de una transformación dada. Muchos algoritmos cuánticos requieren tanto una operación como su adjunto para realizar un cálculo. Q# emplea la computación simbólica que puede generar automáticamente la correspondiente implementación adjunta para una implementación de cuerpo particular Esta generación es posible incluso para implementaciones que mezclan de forma libre cálculos clásicos y cuánticos. Sin embargo, hay algunas restricciones que se aplican en este caso. Por ejemplo, la autogeneración no se admite por razones de rendimiento si la implementación hace uso de variables mutables. Además, cada operación invocada dentro del cuerpo genera el correspondiente adjunto necesita soportar el propio functor Adjoint.

Aunque no se pueden deshacer fácilmente las mediciones en el caso de los multiqubits, es posible combinar las mediciones para que la transformación aplicada sea unitaria. En este caso, significa que, aunque la implementación del cuerpo contenga medidas que por si solas no soportan el functor Adjoint, el cuerpo en su totalidad es adjuntable. No obstante, la autogeneración de la implementación adjoint fallará en este caso. Por esta razón, es posible especificar manualmente la implementación. El compilador genera automáticamente implementaciones optimizadas para patrones comunes como las conjugaciones. Sin embargo, una especialización explícita se puede utilizar para definir una implementación más optimizada a mano. Es posible especificar cualquier implementación y cualquier número de implementaciones explícitamente.

Nota:

La corrección de una implementación especificada manualmente no es verificada por el compilador.

En el siguiente ejemplo, la declaración de una operación SWAP, que intercambia el estado de dos cúbits q1 y q2, declara una especialización explícita para su versión adjunta y su versión controlada. Mientras que las implementaciones para el Adjoint SWAP y el Controlled SWAP son, por tanto, definidas por el usuario, el compilador aún necesita generar la implementación para la combinación de ambos funtores (Controlled Adjoint SWAP, que es lo mismo que el Adjoint Controlled SWAP).

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

Directivas de generación automática

Al determinar cómo generar una especialización concreta, el compilador da prioridad a las implementaciones definidas por el usuario. Esto significa que si una especialización adjoint es definida por el usuario y una especialización controlled es auto-generada, entonces la especialización adjoint controlada es generada basada en la adjoint definida por el usuario y viceversa. En este caso, ambas especializaciones son definidas por el usuario. Como la autogeneración de una implementación adjoint está sujeta a más limitaciones, la especialización adjoint controlada genera por defecto la especialización controlled de la implementación explícitamente definida de la especialización adjoint.

En el caso de la implementación de SWAP, la mejor opción es adjuntar la especialización controlada para evitar condicionar innecesariamente la ejecución de la primera y la última CNOT al estado de los cúbits de control. Agregar una declaración explícita para la versión adjoint controlada que especifique una directiva de generación adecuada obliga al compilador a generar la especialización adjoint controlada en función de la implementación especificada manualmente de la versión controlada en su lugar. Tal declaración explícita de una especialización que debe ser generada por el compilador toma la forma

    controlled adjoint invert;

y se insertan dentro de la declaración de SWAP. Por otro lado, al insertar la línea

    controlled adjoint distribute;

se obliga al compilador a generar la especialización en base a la especialización adjoint definida (o generada). Consulte esta propuesta de inferencia de especialización parcial para obtener más detalles.

Para la operación SWAP, hay una mejor opción. SWAP es autoadjunta, es decir, es su propia inversa; la implementación definida del adjunto simplemente llama al cuerpo de SWAP. Esto se expresa con la directiva

    adjoint self;

Declarar la especialización del adjunto de esta manera asegura que la especialización controlada del adjunto que el compilador inserta automáticamente se limita a invocar la especialización controlada.

Las siguientes directivas de generación existen y son válidas:

Especialización Directivas
Especialización body: -
Especialización adjoint: self, invert
Especialización controlled: distribute
Especialización controlled adjoint: self, invert, distribute

Que todas las directivas de generación resulten válidas para una especialización adjunta controlada no es una casualidad; mientras los funtores conmuten, el conjunto de directivas de generación válidas para implementar la especialización para una combinación de funtores es siempre la unión del conjunto de generadores válidos para cada uno de ellos.

Además de las directivas enumeradas anteriormente, la directiva auto es válida para todas las especializaciones, excepto body; indica que el compilador debe elegir automáticamente una directiva de generación adecuada. La declaración

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

es equivalente a

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

La anotación es is Adj + Ctl, en este ejemplo, especifica las características de la operación, que contienen la información sobre los funtores que soporta una determinada operación.

Aunque, por razones de legibilidad, se recomienda anotar cada operación con una descripción completa de sus características, el compilador inserta o completa automáticamente la anotación en función de las especializaciones declaradas explícitamente. A la inversa, el compilador también genera especializaciones que no fueron declaradas explícitamente pero que deben existir en base a las características anotadas. Decimos que la anotación dada tiene declaraciones implícitas de estas especializaciones. El compilador genera automáticamente las especializaciones necesarias si puede, con una directiva adecuada. Por lo tanto, Q# admite la inferencia tanto de las características de la operación como de las especializaciones existentes basadas en anotaciones (parciales) y en las especializaciones definidas explícitamente.

En cierto sentido, las especializaciones son similares a las sobrecargas individuales para la misma invocación, con la advertencia de que se aplican ciertas restricciones a las sobrecargas que se pueden declarar.