Qubit – Introducción a la Computación Cuántica

Hola con tod@s, esta es la tercera de una serie de publicaciones en las que trabajamos con el tema de computación cuántica, y ahora vamos a profundizar y entender el Qubit y vamos a simularlo en lenguajes de computación clásica; para lo cual hemos elegido Python y Kotlin, ¡Empecemos!

El qubit, como ya lo hemos mencionado en una publicación anterior, es la unidad de información con la que opera la computación cuántica y que posee los principios de la mecánica cuántica: Principio de Superposición (un qubit puede ser 0 o 1 al mismo tiempo), Colapso de función de Onda (Si a un qubit se le observa, su función de onda colapsa y el qubit puede valer 0 o 1 no simultáneo), Entrelazamiento Cuántico (un qubit puede entrelazarse a otro de tal manera que si el uno colapsa en un valor, el otro inmediatamente colapsará en un valor complementario, sin importar la distancia a la que se encuentren), entre otros.

Representación Matemática

Para representar matemáticamente un qubit, haremos uso de un sistema de representación de sistemas cuánticos, denominado Notación de Dirac.

Notación de Dirac

Conocido también como Notación Bra-ket, es la notación estándar en mecánica cuántica para describir los estados cuánticos. Se compone de dos elementos:

\text{Bra =}  \lang \phi \vert \text{, ket =} \vert \psi \rang

El estado de un sistema cuántico se identifica por un vector en el espacio de Hilbert complejo; a este vector se lo denomina ket y para cada uno, existe un vector dual denominado Bra.

A cada ket le corresponde un bra y viceversa.

\lang \phi \vert \rho \rang = (\vert \rho \rang, \lang \phi \vert) \text{ para todos los kets } \vert \rho \rang

Sin entrar en más detalle y para aumentar la comprensión del tema, vamos a aplicar la notación de Dirac al qubit.

El qubit es un elemento del espacio de Hilbert de funciones de onda de dos dimensiones, el cual es generado por los kets {0 y 1} representados como vectores de la siguiente manera:

\vert 0 \rang \text{ ( ket 0 )} \\
\vert 1 \rang \text{ ( ket 1 ), donde: } \\ \\
\vert 0 \rang = \begin{pmatrix}
1  \\
0 \\
\end{pmatrix},
\vert 1 \rang = \begin{pmatrix}
0  \\
1 \\
\end{pmatrix} \\ \\

Siendo vectores ortonormales tal que para cada ket del qubit, existe un bra tal que:

\lang 0 \vert 0 \rang = \lang 1 \vert 1 \rang = 1 \\
\lang 1 \vert 0 \rang = \lang 0 \vert 1 \rang = 0

Ahora bien, decíamos que un qubit está representado por una superposición lineal de elementos. En este sentido podemos expresar matemáticamente un qubit por una combinación lineal de los ket 0 y 1 de la siguiente manera:

\text{ sea } \vert x \rang \text{ un qubit, su representación es:} \\
\vert x \rang = \alpha \vert 0 \rang  + \beta \vert 1 \rang \\
\text{(alpha de ket 0 mas beta de ket 1)}

Donde alpha y beta son números complejos tales que:

\vert \alpha \vert ^2 + \vert \beta \vert ^2 = 1\\

Es decir que la suma de sus módulos al cuadrado sean iguales a 1.

Entendiendo el Qubit

Hagamos un resumen de los conceptos que hemos visto hasta ahora. Primero, vimos que un qubit puede ser 0 o 1 al mismo tiempo cuando no es observado e hicimos uso de una combinación lineal de números complejos y vectores para representarlo. Observemos ahora el siguiente gráfico de la representación del qubit:

include-header


Representación de un Qubit

En este gráfico observamos cómo el qubit puede tomar el valor de 0 y 1 al mismo tiempo o, a su vez, tomar cualquiera de esos valores dependiendo únicamente del valor que tomen los números complejos alpha y beta.

Recuerda que una condición necesaria para que un qubit sea válido, es que la suma de los cuadrados de los módulos de alpha y beta sea igual a 1.

