Compartir a través de


Declaraciones de especialización

Como se explica en la sección sobre declaraciones invocables, actualmente no hay ninguna razón para declarar explícitamente especializaciones para las funciones. Este tema se aplica a las operaciones y elabora sobre cómo declarar las especializaciones necesarias para admitir determinados functores.

Se trata de un problema bastante común en la computación cuántica para requerir el adyacente de una transformación determinada. Muchos algoritmos cuánticos requieren una operación y su adyacente para realizar un cálculo. Q# emplea cálculos simbólicos que pueden generar automáticamente la implementación adyacente correspondiente para una implementación concreta del cuerpo. Esta generación es posible incluso para implementaciones que combinan libremente cálculos clásicos y cuánticos. Sin embargo, hay algunas restricciones que se aplican en este caso. Por ejemplo, la generación automática no se admite por motivos de rendimiento si la implementación usa variables mutables. Además, cada operación a la que se llama dentro del cuerpo genera las necesidades adyacentes correspondientes para admitir el functor Adjoint.

Aunque no se puedan deshacer fácilmente las medidas en el caso de varios cúbits, es posible combinar medidas para que la transformación aplicada sea unitaria. En este caso, significa que, aunque la implementación del cuerpo contiene medidas que por sí mismas no admiten el functor de Adjoint, el cuerpo en su totalidad es adyacente. No obstante, se produce un error en la generación automática de la implementación adyacente en este caso. Por este motivo, es posible especificar manualmente la implementación. El compilador genera automáticamente implementaciones optimizadas para patrones comunes, como conjugaciones. No obstante, puede ser conveniente definir una implementación más optimizada a mano. Es posible especificar una implementación y cualquier número de implementaciones explícitamente.

Nota:

El compilador no comprueba la exactitud de esta implementación especificada manualmente.

En el ejemplo siguiente, 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 adyacente y su versión controlada. Aunque las implementaciones de Adjoint SWAP y Controlled SWAP están definidas por el usuario, el compilador sigue necesitando generar la implementación para la combinación de ambos functors (Controlled Adjoint SWAP, que es igual que 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 determinada, el compilador prioriza las implementaciones definidas por el usuario. Esto significa que si una especialización adyacente está definida por el usuario y se genera automáticamente una especialización controlada, la especialización adjunta controlada se genera en función del adyacente definido por el usuario y viceversa. En este caso, ambas especializaciones están definidas por el usuario. Dado que la generación automática de una implementación adyacente está sujeta a más limitaciones, la especialización adjunta controlada tiene como valor predeterminado generar la especialización controlada de la implementación definida explícitamente de la especialización adyacente.

En el caso de la implementación de SWAP, la mejor opción es anexar la especialización controlada para evitar condicionar innecesariamente la ejecución de la primera y la última CNOT en el estado de los cúbits de control. Agregar una declaración explícita para la versión adyacente controlada que especifica una directiva de generación de adecuada obliga al compilador a generar la especialización adjunta controlada basada en la implementación especificada manualmente de la versión controlada en su lugar. Esta declaración explícita de una especialización generada por el compilador adopta la forma

    controlled adjoint invert;

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

    controlled adjoint distribute;

fuerza al compilador a generar la especialización en función de la especialización adyacente definida (o generada). Para obtener más información, consulte esta inferencia parcial propuesta para obtener más detalles.

Para la operación SWAP, hay una mejor opción. SWAP es autodjunto, es decir, es su propio inverso; la implementación definida del adyacente simplemente llama al cuerpo de SWAP y se expresa con la directiva .

    adjoint self;

Declarar la especialización adyacente de esta manera garantiza que la especialización adjunta controlada que el compilador inserte automáticamente simplemente invoca la especialización controlada.

Existen las siguientes directivas de generación y son válidas:

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

Que todas las directivas de generación son válidas para una especialización adjunta controlada no es una coincidencia; siempre que los functors conmuten, el conjunto de directivas de generación válidas para implementar la especialización para una combinación de functors siempre es la unión del conjunto de generadores válidos para cada uno.

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. 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 is Adj + Ctl en este ejemplo especifica las características de operación , que contienen la información sobre qué functors admite una operación determinada.

Aunque por motivos 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 especializaciones declaradas explícitamente. Por el contrario, el compilador también genera especializaciones que no se declaran explícitamente, pero que necesitan existir en función de las características anotadas. Decimos que la anotación dada declara implícitamente estas especializaciones. El compilador genera automáticamente las especializaciones necesarias si puede, seleccionando una directiva adecuada. Q# admite así la inferencia de las características de operación y las especializaciones existentes basadas en anotaciones (parciales) y especializaciones definidas explícitamente.

En cierto sentido, las especializaciones son similares a las sobrecargas individuales para el mismo invocable, con la advertencia de que ciertas restricciones se aplican a las sobrecargas que se pueden declarar.