Sistemas cuánticos abiertos

Los sistemas cuánticos que están aislados de sus entornos, de forma que ningún otro sistema interactúa con los cúbits, se denominan sistemas cuánticos cerrados. Por el contrario, un dispositivo que está sujeto a cierta cantidad de interacción, o ruido, de su entorno es un sistema cuántico abierto. En general, estas interacciones entre el sistema y el entorno cambian significativamente la dinámica del sistema y tienen como resultado una disipación cuántica, de modo que la información contenida en el sistema se pierde en su entorno.

El kit de desarrollo de Quantum proporciona un simulador de ruido para la simulación de sistemas cuánticos abiertos. Esta característica permite simular el comportamiento de los programas de Q# bajo la influencia del ruido, y también usar la representación del estabilizador (también conocida como simulación CHP) de algoritmos cuánticos, es decir, algoritmos que constan únicamente de CNOT, Hadamard y puertas de fase.

En este artículo se explican algunos de los conceptos básicos de los sistemas cuánticos abiertos y cómo las operaciones cuánticas pueden afectar a los estados de los sistemas abiertos.

Invocación de los simuladores de ruido desde Python

Debe crear un programa host de Python que invoque el programa cuántico y pueda seguir procesando los resultados devueltos. Para más información, consulte Formas de ejecutar un programa de Q#.

  1. Para empezar, importe la biblioteca QuTiP, una popular biblioteca de Python para manipular estados y procesos de sistemas cuánticos abiertos y cerrados.
import qutip as qt
import numpy as np
import matplotlib.pyplot as plt

  1. Puede habilitar el uso de los simuladores de ruido con el paquete qsharp:
import qsharp

Revisión de los estados cuánticos

Antes de analizar la representación de sistemas cuánticos abiertos, resulta útil revisar rápidamente las representaciones de sistemas cuánticos cerrados. En concreto, el estado de un registro de $n$ cúbits se puede representar como un vector de $2^n$ números complejos. Por ejemplo, el estado de un solo cúbit se puede escribir como un vector de la forma

