Tutorial: Implementación de la transformación de Quantum Fourier en Q#

Nota

Microsoft Quantum Development Kit (QDK clásico) ya no se admitirá después del 30 de junio de 2024. Si es un desarrollador de QDK existente, se recomienda realizar la transición al nuevo Azure Quantum Development Kit (QDK moderno) para seguir desarrollando soluciones cuánticas. Para obtener más información, consulte Migración Q# del código al QDK moderno.

En este tutorial, se muestra cómo escribir y simular un programa cuántico que funciona en cúbits individuales.

Aunque Q# se creó como un lenguaje de programación genérico para programas cuánticos a gran escala, puede utilizarse también para explorar el nivel inferior de los programas cuánticos, es decir, dirigirse directamente a cúbits específicos. En concreto, este tutorial examina con más detalle la transformación quantum fourier (QFT), una subrutina que es integral a muchos algoritmos cuánticos más grandes.

En este tutorial, aprenderá a:

  • Defina las operaciones cuánticas en Q#.
  • Escritura del circuito de transformación de Quantum Fourier
  • Simulación de una operación cuántica desde la asignación de cúbits a la salida de medición.
  • Observe cómo evoluciona la función de onda simulada del sistema cuántico a lo largo de la operación.

Nota:

Esta vista general del procesamiento cuántico de la información suele describirse en términos de circuitos cuánticos, que representan la aplicación secuencial de puertas, u operaciones, a cúbits específicos de un sistema. Así, las operaciones de uno y varios cúbits que aplique secuencialmente pueden representarse fácilmente en un diagrama de circuitos. Por ejemplo, la transformación cuántica de fourier de tres cúbits completa que se usa en este tutorial tiene la siguiente representación como un circuito: Diagrama de un circuito de transformación de Fourier cuántico.

Sugerencia

Si desea acelerar el recorrido de la computación cuántica, consulte Código con Azure Quantum, una característica única del sitio web de Azure Quantum. Aquí puede ejecutar ejemplos integrados Q# o sus propios Q# programas, generar código nuevo Q# a partir de las indicaciones, abrir y ejecutar el código en VS Code para la Web con un solo clic y formular a Copilot cualquier pregunta sobre la computación cuántica.

Requisitos previos

Create un nuevo Q# archivo

  1. En VS Code, seleccione Archivo > nuevo archivo de texto.
  2. Guarde el archivo como QFTcircuit.qs. Este archivo contendrá el Q# código del programa.
  3. Abra QFTcircuit.qs.

Escritura de un circuito QFT en Q#

La primera parte de este tutorial consiste en definir la operación de Q#Perform3qubitQFT, que realiza la transformación cuántica de Fourier sobre tres cúbits. La función DumpMachine se usa para observar cómo evoluciona la función de onda simulada del sistema de tres cúbits a lo largo de la operación. En la segunda parte del tutorial, agregará funcionalidad de medición y comparará los estados previos y posteriores a la medición de los cúbits.

Compilará la operación paso a paso. Copie y pegue el código en las secciones siguientes en el archivo QFTcircuit.qs .

Puede ver el código completo Q# de esta sección como referencia.

Espacios de nombres para acceder a otras operaciones de Q#

Dentro del archivo Q#, defina el espacio de nombres NamespaceQFT, al que accederá el compilador. Para que esta operación haga uso de las operaciones de Q# existentes, se abren los espacios de nombres de Microsoft.Quantum.* correspondientes.

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    // operations go here
}

Definición de operaciones con argumentos y retornos

A continuación, se define la operación Perform3qubitQFT:

operation Perform3qubitQFT() : Unit {
    // do stuff
}

Por ahora, la operación no toma argumentos y devuelve un objeto Unit, que equivale a devolver void en C# o una tupla vacía Tuple[()] en Python. Después, modificará la operación para que devuelva una matriz de resultados de medida.

Asignación de cúbits

Dentro de la Q# operación, asigne un registro de tres cúbits con la use palabra clave . Con use, los cúbits se asignan automáticamente en el estado $\ket{0}$.

use qs = Qubit[3]; // allocate three qubits

Message("Initial state |000>:");
DumpMachine();