Ahora, vimos que la suma de los cuadrados de los módulos de alpha y beta debe ser igual a 1 pero, ¿Qué significa esto?

Al ser su suma igual a 1 se convierte en una densidad de probabilidad, es decir que en cualquier estado que se encuentre, la suma de las dos funciones van a tener una probabilidad de 100% (en términos semánticos podemos decir que puede ser 20% 0 y 80% 1). Es decir que esta condición convierte a los números complejos que multiplican a los kets dentro del qubit en discriminantes de la probabilidad del qubit de tomar un valor o ambos al mismo tiempo.

Incertidumbre en un qubit

Cuando se opera con qubits, la incertidumbre que se tiene de obtener cierto valor puede tomar 3 escenarios:

  • El qubit ha colapsado en un valor particular y su valor es conocido.
  • El qubit tiene una probabilidad mas alta de salir un valor que otro.
  • El qubit es equiprobable, es decir que la probabilidad de salir un valor u otro es la misma.

Cuando un qubit esta en equiprobabilidad (es decir que cada módulo al cuadrado de alpha y beta son iguales a 0.5) se le conoce como el estado de mayor incertidumbre del qubit.

Simulando el qubit

Vamos entonces ahora a simular qubits haciendo uso de lenguajes de computación clásica, tales como Python y Kotlin en este caso.

Para la representación de un qubit de acuerdo a los conceptos que hemos visto, definimos sus siguientes atributos:

  • Alpha y Beta: dos números complejos.
  • Ket 0 y ket 1: en su representación vectorial.
  • 1 booleano: que nos indica si el qubit ya ha sido observado.
  • Valor: el valor que toma el qubit una vez ha sido observado.

En Python, estos atributos son representados de la siguiente manera:

class Qubit:

    def __init__(self, alpha, beta, value=0, is_observed=False):
        self.alpha = alpha
        self.beta = beta
        self.ket_zero = [1, 0]
        self.ket_one = [0, 1]
        self.value = value
        self.is_observed = is_observed

En kotlin, lo representamos de esta manera:

class Qubit(alpha: Complex, beta: Complex){
    var alpha: Complex
    var beta: Complex
    var ketZero: Array<Int>
    var ketOne: Array<Int>
    var value: Int
    var isObserved: Boolean

    init {
        this.alpha = alpha
        this.beta = beta
        this.ketZero = arrayOf(1, 0)
        this.ketOne = arrayOf(0, 1)
        this.value = 0
        this.isObserved = false
    }

Analizar la función de onda del qubit

Si bien no podemos observar un qubit sin modificarlo, podemos analizar la función de onda del mismo y analizar las probabilidades que tiene un qubit de tomar cierto valor o los dos al mismo tiempo. Esta función no me va a devolver un valor, simplemente imprimirá en pantalla el valor de la función de onda del qubit.
En Python, esta función la representamos de la siguiente manera:

def analyze_wave_function(self):
        print("WaveFunction: ")
        print("|S> = ({0})|0> + ({1})|1>".format(self.alpha.get_complex_value(), self.beta.get_complex_value()))
        print("Probabilities: ")
        print("|S> = ({0}%)|0> + ({1}%)|1>".format(pow(self.alpha.module(), 2) * 100, pow(self.beta.module(), 2) * 100))

en Kotlin tenemos:

fun analyzeWaveFunction(){
        println('WaveFunction: ')
        println('|S> = (' + this.alpha.getComplexValue() + ')|0> + ('+ this.beta.getComplexValue() + ')|1>')
        println('Probabilities: ')
        println('|S> = (' + (this.alpha.module().pow(2)*100) + '%)|0> + (' + (this.beta.module().pow(2)*100) + '%)|1>')
}

Observar el qubit

Como sabemos, observar un qubit es colapsar al qubit en un solo valor únicamente. Para ello, primero vamos a definir dos operaciones privadas que van a colapsar el qubit en el valor cero y uno respectivamente.
En Python sería de la siguiente manera:

    def _set_qubit_to_one(self):
        self.value = 1
        self.alpha.set_values(0, 0)
        self.beta.set_values(1, 0)

    def _set_qubit_to_zero(self):
        self.value = 0
        self.alpha.set_values(1, 0)
        self.beta.set_values(0, 0)

Y en kotlin respectivamente:

    private fun setQubitToOne(){
        this.value = 1
        this.alpha.setValues(0f, 0f)
        this.beta.setValues(1f, 0f)
    }

    private fun setQubitToZero(){
        this.value = 0
        this.alpha.setValues(1f, 0f)
        this.beta.setValues(0f, 0f)
    }

Y ahora vamos a crear la operación de observación del qubit, con el colapso de la función de onda de la siguiente manera:
Primero verificamos si el qubit ya ha sido observado, siendo así solo devolvemos o imprimimos el valor del qubit, caso contrario operaremos de la siguiente manera:

  • Si el cuadrado del módulo de alpha es menor que el cuadrado del módulo de beta: colapsamos el qubit en 1.
  • Si el cuadrado del módulo de alpha es mayor que el cuadrado del módulo de beta: colapsamos el qubit en 0.
  • Si los cuadrados de los módulos de alpha y beta son iguales colpasamos el valor de manera aleatoria.

En python esto sería:

    def observe(self):
        if not self.is_observed:
            if pow(self.alpha.module(), 2) < pow(self.beta.module(), 2):
                self._set_qubit_to_one()

            elif pow(self.alpha.module(), 2) > pow(self.beta.module(), 2):
                self._set_qubit_to_zero()

            else:
                if randint(0, 1) == 1:
                    self._set_qubit_to_one()
                else:
                    self._set_qubit_to_zero()

            self.is_observed = True
        print('\n ...OBSERVING THE QUBIT... \n')
        print('The value of the Qubit is: {0}'.format(self.value))

En kotlin tenemos:

    fun observe(){
        if(!isObserved){
            if(this.alpha.module().pow(2) < this.beta.module().pow(2)){
                this.setQubitToOne()
            }else if(this.alpha.module().pow(2) > this.beta.module().pow(2)){
                this.setQubitToZero()
            }else{
                if(Random.nextInt(0, 1)==1) this.setQubitToOne() else this.setQubitToZero()
            }
            this.isObserved = true
        }
        println('\n ...OBSERVING THE QUBIT... \n')
        println('The value of the Qubit is: ' + this.value)
    }

Finalmente, probamos nuestro qubit.
Primero, vamos a analizar un qubit equiprobable, es decir cuya probabilidad de ser cero o uno sea la misma.

qubit = QubitBuilder.build_equal_opportunity_qubit()
analyze_qubit(qubit)

Nos mostrará el siguiente resultado, en el cual podemos ver que alpha y beta tienen el mismo valor y la suma de sus módulos al cuadrado equivale a 0.5 (o aproximado a 50% de probabilidad):

============= EQUAL-OPPORTUNITY QUBIT =============
WaveFunction: 
|S> = (0.7071067811865475 + 0i)|0> + (0.7071067811865475 + 0i)|1>
Probabilities: 
|S> = (49.999999999999986%)|0> + (49.999999999999986%)|1>

Ahora observamos el qubit, sabiendo que colapsará en cero o uno, en este caso aleatoriamente:

 ...OBSERVING THE QUBIT... 
The value of the Qubit is: 0

Como el qubit ya fue observado, su función de onda colapsó en el valor cero. Si vuelvo a observar su función de onda, ahora estará definida de la siguiente manera:

WaveFunction: 
|S> = (1 + 0i)|0> + (0 + 0i)|1>
Probabilities: 
|S> = (100.0%)|0> + (0.0%)|1>

De la misma forma, podremos probar tanto en Python y en kotlin la construcción y el manejo de qubits equiprobales y de probabilidad aleatoria; para lo cual hemos compartido el código fuente de esta simulación en el siguiente enlace:

https://github.com/nano-bytes/quantum-models

¡Hasta una próxima entrega!

You may also like...