$$\begin{aligned}\ket{\psi}=\alpha\ket{{0} + \beta\ket{{1}=\left( \begin{matrix}\alpha \ \beta\end{matrix}\right) \end{aligned}$$

donde $\alpha$ y $\beta$ son números complejos, como $|\alpha|^2 + |\beta|^2 = 1$.

En Q#, puede pedir al simulador predeterminado que vuelque el estado que usa para simular programas cuánticos y obtener una descripción de ese estado como un vector de esta forma. Para obtener más información, consulte Funciones de volcado.

  1. En la misma carpeta que el programa host de Python, cree el siguiente programa de Q# en un archivo llamado OpenSystemsConcepts.qs:
namespace OpenSystemsConcepts {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Measurement;
    open Microsoft.Quantum.Random;

    operation DumpPlus() : Unit {
        use q = Qubit();
        within {
            H(q);
        } apply {
            DumpMachine();
        }
    }
}

Nota:

El atributo @EntryPoint() utilizado para las aplicaciones de Q# no puede utilizarse con los programas host. Se producirá un error si está presente en el archivo de Q# al que el host está llamando.

  1. Agregue el código siguiente al programa host para importar la operación de Q#DumpPlus:
from OpenSystemsConcepts import DumpPlus

print(DumpPlus.simulate())

Al llamar a DumpMachine function, se genera la siguiente salida:

# wave function for qubits with ids (least to most significant): 0
∣0❭:     0.707107 +  0.000000 i  ==     ***********          [ 0.500000 ]     --- [  0.00000 rad ]
∣1❭:     0.707107 +  0.000000 i  ==     ***********          [ 0.500000 ]     --- [  0.00000 rad ]

Nota:

Tenga en cuenta que la salida puede tener un aspecto diferente si ejecuta el ejemplo en un cuaderno de Python, como Jupyter Notebook, en lugar de en la línea de comandos, porque esa interfaz entiende cómo reenviar diagnósticos HTML desde el kernel de IQ# al kernel de IPython.

La primera fila proporciona un comentario con los identificadores de los cúbits correspondientes en su orden significativo. El resto de las filas describen la amplitud de probabilidad de medir el vector de estado base $\ket{n}$ en los formatos cartesiano y polar. A continuación indicamos los detalles de la primera fila:

  • ∣0❭: esta fila corresponde al estado base computacional 0.
  • 0.707107 + 0.000000 i : amplitud de probabilidad en formato cartesiano.
  • == : el signo equal separa ambas representaciones equivalentes.
  • ********** : representación gráfica de la magnitud; el número de * es proporcional a la probabilidad de medir este vector de estado.
  • [ 0.500000 ]: valor numérico de la magnitud.
  • --- : representación gráfica de la fase de la amplitud (consulte la salida siguiente).
  • [ 0.0000 rad ] : valor numérico de la fase (en radianes).

El diagnóstico anterior indica que, en el momento en que se llama a DumpMachine, el estado del cúbit lo da el vector $\ket{+} \mathrel{:=} (\ket{{0} + \ket{1}) / \sqrt{2} \approx 0.7071 \ket{{0} + 0.7071 \ket{1}$.

También puede escribir este estado en la notación QuTiP, mediante qt.basis(2, i) para representar $\ket{i}$ en un solo cúbit:

ket0 = qt.basis(2, 0)
ket1 = qt.basis(2, 1)
ket_plus = (1 / np.sqrt(2)) * (ket0 + ket1)
print(ket_plus)
Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket
Qobj data =
[[0.707]
 [0.7.7]]
  1. Al medir un cúbit en el estado $\ket{+}$ en la base $Z$, obtiene 0 y 1 con la misma probabilidad. Esto se puede usar como generador cuántico de bits aleatorios.
operation SampleRandomBit() : Result {
    use q = Qubit();
    H(q);
    return MResetZ(q);
}
from OpenSystemsConcepts import SampleRandomBit
print(sum(SampleRandomBit.simulate() for _ in range(100)))
54
  1. Aunque el estado $\ket{+}$ no es inherentemente aleatorio, puede volver de forma determinista al estado $\ket{0}$ aplicando otra operación de Hadamard:
operation ApplyHTwiceAndMeasure() : Result {
    use q = Qubit();
    H(q);
    H(q);
    return MResetZ(q);
}
from OpenSystemsConcepts import ApplyHTwiceAndMeasure
print(sum(ApplyHTwiceAndMeasure.simulate() for _ in range(100)))
0

Preparación de estados aleatorios

En lugar de preparar $\ket{+}$ y luego medirlo, también podría considerar la posibilidad de voltear una moneda de forma clásica y usar el resultado para preparar el estado $\ket{{0}$ o $\ket{{1}$. Por lo tanto, si el resultado es& quot;headsquot&;, el cúbit se prepara en el estado $\ket{0}$, y si el resultado es "tails", el cúbit se prepara en el estado $\ket{1}$.

  1. Agregue el código siguiente al programa de Q#.
operation PrepareAndMeasureRandomState() : Result {
    use q = Qubit();
    if DrawRandomBool(0.5) {
        X(q);
    }
    return MResetZ(q);
}

La operación PrepareAndMeasureRandomState aplica la X a un cúbit con un 50% de probabilidad, como en el volteo clásico de una moneda. Al hacerlo, los resultados son 50/50 para el estado $\ket{0}$ y el estado $\ket{1}$:

from OpenSystemsConcepts import PrepareAndMeasureRandomState
print(sum(PrepareAndMeasureRandomState.simulate() for _ in range(100)))
45
  1. Sin embargo, ahora, si vuelve a aplicar H, el resultado de la operación no vuelve a un resultado determinista:
operation ApplyHToRandomStateAndMeasure() : Result {
    use q = Qubit();
    if DrawRandomBool(0.5) {
        X(q);
    }
    H(q); // Doesn't get us back to 0 state
    return MResetZ(q);
}
from OpenSystemsConcepts import PrepareAndMeasureRandomState
print(sum(ApplyHToRandomStateAndMeasure.simulate() for _ in range(100)))
42

Operador de densidad

Como resulta, no hay ningún vector único que represente el estado preparado por la operación ApplyHToRandomStateAndMeasure a menos que sepa el resultado aleatorio del volteo de la moneda (DrawRandomBool(0.5)). Si no conoce el resultado del volteo de la moneda, el estado cuántico lo ofrece el siguiente conjunto de vectores de estado,

$$\rho=\lbrace \ket{{0}\text{ con probabilidad }\frac{1}{2}, \ket{{1}\text{con probabilidad }\frac{{1}{{2} \rbrace. $$

Dado un estado cuántico $\ket{\psi}$, la probabilidad del resultado $\ket{\phi}$ después de que la regla de Born dé una medida,

$$\begin{\begin{align} \Pr(\phi|\psi) &=\left|\left\langle\phi|\psi\right\rangle\right|^2 \\&=\left\langle\phi|\psi\right\rangle\left\langle\psi|\phi\right\rangle. \end{align} $$

El truco aquí es promediar los diferentes vectores de estado que podría preparar la operación ApplyHToRandomStateAndMeasure:

$$\begin{\begin{align}\text{Pr}(\phi|\rho) &=\mathbb{E}_{\psi\sim\rho}\left( \text{Pr}(\phi|\psi) \right) \\&=\mathbb{E}_{\psi\sim\rho}\left( \left\langle\phi|\psi\right\rangle\left\langle\psi|\phi\right\rangle\right) \\&=\sum_i \text{Pr}(\psi_i) \left\langle\phi|\psi_i \right\rangle\left\langle\psi_i |\phi\right\rangle\\&=\left\langle\phi\Bigg|\left( \sum_i \text{Pr}(\psi_i) \ket{\psi_i}\bra{\psi_i}\right) \Bigg|\phi\right\rangle. \end{align} $$

La factorización de $\bra{\phi}$ y $\ket{\phi}$ en el último paso nos proporciona una forma nueva y clara de escribir conjuntos de vectores de estado como matrices denominadas operadores de densidad. Por ejemplo, el conjunto $\rho$ también se puede escribir como el siguiente operador de densidad,

$$\begin{align}\rho&=\sum_i \Pr(\psi_i) \ket{\psi_i}\bra{\psi_i}\\&=\frac{{1}{{2}\ket{0}\bra{0} + \frac{1}{2}\ket{{1}\bra{{1}\\&=\frac{{1}{{2}\left( \begin{matrix} 1 & 0 \\ 0 & 1 \end{matrix}\right). \end{align} $$

Mediante los operadores de densidad, puede escribir ambos conjuntos de estados, así como los proyectores en esos vectores de estado. Por ejemplo, el estado $\ket{+}$ se puede escribir como el operador de densidad

$$\begin{align}\ket{+}\bra{+}=\frac{1}{2}\left( \begin{matrix} 1 & 1 \\ 1 & 1 \end{matrix}\right). \end{align} $$

Es decir, aunque tanto SampleRandomBit como PrepareAndMeasureRandomState preparan operadores de densidad con los mismos elementos diagonales (y, por tanto, tienen las mismas probabilidades de medición en la base $Z$), los dos operadores de densidad tienen diferentes elementos no diagonales.

Se dice que los operadores de densidad en general representan estados mixtos y que los estados que se pueden escribir como $\ket{\psi}\bra{\psi}$ para algún vector de estado $\ket{\psi}$ (por ejemplo, $\ket{+}\bra{+}$) son estados puros. Para obtener más información sobre las diferencias entre los estados mixtos y los estados puros, vea operadores de densidad.

Representación de procesos cuánticos

Teniendo en cuenta la operación de Hadamard, puede simular su acción en un estado puro $\ket{\psi}$ como $H \ket{\psi}$. Por analogía directa, puede simular lo que $H$ hace a un operador de densidad multiplicando ambos a la izquierda y a la derecha:

$$\begin{align}\rho \longmapsto H\rho H^{\dagger}= H \rho H. \end{align}$$

Sin embargo, de forma más general, también puede representar procesos en los que una de varias operaciones unitarias se aplica de forma aleatoria. Por ejemplo, supongamos que la operación $H$ funciona el 95% del tiempo, pero el otro 5% del tiempo no hace nada. Simplemente puede agregar los operadores de densidad ponderados por la probabilidad de cada caso:

$$\begin{\begin{align}\rho \longmapsto 0.95 H \rho H + 0.05 \rho. \end{align} $$

Una manera de modelar esto es pensar en la operación $H$ no como representada por una matriz unitaria, sino por una función de operadores de densidad a operadores de densidad.

$$\begin{\begin{align}\Lambda_H(\rho) = H\rho H. \end{\end{align}$$

Dado que las funciones de densidad representan un promedio en diferentes preparaciones y, puesto que las medias son lineales, estas funciones también deben ser lineales en general.

Una función lineal que asigna un operador de densidad a otro operador de densidad se denomina proceso cuántico.

Sugerencia

Además de la linealidad, los procesos cuánticos que se pueden realizar en la práctica cumplen la condición de ser completamente positivos, así como mapas de conservación de seguimiento (CPTP). Un mapa lineal que es completamente positivo y conserva el seguimiento se conoce como canal cuántico.

Hay diferentes maneras de representar procesos cuánticos, pero el método más común al trabajar con modelos de ruido para sistemas cuánticos abiertos es usar superoperadores. Del mismo modo que los operadores son funciones lineales de vectores a vectores, los superoperadores son funciones lineales de operadores a operadores y se pueden escribir mediante matrices.

Por ejemplo, puede usar qt.to_super para convertir algunas matrices unitarias en superoperadores. Observe que el operador $𝟙$ de identidad 2 × 2 que usamos para simular la operación I es una matriz de identidades 4 × 4:

# Convert the 𝟙 matrix used to simulate Microsoft.Quantum.Intrinsic.I (single qubit no-op) into a superoperator.
print(qt.to_super(qt.qeye(2)))
Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True
Qobj data =
[[1.0  0.0  0.0  0.0]
 [0.0  1.0  0.0  0.0]
 [0.0  0.0  1.0  0.0]
 [0.0  0.0  0.0  1.0]]

Como se esperaba, el superoperador resultante del operador de identidad asigna todos los operadores a sí mismos.

Por otro lado, para la operación $X$, el resultado es diferente:

# Convert the 𝑋 matrix used to simulate Microsoft.Quantum.Intrinsic.X into a superoperator.
print(qt.to_super(qt.sigmax()))
Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True
Qobj data =
[[0.0  0.0  0.0  1.0]
 [0.0  0.0  1.0  0.0]
 [0.0  1.0  0.0  0.0]
 [1.0  0.0  0.0  0.0]]

Observe que cada columna es una pila de los elementos en una salida del operador mediante la función $\Lambda_X(\rho) = X \rho X^{\dagger}= X \rho X$.

Puesto que $\Lambda(\ket{{0}\bra{{0}) = X\ket{{0}\bra{0}X =\ket{{1}\bra{1}$, la primera columna es una pila de los elementos de $\ket{{1}\bra{{1}=\left(\begin{matrix} 0 & 0 \\ 0 & 1 \end{matrix}\right)$. De forma similar, la segunda columna es una pila de los elementos de $\Lambda_X(\ket{{0}\bra{{1}) =\ket{1}\bra{0}$:

print(np.array((ket1 * ket0.dag()).data.todense().flat))
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j]

El mismo patrón se puede usar para convertir otros operadores unitarios, como $H$ y $Z$:

H_super = qt.to_super(qt.qip.operations.hadamard_transform())
print(H_super)
Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True
Qobj data =
[[0.5 0.5 0.5 0.5]
 [0.5 -0.5 0.5 -0.5]
 [0.5 0.5 -0.5 -0.5]
 [0.5 -0.5 -0.5 0.5]]
Z_super = qt.to_super(qt.sigmaz())
print(Z_super)
Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True
Qobj data =
[[1.0 0.0 0.0 0.0]
 [0.0 -1.0 0.0 0.0]
 [0.0 0.0 -1.0 0.0]
 [0.0 0.0 0.0 1.0]]

Si revisa el ejemplo del superoperador $Z$, observará que no hay un signo $-1$ en la esquina inferior derecha. Esto se debe a que los operadores de densidad y los superoperadores no tienen la misma ambigüedad de fase global que los vectores de estado y los operadores unitarios.

En concreto, considere la operación $Z$ que actúa en un cúbit en el estado $\ket{1}$. Al simular esto con vectores de estado, el resultado es $Z \ket{{1}= -\ket{{1}$, donde el signo $-$ delante de $\ket{1}$ es, en este caso, una fase global insignificante. Por otro lado, al usar la notación de sistemas abiertos, la misma operación da como resultado $\Lambda_Z(\ket{{1}\bra{{1}) = Z\ket{1}\bra{1}Z^{\dagger}= Z\ket{1}\bra{1}Z = (-\ket{1})(-\bra{{1}) =\ket{{1}\bra{1}$, de modo que las fases globales en las partes "ket" y "bra& de $\ket{{1}\bra{1}$ se cancelan entre sí.

De forma más general, supongamos que $U \ket{\phi}= e^{i\phi}\ket{\phi}$ para un operador unitario $U$, una fase $\phi$ y un vector de estado $\ket{\phi}$. Entonces, dado que $\bra{\phi} U^\dagger= (U \ket{\phi})^\dagger= (e^{i \phi}\ket{\phi})^\dagger=\bra{\phi} e^{-i\phi}$, esto produce la misma cancelación:

$$\begin{\begin{align}\Lambda_U (\ket{\phi}\bra{\phi}) &= U \ket{\phi}\bra{\phi} U^{\dagger}\\&= e^{i\phi}\ket{\phi}\bra{\phi} e^{-i\phi}\\&=\ket{\phi}\bra{\phi}\end{align}$$

Por otro lado, las fases relativas se siguen representando en la notación de sistemas abiertos, que se puede confirmar si se observan las matrices de $\ket{+}\bra{+}$ y $\ket{{-}\bra{{-}$:

print(ket_plus * ket_plus.dag())
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[0.5 0.5]
 [0.5 0.5]]
ket_minus = (ket0 - ket1) / np.sqrt(2)
print(ket_minus * ket_minus.dag())
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[0.5 -0.5]
 [-0.5 0.5]]

Es decir, los elementos no diagonales de los operadores de densidad describen las fases relativas entre cada estado base computacional.

Procesos cuánticos ruidosos

El uso de superoperadores no solo le permite representar operaciones unitarias conocidas, sino también aquellas funciones de operadores de densidad a operadores de densidad que surgen al describir ruido. Por ejemplo, el proceso cuántico $\Lambda(\rho) = 0.95 H\rho H + 0.05 \rho$ se puede escribir como superoperador sumando los superoperadores para $H$ and $I$ ponderados por la probabilidad de cada caso:

lambda_noisy_h = 0.95 * qt.to_super(qt.qip.operations.hadamard_transform()) + 0.05 * qt.to_super(qt.qeye(2))
print(lambda_noisy_h)
Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True
Qobj data =
[[0.525 0.475 0.475 0.475]
 [0.475 -0.425 0.475 -0.475]
 [0.475 0.475 -0.425 -0.475]
 [0.475 -0.475 -0.475 0.525]]

A diferencia de la matriz unitaria $H$ que describe la acción ideal de la operación de Hadamard, el superoperador $\Lambda_{\text{noisy}H}$ simula la acción de la operación de Hadamard cuando tiene una probabilidad del 5% de no hacer nada.

Simulación de ruido en Q#

De forma similar al método .simulate(), las operaciones de Q# importadas en Python exponen un método .simulate_noise() que se puede usar para ejecutar programas de Q# en los simuladores de ruido.

De forma predeterminada, el método .simulate_noise() asume un modelo de error ideal, es decir, sin ruido. Para configurar un modelo de error determinado, puede usar las funciones qsharp.get_noise_model y qsharp.set_noise_model para obtener y establecer el modelo de ruido actual para los simuladores de versión preliminar. Cada modelo de error se representa como un diccionario de los nombres de operación intrínsecos a los objetos que representan los errores en esas operaciones intrínsecas. Para más información, consulte Configuración de modelos de ruido en sistemas abiertos.

Puede simular el superoperador $\Lambda_{text{noisy}H}$ definido anteriormente como modelo de ruido y ejecutar la operación DumpPlus.

  1. En primer lugar, ejecute el código con un modelo de ruido ideal para poder comparar el resultado.
qsharp.config['simulators.noisy.nQubits'] = 1
qsharp.set_noise_model_by_name('ideal')

# Run with an ideal noise model first.
print(DumpPlus.simulate_noise())
'text/plain': 'Mixed state on 1 qubits: [ [0.5000000000000001 + 0 i, 0.5000000000000001 + 0 i] [0.5000000000000001 + 0 i, 0.5000000000000001 + 0 i] ]'
  1. Para configurar el modelo de ruido, establezca lambda_noisy_h como el modelo de ruido que se debe seguir y, a continuación, ejecute la operación DumpPlus para ver los efectos de la operación de Hadamard ruidosa en el estado cuántico.
noise_model = qsharp.set_noise_model_by_name('ideal', h=lambda_noisy_h)

# Run again, using the new noisy superoperator for the `H` operation.
print(DumpPlus.simulate_noise())
'text/plain': 'Mixed state on 1 qubits: [ [0.5249999999999999 + 0 i, 0.47500000000000026 + -7.536865798903469E-33 i] [0.5249999999999999 + 0 i, 0.47500000000000026 + 7.536865798903469E-33 i & 0.4750000000000007 + 0 i] ]'

Al comparar los dos estados cuánticos, puede ver que al agregar una probabilidad a la operación de Hadamard de errores en el modelo de ruido, el simulador de ruido usa la representación del operador de densidad junto con el modelo de ruido definido para simular el efecto de ese ruido en el estado de los cúbits.

Además, hay muchos otros tipos de ruido. Por ejemplo, un modelo de ruido muy conocido es el ruido completamente despolarizador, que da como resultado la misma probabilidad de aplicar las operaciones I, X, Y o Z a un solo cúbit:

[I, X, Y, Z] = map(qt.to_super, [qt.qeye(2), qt.sigmax(), qt.sigmay(), qt.sigmaz()])
completely_depolarizing_process = 0.25 * (I + X + Y + Z)
print(completely_depolarizing_process)
Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True
Qobj data =
[[0.5 0.0 0.0 0.5]
 [0.0 0.0 0.0 0.0]
 [0.0 0.0 0.0 0.0]
 [0.5 0.0 0.0 0.5]]

En la salida, puede ver que el ruido completamente despolarizador asigna todos los operadores de densidad de entrada a la misma salida, más concretamente, $𝟙 / 2$. Es decir, el ruido completamente despolarizador reemplaza cualquier estado anterior por el estado máximamente mixto.

El ruido de despolarización también puede ser de una intensidad finita (es decir, no completamente despolarizador) tomando una combinación lineal de los procesos de identidad y de despolarización completa.

def depolarizing_noise(strength=0.05):
    return strength * completely_depolarizing_process + (1 - strength) * qt.to_super(I)
    
print(depolarizing_noise(0.05))
Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True
Qobj data =
[[0.975 0.0 0.0 0.025]
 [0.0 0.950 0.0 0.0]
 [0.0 0.0 0.950 0.0]
 [0.025 0.0 0.0 0.975]]

Algunos modelos de ruido se pueden representar como una combinación de operadores unitarios, pero hay muchos oros tipos de ruido que no se pueden representar de esta manera. Por ejemplo, por analogía al ruido completamente despolarizador, un proceso que tiene cierta probabilidad de restablecer su estado de entrada se puede representar como una mezcla de un superoperador que siempre se restablece y el proceso de identidad.

Para ver esto, considere el proceso $\Lambda_{\text{Reset}}(\rho) = Tr(\rho) \ket{{0}\bra{{0}$. Dado que este proceso hace lo mismo con cada operador de densidad como entrada, la primera y la cuarta columna deben ser iguales, es decir, $\left(\begin{array} 1 & 0 & 0 & 0 \end{array}\right)^{\text{T}}$.

Por otro lado, la segunda y la tercera columna representan la salida de $\Lambda_{\text{Reset}}$ que actúa sobre $\ket{{0}\bra{{1}$ y $\ket{{1}\bra{0}$ respectivamente. Ninguno de ellos es un operador de densidad válido por sí solo, de hecho, $Tr(\ket{0}\bra{1}) = Tr(\ket{{1}\bra{{0})$, de modo que el factor de seguimiento pone ambas columnas a cero.

lambda_reset = qt.Qobj([
    [1, 0, 0, 1],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
], dims=completely_depolarizing_process.dims)

Puede usar lambda_reset para representar el ruido de amortiguación de amplitud, en el que existe una posibilidad finita de restablecer cada cúbit:

def amplitude_damping_noise(strength=0.05):
    return strength * lambda_reset + (1 - strength) * qt.to_super(I)
print(amplitude_damping_noise(0.05))
Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = False
Qobj data =
[[1.0 0.0 0.0 0.050]
 [0.0 0.950 0.0 0.0]
 [0.0 0.0 0.950 0.0]
 [0.0 0.0 0.0 0.950]]

Ejemplo: Decadencia de la señal de Ramsey

En este ejemplo, estudiará la evolución de un cúbit cuando le aplique repetidamente la puerta de fase π/4 o S y, a continuación, haga una medición. Este sencillo ejemplo funciona como un buen modelo de juguete para la interferometría de Ramsey, que es una técnica para medir las frecuencias de transición de partículas con precisión atómica.

En ausencia de ruido, una señal de Ramsey sigue oscilando de un lado a otro con cada iteración de S. Agregue el código siguiente al programa cuántico de Q#:

operation ApplySRepeatedlyAndMeasure(nRepetions : Int) : Result {
    use q = Qubit();
    within {
        H(q);
    } apply {
        for _ in 1..nRepetions {
            S(q);
        }
    }
    return MResetZ(q);
}

La operación ApplySRepeatedlyAndMeasure establece un cúbit en superposición, aplica la operación de fase π/4 nRepetionsveces y, a continuación, devuelve la medida en el eje $Z$. Agregue el código siguiente al programa host para simular la operación ApplySRepeatedlyAndMeasure en ausencia de ruido.

from OpenSystemsConcepts import ApplySRepeatedlyAndMeasure

qsharp.set_noise_model_by_name('ideal')

ns = np.arange(1, 101)
signal_wo_noise = [
    sum(ApplySRepeatedlyAndMeasure.simulate_noise(nRepetions=n) for _ in range(100))
    for n in ns
]

print(plt.plot(ns, signal_wo_noise))

Trazado de la señal ramsey en la absense de ruido.

Por otro lado, si la operación $S$ tiene algún ruido de amortiguación de amplitud finita, la señal finalmente decaerá:

S = qt.Qobj([
    [1, 0],
    [0, 1j]
])

qsharp.set_noise_model_by_name(
    'ideal',
    h=(
        depolarizing_noise(0.025) *
        qt.to_super(qt.qip.operations.hadamard_transform())
    ),
    s=(
        amplitude_damping_noise(0.025) *
        qt.to_super(S)
    )
)

# Note that this can take a few moments to run.
signal = [
    sum(ApplySRepeatedlyAndMeasure.simulate_noise(nRepetions=n) for _ in range(100))
    for n in ns
]

print(plt.plot(ns, signal_wo_noise, label='Ideal Signal'))
print(plt.plot(ns, signal, label='With Amplitude Damping Noise'))
plt.legend()

Trazado de la señal de Ramsey ideal y en presencia de ruido.

Representación del ruido en rotaciones de tiempo continuo

Hasta ahora, se han considerado principalmente operaciones cuánticas como H o X. Específicamente, estas operaciones son ambas de tipo Qubit => Unit is Adj + Ctl, lo que indica que no tienen ningún parámetro adicional más allá del cúbit de destino en el que actúan.

Por el contrario, la operación Rz tiene el tipo (Double, Qubit) => Unit is Adj + Ctl, que representa el ángulo de la rotación que se va a realizar, así como el destino. En general, el ruido incurrido por Rz(0.123) no es necesario ser el mismo que el ruido incurrido por Rz(0.456), de modo que también necesite una manera de representar el ruido de tiempo continuo , así como el caso discreto considerado anteriormente.

En sistemas cuánticos cerrados (es decir, sistemas cuánticos que evolucionan sin ruido), la evolución de tiempo continuo se da mediante un operador hamiltoniano $H$ que representa la derivada del tiempo de un estado cuántico según la ecuación de Schrödinger: $$\begin{equation*}\frac{\partial}{\partial t}\ket{\psi(t)}= -iH \ket{\psi(t)}. \end{equation*}$$ Esto se resuelve tomando la matriz exponencial de $H$, $$\begin{aligned}\ket{\psi(t)}&= U(t) \ket{\psi(0)}\text{ donde} \ U(t) &= \exp(-itH). \end{aligned}$$

Por analogía, puede representar el derivado de un operador de densidad en ausencia de ruido mediante la reorganización de la ecuación de Schrödinger: $$\begin{ecuación*}\frac{\partial\partial}{\rho} t(t) = -i[H, ], \end{ecuación*}$$ donde $[A, \rhoB] \mathrel{:=} AB - BA$ es elmutador de $A$ con $B.$ El efecto del ruido se puede incluir en lo anterior agregando uno o varios operadores de salto${L_i}$ que representan los distintos tipos de procesos de ruido que compiten con una evolución coherente: $$\begin{equation*}\frac{\partial}{\partial t}\rho(t) = -i[H, \rho] + \sum_i \left( L_i \rho L_i^\dagger - \frac12 {L_i^\dagger L, \rho}\right) \end{equation*}$$ donde ${A, B} \mathrel{:=} AB + BA$ es el anticonmutador de $A$ con $B$.

Para representar el derivado anterior como una sola matriz como lo hizo con $U(t)$ en el caso de sistemas cerrados, necesita una manera de girar la multiplicación izquierda y derecha de una matriz en multiplicación izquierda de un vector. Puede usar lo que se conoce como convención de apilamiento de columnas para convertirse $\rho$ en un vector $|\rho⟫$; por ejemplo,\Bigg|\left$$ (\begin{matriz a & matriz} b \ c & d}\right\end{)!!!!\Bigg\rangle=\left\Bigg\rangle (\begin{matriz}a \ c \ b \ d\end{matriz}\right). $$A continuación, puede usar la identidad que $|ABC⟫ = C^\mathrm{T}\otimes A |B⟫$ para convertir los sistemas abiertos anteriores derivados en una matriz única $$ G = -i(1 \otimes H - H ^\mathrm{T\otimes} I) + \sum_i \left( L_i^* \otimes L_i - \frac12 (L_i^ L_i)^\mathrm{\daggerT}\otimes 1 - \frac12 1 \otimes (L_i^\dagger L_i)^\mathrm{T}\right). $$ Y esto nos da que $$\rho(t) = \exp(tG) \rho(0). $$

Puede asignar estos generadores a un modelo de ruido mediante la to_generator función proporcionada por el qsharp paquete. Por ejemplo, la acción ideal de la operación Rz viene dada por el hamiltoniano $H = -\frac12 Z$; podemos representar una desalineación de este hamiltoniano junto con los procesos $T_1$ y $T_2$ pasando cada uno a to_generator:

qsharp.set_noise_model_by_name(
    'ideal',
    rz=to_generator(
        -0.48 * qt.sigmaz(),
        t1_dissipation(100.0),
        t2_dissipation(25.0)
    )
)

También puede agregar procesos cuánticos fijos que se aplican antes y después de la evolución continua en el tiempo mediante los argumentos de palabra pre clave y post :

qsharp.set_noise_model_by_name(
    'ideal',
    rz=to_generator(
        -0.48 * qt.sigmaz(),
        t1_dissipation(100.0),
        t2_dissipation(25.0),
        post=depolarizing_process(0.95)
    )
)

Ejemplo: revisión de la evolución de Ramsey

La operación S utilizada en nuestro ejemplo de Ramsey anterior es un caso especial de la operación Rz; específicamente, S aplica una rotación sobre el eje $Z$ de un ángulo de $\pi / 4$. Al representar el ruido de tiempo continuo, también podemos considerar el caso más general.

Vamos a continuar y definiremos una nueva Q# operación para el ejemplo de Ramsey en tiempo continuo:

open Microsoft.Quantum.Measurement;

operation MeasureRamsey(nShots : Int, t : Double) : Int {
    mutable nUp = 0;
    for idxShot in 1..nShots {
        use q = Qubit();
        within {
            H(q);
        } apply {
            Rz(t, q);
        }
        if MResetZ(q) == One {
            set nUp += 1;
        }
    }
    return nUp;
}

A continuación, puede asignar un proceso de ruido en tiempo continuo a la Rz operación:

import qsharp
import qutip as qt

qsharp.set_noise_model_by_name(
    'ideal',
    rz=to_generator(
        -0.48 * qt.sigmaz(),
        t1_dissipation(100.0),
        t2_dissipation(25.0),
        post=depolarizing_process(0.95)
    )
)

Trazado de la señal de Ramsey ideal y en presencia de ruido.

Por último, puede simular en una variedad de tiempos de evolución diferentes y trazar el efecto del ruido anterior:

import numpy as np

ts = np.linspace(0, 10, 101)
n_ups_noiseless = np.array([MeasureRamsey.simulate(nShots=500, t=t) for t in ts])
n_ups = np.array([MeasureRamsey.simulate_noise(nShots=500, t=t) for t in ts])

Aquí, tenga en cuenta que los elementos de ts no son incluso múltiplos de $\pi / 4$, de modo que no se pueden reproducir los resultados anteriores sin asignar un generador de tiempo continuo para describir el ruido en el que se incurre llamando a Rz.

Pasos siguientes