Al igual que la computación cuántica real, Q# no permite acceder directamente a los estados de los cúbits. Sin embargo, la DumpMachine operación imprime el estado actual de la target máquina, por lo que puede proporcionar información valiosa para la depuración y el aprendizaje cuando se usa junto con el simulador de estado completo.

Aplicación de operaciones de un solo cúbit y controladas

A continuación, aplique las operaciones que componen la Perform3qubitQFT propia operación. Q# ya contiene estas y muchas otras operaciones cuánticas básicas en el espacio de nombres Microsoft.Quantum.Intrinsic.

La primera operación aplicada es la operación H (Hadamard) al primer cúbit:

Diagrama que muestra un circuito para tres QFT de cúbit a través del primer Hadamard.

Para aplicar una operación a un cúbit específico de un registro (por ejemplo, un solo Qubit de una matriz Qubit[]) use la notación de índice estándar. Así, la aplicación de la operación H al primer cúbit del registro qs toma la siguiente forma:

H(qs[0]);

Además de aplicar la operación H (Hadamard) a los cúbits individuales, el circuito de QFT consiste principalmente en rotaciones de R1 controladas. Una R1(θ, <qubit>) operación en general deja el componente $\ket$ del cúbit sin cambios al aplicar una rotación de $e^{i\theta}$ al componente $\ket{0}{1}$.

Q# permite condicionar fácilmente la ejecución de una operación a uno o varios cúbits de control. En general, se antepone Controlled a la llamada, y los argumentos de la operación cambian de la siguiente manera:

Op(<normal args>) $\to$ Controlled Op([<control qubits>], (<normal args>))

Tenga en cuenta que el argumento de los cúbits de control deben proporcionarse como una matriz, aunque se trate de un solo cúbit.

Las operaciones controladas en QFT son las R1 operaciones que actúan en el primer cúbit (y controladas por los cúbits segundo y tercero):

Diagrama que muestra un circuito para tres transformaciones cuánticas de Fourier de cúbit a través del primer cúbit.

En el archivo de Q#, llame a estas operaciones con estas instrucciones:

Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));

La función PI() se utiliza para definir las rotaciones en términos de radianes pi.

Aplicar operación SWAP

Después de aplicar las operaciones pertinentes H y rotaciones controladas a los cúbits segundo y tercero, el circuito tiene este aspecto:

//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));

//third qubit:
H(qs[2]);

Por último, aplica una SWAP operación a los cúbits primero y tercero para completar el circuito. Esto es necesario porque la naturaleza de la transformación cuántica de Fourier hace que los cúbits salgan en orden inverso, por lo que los intercambios permiten una integración perfecta de la subrutina en algoritmos de mayor tamaño.

SWAP(qs[2], qs[0]);

Ahora que ha terminado de escribir las operaciones en el nivel de cúbit de la transformación cuántica de Fourier en nuestra operación de Q#:

Diagrama en el que se muestra un circuito para tres transformaciones cuánticas de cuatro cúbits.

Desasignación de cúbits

El último paso es llamar de nuevo a DumpMachine() para ver el estado después de la operación y desasignar los cúbits. Los cúbits estaban en el estado $\ket{0}$ cuando los asignó y deben restablecerse a su estado inicial mediante la operación ResetAll.

Requerir que todos los cúbits se restablezcan explícitamente a $\ket{0}$ es una característica básica de Q#, ya que permite a otras operaciones conocer su estado exactamente cuando comienzan a usar esos mismos cúbits (un recurso escaso). Esto también asegura que no se entrelazarán con ningún otro cúbit del sistema. Si el restablecimiento no se realiza al final de un bloque de asignación use, puede producirse un error en tiempo de ejecución.

Agregue las siguientes líneas al archivo de Q#:

Message("After:");
DumpMachine();

ResetAll(qs); // deallocate qubits

La operación completa de QFT

El Q# programa se ha completado. Su archivo QFTcircuit.qs ahora debería tener este aspecto:

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3qubitQFT() : Unit {

        use qs = Qubit[3]; // allocate three qubits

        Message("Initial state |000>:");
        DumpMachine();

        //QFT:
        //first qubit:
        H(qs[0]);
        Controlled R1([qs[1]], (PI()/2.0, qs[0]));
        Controlled R1([qs[2]], (PI()/4.0, qs[0]));

        //second qubit:
        H(qs[1]);
        Controlled R1([qs[2]], (PI()/2.0, qs[1]));

        //third qubit:
        H(qs[2]);

        SWAP(qs[2], qs[0]);

        Message("After:");
        DumpMachine();

        ResetAll(qs); // deallocate qubits

    }
}

Ejecución del circuito QFT

Por ahora, la Perform3qubitQFT operación no devuelve ningún valor: la operación devuelve Unit el valor . Más adelante, modificará la operación para devolver una matriz de resultados de medida (Result[]).

  1. Al ejecutar un Q# programa, debe agregar un EntryPoint al Q# archivo . Este atributo indica al compilador que esta operación es el punto de entrada del programa. Agregue la línea siguiente a la parte superior del archivo antes de Q# la Perform3qubitQFT operación :

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. Antes de ejecutar el programa, debe establecer el target perfil en Sin restricciones. Seleccione Ver-> Paleta de comandos, busque QIR, seleccione Q#: Establezca el perfil de Azure Quantum QIR targety, a continuación, seleccione Q#: sin restricciones.

  3. Para ejecutar el programa, seleccione Ejecutar Q# archivo en la lista desplegable icono de reproducción en la parte superior derecha o presione Ctrl+F5. El programa ejecuta la operación o función marcada con el @EntryPoint() atributo en el simulador predeterminado.

  4. Las Message salidas y DumpMachine aparecen en la consola de depuración.

Nota

Si el target perfil no está establecido en Sin restricciones, recibirá un error al ejecutar el programa.

Descripción de la salida del circuito QFT

Cuando se llama en el simulador de estado completo, DumpMachine() proporciona estas representaciones múltiples de la función de onda del estado cuántico. Los posibles estados de un sistema de $n$ cúbits se pueden representar mediante estados de base computacional $2^n$, cada uno con un coeficiente complejo correspondiente (una amplitud y una fase). Los estados de base computacional se corresponden con todas las cadenas binarias posibles de longitud $n$; es decir, todas las combinaciones posibles de estados de cúbit $\ket{0}$ y $\ket{1}$, donde cada dígito binario corresponde a un cúbit individual.

La primera fila proporciona un comentario con los identificadores de los cúbits correspondientes en su orden significativo. Que el cúbit 2 sea el "más significativo" significa que, en la representación binaria del vector de estado base $\ket{i}$, el estado de cúbit 2 corresponde al dígito situado más a la izquierda. Por ejemplo, $\ket{6} = \ket{110}$ se compone de los cúbits 2 y 1, ambos en $\ket{1}$, y del cúbit 0 en $\ket{0}$.

El resto de las filas describen la amplitud de probabilidad de medir el vector de estado base $\ket{i}$ en formato cartesiano y polar. Examen de la primera fila del estado de entrada $\ket{000}$:

  • |0>: esta fila corresponde al estado de base computacional 0 (como el estado inicial después de la asignación era $\ket{000}$, esperaríamos que este fuera el único estado con amplitud de probabilidad en este momento).
  • 1.000000 + 0.000000 i: amplitud de la probabilidad en formato cartesiano.
  • == : el signo equal separa ambas representaciones equivalentes.
  • ********************: una representación gráfica de la magnitud. El número de * es proporcional a la probabilidad de medir este vector de estado.
  • [ 1.000000 ]: valor numérico de la magnitud.
  • --- : representación gráfica de la fase de la amplitud.
  • [ 0.0000 rad ]: valor numérico de la fase (en radianes).

Tanto la magnitud como la fase se muestran con una representación gráfica. La representación de la magnitud es sencilla: muestra una barra de * y, cuanto mayor sea la probabilidad, mayor será la barra.

La salida muestra que las operaciones programadas transformaron el estado de

$$ \ket{\psi}_{initial} = \ket{000} $$

to

$$ \begin{align} \ket{\psi}_{final} &= \frac{1}{\sqrt{8}} \left( \ket{000} + \ket{001} + \ket + \ket{010} + \ket + \ket{100}{011} + \ket + \ket + \ket{101} ket{110} + \ket{111} \right) \\ &= \frac{1}{\sqrt{2^n}}\sum_{j=0}^{2^n-1} \ket{j}, \end{align} $$

que es precisamente el comportamiento de la transformación de Fourier de tres cúbits.

Si tiene curiosidad sobre cómo se ven afectados otros estados de entrada, le recomendamos que pruebe a aplicar operaciones de cúbits antes de la transformación.

Adición de medidas al circuito QFT

La presentación de la función DumpMachine mostró los resultados de la operación; lamentablemente, una piedra angular de la mecánica cuántica indica que un sistema cuántico real no puede tener esta función DumpMachine. En su lugar, la información se extrae mediante medidas que, por lo general, no solo no proporcionan información sobre el estado cuántico completo, sino que también pueden modificar drásticamente el propio sistema.

Hay muchos tipos de medidas cuánticas, pero el ejemplo se centra en la más básica: la medida de proyección en cúbits únicos. Tras medir en una base determinada (por ejemplo, la base de cálculo $ { \ket{0}, \ket{1} } $), el estado del cúbit se proyecta en el estado base que se midió, lo que destruye cualquier superposición entre los dos.

Modificación de la operación QFT

Para implementar las medidas dentro de un programa de Q#, se usa la operación M, que devuelve un tipo Result.

En primer lugar, modifique la operación Perform3QubitQFT para devolver una matriz de resultados de medida, Result[], en lugar de Unit.

operation Perform3QubitQFT() : Result[] {

Definición e inicialización de la matriz Result[]

Antes de asignar cúbits, declare y enlace una matriz de tres elementos (una Result para cada cúbit):

mutable resultArray = [Zero, size = 3];

La palabra clave mutable que precede a resultArray permite modificar la variable más adelante en el código; por ejemplo, al agregar los resultados de la medida.

Medida en un bucle for y adición de los resultados a la matriz

Después de las operaciones de transformación de QFT, inserte el código siguiente:

for i in IndexRange(qs) {
    set resultArray w/= i <- M(qs[i]);
}

La función IndexRange a la que se llama en una matriz (por ejemplo, nuestra matriz de cúbits, qs) devuelve un intervalo de índices de la matriz. Aquí, se usa en el bucle for para medir secuencialmente cada cúbit con la instrucción M(qs[i]). A continuación, cada tipo de Result medido (Zero o One) se agrega a la posición del índice correspondiente en resultArray con una instrucción de actualización y reasignación.

Nota:

La sintaxis de esta instrucción es única para Q#, pero corresponde a la reasignación de variables similar resultArray[i] <- M(qs[i]) que se ve en otros lenguajes, como F# y R.

La palabra clave set siempre se usa para reasignar variables enlazadas mediante mutable.

Devuelve resultArray.

Con los tres bits cúbits medidos y los resultados agregados a resultArray, es seguro restablecer y desasignar los cúbits como antes. Para devolver las medidas, inserte:

return resultArray;

Ejecutar el circuito QFT con las medidas

Ahora, cambie la ubicación de las funciones DumpMachine para generar el estado antes y después de las medidas. El código de Q# final tendrá el aspecto siguiente:

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3QubitQFT() : Result[] {

        mutable resultArray = [Zero, size = 3];

        use qs = Qubit[3];

        //QFT:
        //first qubit:
        H(qs[0]);
        Controlled R1([qs[1]], (PI()/2.0, qs[0]));
        Controlled R1([qs[2]], (PI()/4.0, qs[0]));

        //second qubit:
        H(qs[1]);
        Controlled R1([qs[2]], (PI()/2.0, qs[1]));

        //third qubit:
        H(qs[2]);

        SWAP(qs[2], qs[0]);

        Message("Before measurement: ");
        DumpMachine();

        for i in IndexRange(qs) {
            set resultArray w/= i <- M(qs[i]);
        }

        Message("After measurement: ");
        DumpMachine();

        ResetAll(qs);
        Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
        return resultArray;

    }
}

Sugerencia

Recuerde guardar el archivo cada vez que introduzca un cambio en el código antes de volver a ejecutarlo.

  1. Agregue un EntryPoint elemento antes de la Perform3qubitQFT operación :

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. Establezca el target perfil en Sin restricciones. Haga clic en el botón QIR: Base en la parte inferior de la ventana de VS Code y seleccione Sin restricciones en el menú desplegable. Si el target perfil no está establecido en Sin restricciones, recibirá un error al ejecutar el programa.

  3. Para ejecutar el programa, seleccione Ejecutar Q# archivo en la lista desplegable icono de reproducción en la parte superior derecha o presione Ctrl+5. El programa ejecuta la operación o función marcada con el @EntryPoint() atributo en el simulador predeterminado.

  4. Las Message salidas y DumpMachine aparecen en la consola de depuración.

La salida debe ser similar a la salida:

Before measurement: 
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|1>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|2>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|3>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|4>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|5>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|6>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|7>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
After measurement:
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|1>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|2>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|3>:     1.000000 +  0.000000 i  ==     ******************** [ 1.000000 ]     --- [  0.00000 rad ]
|4>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|5>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|6>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|7>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]

Post-QFT measurement results [qubit0, qubit1, qubit2]: 
[One,One,Zero]

Esta salida muestra algunas cosas diferentes:

  1. Al comparar el resultado devuelto con la medida previa DumpMachine, no se ilustra claramente la superposición posterior de QFT sobre los estados base. Una medida solo devuelve un estado base único, con una probabilidad determinada por la amplitud de ese estado en la función de onda del sistema.
  2. A partir de la medida posterior DumpMachine, se puede ver que la medida cambia el estado en sí y lo proyecta desde la superposición inicial sobre los estados base al estado base único que corresponde al valor medido.

Si repite esta operación muchas veces, las estadísticas de los resultados comienzan a ilustrar la superposición igualmente ponderada del estado posterior a QFT que da lugar a un resultado aleatorio en cada iteración. Sin embargo, además de ser un método ineficaz e imperfecto, solo reproduciría las amplitudes relativas de los estados base, no las fases relativas entre ellos. Esto último no es un problema en este ejemplo, pero vería que aparecen fases relativas si se da una entrada más compleja a QFT que $\ket{000}$.

Uso de las Q# operaciones para simplificar el circuito QFT

Tal y como se mencionó en la introducción, gran parte de la potencia de Q# se debe al hecho de que permite olvidarse de las preocupaciones de tener que tratar con cúbits individuales. De hecho, si desea desarrollar programas cuánticos aplicables a escala completa, tener que preocuparse de si una operación H va antes o después de una rotación determinada es un lastre.

El Q# espacio de nombres Microsoft.Quantum.Canon contiene la ApplyQFT operación , que puede usar y aplicar para cualquier número de cúbits.

  1. Para acceder a la ApplyQFT operación, agregue open la instrucción para el Microsoft.Quantum.Canon espacio de nombres al principio del Q# archivo:

    open Microsoft.Quantum.Canon;
    
  2. Reemplace todo desde el primero H al SWAP reemplazado por:

    ApplyQFT(qs);
    
  3. Vuelva a ejecutar el Q# programa y observe que la salida es la misma que antes.

  4. Para ver la ventaja real del uso Q# de operaciones, cambie el número de cúbits a algo distinto 3de :

mutable resultArray = [Zero, size = 4];

use qs = Qubit[4];
//...

Así puede aplicar la operación QFT adecuada para un número determinado de cúbits sin tener que preocuparse por el desorden de las nuevas operaciones y rotaciones de H en cada cúbit.

Pasos siguientes

Explore otros tutoriales de Q#:

  • El generador de números aleatorios cuánticos muestra cómo escribir un Q# programa que genera números aleatorios fuera de cúbits en superposición.
  • El algoritmo de búsqueda de Grover muestra cómo escribir un Q# programa que usa el algoritmo de búsqueda de Grover.
  • El entrelazamiento cuántico muestra cómo escribir un Q# programa que manipula y mide cúbits y muestra los efectos de la superposición y el entrelazamiento.
  • Quantum Katas son tutoriales autodirigido y ejercicios de programación destinados a enseñar los elementos de la computación cuántica y Q# la programación al mismo tiempo.