Algoritmo de Shor
El algoritmo de Shor es un algoritmo cuántico desarrollado por Peter Shor en 1994, diseñado para factorizar números enteros de manera eficiente.
Su importancia radica en que puede resolver este problema en tiempo polinomial, en contraste con los mejores algoritmos clásicos conocidos, que requieren tiempo subexponencial o exponencial.
La aplicación más destacada está en la criptografía, ya que amenaza la seguridad de los sistemas basados en la factorización de números grandes, como RSA.
Objetivo
Sección titulada «Objetivo»El objetivo del algoritmo de Shor es encontrar los factores de un número entero compuesto .
La clave del algoritmo radica en identificar el período de la función , donde es el número que se quiere factorizar, es un número elegido al azar tal que y es coprimo de (es decir, y no comparten factores primos), y es el período buscado.
Para lograrlo, utiliza un enfoque híbrido que combina cálculos clásicos con una subrutina cuántica basada en la estimación de fases.
Intuición
Sección titulada «Intuición»Factorizar un número implica encontrar dos números y tal que .
Otra forma de expresar este problema es a partir de la búsqueda del período u orden de un número , tal que , y debe ser lo más chico posible y mayor a . Por ejemplo:
En este caso , por lo que cada 2 valores se repite la secuencia.
Una vez identificado , se obtienen los factores de de la siguiente manera ( significa Greatest Common Divisor, que en español se traduce como , Máximo Común Divisor):
El algoritmo de Shor resuelve el problema de factorización utilizando una solución mixta, con componentes clásicos y con un componente cuántico que resuelve el problema de búsqueda del orden.
Este algoritmo tiene tres etapas clave:
-
Clásico: Inicialización y selección de candidato
-
Cuántico: Estimación de fases aplicado al candidato
-
Clásico: Obtención del orden y los factores de
Estos pasos se pueden descomponer en componentes funcionales más chicos y menos misteriosos:
Figura (1): Diagrama del algoritmo de Shor, identificando sus componentes funcionales clásicas y cuánticas.
Los componentes funcionales de este algoritmo se pueden a reducir a los siguientes pasos:
-
Clásico: Si es par, y . Terminar.
-
Clásico: Si es una potencia de números primos, existe un algoritmo clásico eficiente para obtener y por lo tanto los factores y . Terminar.
-
Clásico: Si no es par ni una potencia de números primos, obtener un número entre , tal que . Si , entonces y . Terminar.
-
Cuántico: Si (son coprimos), obtener usando la subrutina cuántica de estimación de fases.
-
Clásico: Obtener de utilizando el algoritmo de fracciones continuas.
-
Clásico: Si es impar volver al paso 3. Verificar que , y que tal que y , en caso contrario volver al paso 3.
-
Clásico: Obtener los factores:
Resolviendo estos pasos se soluciona el problema de factorización. En las próximas secciones se profundiza matemáticamente y se implementan estos pasos.
Solución
Sección titulada «Solución»A continuación se divide la explicación de la solución en las tres etapas definidas previamente, a modo de simplificar la lectura del artículo.
Inicialización y selección de candidato
Sección titulada «Inicialización y selección de candidato»Antes de siquiera elegir el candidato, se verifica que no sea par. En caso contrario los factores son obvios, y .
Verificar que el número sea primo antes de empezar es ideal para evitar la búsqueda de factores no existentes (además de y de sí mismo).
Por otra parte, si es una potencia de números primos, es decir siendo un número primo, se puede obtener su exponente con un algoritmo clásico eficiente. Así, y .
Generar un valor aleatorio de tal que . Verificar , si es diferente a , significa que no son coprimos, por lo que es un factor, obteniendo y .
Si , se procede a la estimación de fases aplicada a .
Estimación de fases aplicado al candidato
Sección titulada «Estimación de fases aplicado al candidato»La estimación de fases permite obtener el autovalor de un autovector para una operación U. Este requiere tres parámetros como entrada:
- El primero, , es un registro inicializado en donde se obtendrá , la fracción binaria del ángulo del autovalor . Su tamaño condiciona la precisión de la fracción. A mayor tamaño, mayor precisión.
- Por otra parte, el segundo parámetro es la puerta controlada de U.
- Finalmente, el tercer parámetro es , un registro que representa el autovector de U, del cual se quiere obtener su autovalor.
El primer parámetro es fácil de preparar: un registro de cubits inicializado en . El segundo depende de U, la cual se define a continuación.
Se define U en una forma general de la siguiente manera:
Aplicar U reiteradas veces sobre retorna una potencia de :
A continuación se presenta , un estado cuántico del cual se quiere probar que es autovector de U con autovalor :
Ahora se aplica U sobre para verificar su autovalor.
Cálculo
Por otra parte, al saber que es autovector de U, ocurre el fenómeno de retroceso de fase cuando se aplica la puerta controlada de U:
Para conseguir el tercer parámetro, se debe obtener el estado , el cual se sabe que es autovector de U. Se construye una superposición de que suple esta necesidad:
Cálculo
Si :
Si , donde :
En formato de serie queda de la siguiente manera:
Finalmente, como solo toma el valor cuando :
Así, se tienen los tres parámetros necesarios para la implementación de estimación de fase, por lo que se obtiene el siguiente circuito:

Figura (2): Estructura de la primitiva EF aplicada al algoritmo de Shor.
Se aplica el algoritmo de estimación de fases sobre . Como el autovalor de es , el resultado es:
Cálculo
Teniendo en cuenta el circuito de la Figura 2:
Se obtiene finalmente el valor de luego de aplicar una lectura en . Este valor es estimado a un nivel de precisión dependiente de la cantidad de cubits provistos.
Obtención del orden y los factores de N
Sección titulada «Obtención del orden y los factores de N»Luego de obtener , que es una fracción binaria con formato , se convierte la fracción binaria a una fracción decimal de la siguiente manera:
Una vez obtenido el valor de la fracción decimal, se utiliza un algoritmo para convertir de la fracción decimal a una fracción continua que tiene el siguiente formato:
El resultado de este algoritmo será un arreglo de valores enteros que representa la fracción continua, . Por ejemplo, con el valor :
Se obtiene la fracción continua como un arreglo de los valores enteros:
A partir de este arreglo es posible conseguir el numerador y denominador de la fracción:
Por lo que se extrae a partir de la fracción continua, siendo este el valor con mayor precisión posible de menor a .
El último paso a realizar es comprobar que es un valor correcto y extraer los factores. En todos los casos en los que no sea correcto, se debe elegir un nuevo candidato y reiterar el algoritmo cuántico para obtener otro valor de posible.
El valor de es correcto cuando es par, es decir que . A su vez, tiene que cumplir que , siendo esta la premisa del problema del orden.
Por otra parte se computa y se verifica que , para no obtener los factores “obvios” de , es decir y .
Finalmente se obtienen los valores de y de la siguiente manera:
Ejemplos
Sección titulada «Ejemplos»Dentro de esta sección se implementará el algoritmo de Shor. Se presentará un ejemplo simple y otro ejemplo más general, expresando limitaciones y desafíos a la hora de implementar este algoritmo.
Factorizando el número 15
Sección titulada «Factorizando el número 15»Como primer ejemplo se factoriza el número con el candidato , cuyos factores y son y .
Los ejemplos con Quirk comprenden la parte cuántica del algoritmo, por lo que luego se debe realizar el algoritmo de fracciones continuas para obtener los factores y .
Por otra parte en el ejemplo de Qiskit, se resuelve el problema completo, con partes de computación clásica y cuántica.
Se interpreta como una fracción binaria.
Por ejemplo si se obtiene el valor
Luego, con fracciones continuas se tiene que . Finalmente, .
Como resultado, , .
import sysimport randomfrom math import gcdfrom modules.initialization import initializationfrom modules.continued_fractions import get_orderfrom modules.shor_phase_estimation import shor_cmod15
N=15res = initialization(N)finished = Falsemax_tries = 1000000while not finished and max_tries > 0: max_tries -= 1 candidate = random.choice([7]) if gcd(N, candidate) != 1: continue s_over_r_results = shor_cmod15(candidate) s_over_r_binary = max(s_over_r_results, key=s_over_r_results.get) r = get_order(s_over_r_binary, N) if r == -1: continue print(f"Candidato: {candidate}, r: {r}, s_sobre_r: {s_over_r_binary}") print(f"{candidate}^{r} mod {N}: {candidate**r % N}") if ( r % 2 == 1 or candidate**r % N != 1 or candidate ** (r // 2) % N == 1 or candidate ** (r // 2) % N == N - 1 ): print("Obtener otro candidato.") continue p = gcd(N, candidate ** int(r / 2) - 1) q = gcd(N, candidate ** int(r / 2) + 1)
finished = True print(f"p: {p}, q: {q} [Shor]")
if not finished: print("Fallo al factorizar N.") sys.exit(1)
from math import sqrt, ceilimport sys
def is_prime(n: int) -> bool: """ Determina si un número es primo. """ if n < 2: return False for i in range(2, n): if n % i == 0: return False return True
def eratosthenes(n): """ Criba de Eratóstenes para encontrar los números primos menores o iguales a n. """ multiples = [] for i in range(2, n+1): if i not in multiples: for j in range(i*i, n+1, i): multiples.append(j) return [x for x in range(2, n+1) if x not in multiples]
def power_of_prime(primes, N): """ Determina si N es una potencia de un número primo. """ for p in primes: if N % p == 0: exp=0 while N % p == 0: N = N // p exp += 1 if N == 1: return p, exp else: return -1,-1 return -1,-1
def initialization(N): if N % 2 == 0: print(f"p: 2, q: {int(N/2)} [N es par]") sys.exit(0)
if is_prime(N): print(f"p: {N}, q: 1 [N es primo]") sys.exit(0)
prime_factors = eratosthenes(ceil(sqrt(N))) x, exp = power_of_prime(prime_factors, N) if x != -1: print(f"p: {x}, q: {x**(exp-1)} [N es potencia de un número primo]") sys.exit(0)
from qiskit import QuantumCircuit, transpilefrom qiskit_aer import QasmSimulatorimport numpy as np
N_COUNT = 8 # qubits de salida
def run_circuit(qc: QuantumCircuit): sim = QasmSimulator() transpiled = transpile(qc) result = sim.run(transpiled.decompose(reps=6)).result() return result
def c_amod15(a, power): """ Puerta controlada de multiplicación por a módulo 15. """ if a not in [2,4,7,8,11,13]: raise ValueError("'a' debe ser 2,4,7,8,11 o 13") U = QuantumCircuit(4) for _iteration in range(power): if a in [2,13]: U.swap(2,3) U.swap(1,2) U.swap(0,1) if a in [7,8]: U.swap(0,1) U.swap(1,2) U.swap(2,3) if a in [4, 11]: U.swap(1,3) U.swap(0,2) if a in [7,11,13]: for q in range(4): U.x(q) U = U.to_gate() U.name = f"{a}^{power} mod 15" c_U = U.control() return c_U
def qft_dagger(n): """ Aplica la Transformada de Fourier Cuántica inversa de n qubits al circuito """ qc = QuantumCircuit(n) # Don't forget the Swaps! for qubit in range(n//2): qc.swap(qubit, n-qubit-1) for j in range(n): for m in range(j): qc.cp(-np.pi/float(2**(j-m)), m, j) qc.h(j) qc.name = "QFT†" return qc
def shor_cmod15(a): """ Estimación de fase aplicada a buscar el orden de N=15 """ # N_COUNT qubits de salida, y 4 qubits para U (qubits de entrada) qc = QuantumCircuit(N_COUNT + 4, N_COUNT)
# creación de superposición for q in range(N_COUNT): qc.h(q)
# inicialización de qubits de entrada en |1> qc.x(N_COUNT)
# aplicación de puertas controladas for q in range(N_COUNT): qc.append(c_amod15(a, 2**q), [q] + [i+N_COUNT for i in range(4)])
# inversa QFT qc.append(qft_dagger(N_COUNT), range(N_COUNT))
# medición qc.measure(range(N_COUNT), range(N_COUNT)) print(qc.draw(fold=-1))
res=run_circuit(qc) return res.get_counts()
def decimal_from_binary(binary: str): """ Convierte un número binario a un número decimal. """ decimal = 0 for i in range(len(binary)): decimal += int(binary[i]) * 2 ** -(i + 1) fraction_num = int(decimal * 10 ** len(binary)) fraction_denom = int(10 ** len(binary)) return fraction_num, fraction_denom
def continued_fraction(num: int, denom: int, res=[], debug=False): """ Convierte un decimal a una fracción continua. """ division = denom // num rest = denom % num res.append(division) if debug: print("[num]:", num, "[denom]:", denom, "[division]:", division, "[rest]:", rest) if rest == 0: return [0] + res return continued_fraction(rest, num, res, debug=debug)
def from_continued_fraction(continued_fraction): """ Convierte una fracción continua a la fracción correspondiente. """ num, denom = 1, 0 for u in reversed(continued_fraction): num, denom = denom + num * u, num return num, denom
def get_order(binary_decimal: str, N: int, debug=False): fraction_num, fraction_denom = decimal_from_binary(binary_decimal) if fraction_num == 0: return -1 if debug: print(f"Fracción: {fraction_num}/{fraction_denom}") fractions = continued_fraction(fraction_num, fraction_denom, res=[], debug=debug) if debug: print("Fracción continua:", fractions)
i = 1 finish = i-1 == len(fractions) order=0 while not finish: s, r = from_continued_fraction(fractions[0:i]) if debug: print("[s]:", s, "[r]:", r) if r > N: break i += 1 finish = i-1 == len(fractions) order = r
return order
┌───┐ ┌───────┐┌─┐ q_0: ┤ H ├───────■─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤0 ├┤M├───────────────────── ├───┤ │ │ │└╥┘┌─┐ q_1: ┤ H ├───────┼──────────────■──────────────────────────────────────────────────────────────────────────────────────────────────────┤1 ├─╫─┤M├────────────────── ├───┤ │ │ │ │ ║ └╥┘┌─┐ q_2: ┤ H ├───────┼──────────────┼──────────────■───────────────────────────────────────────────────────────────────────────────────────┤2 ├─╫──╫─┤M├─────────────── ├───┤ │ │ │ │ │ ║ ║ └╥┘┌─┐ q_3: ┤ H ├───────┼──────────────┼──────────────┼──────────────■────────────────────────────────────────────────────────────────────────┤3 ├─╫──╫──╫─┤M├──────────── ├───┤ │ │ │ │ │ QFT† │ ║ ║ ║ └╥┘┌─┐ q_4: ┤ H ├───────┼──────────────┼──────────────┼──────────────┼──────────────■─────────────────────────────────────────────────────────┤4 ├─╫──╫──╫──╫─┤M├───────── ├───┤ │ │ │ │ │ │ │ ║ ║ ║ ║ └╥┘┌─┐ q_5: ┤ H ├───────┼──────────────┼──────────────┼──────────────┼──────────────┼───────────────■─────────────────────────────────────────┤5 ├─╫──╫──╫──╫──╫─┤M├────── ├───┤ │ │ │ │ │ │ │ │ ║ ║ ║ ║ ║ └╥┘┌─┐ q_6: ┤ H ├───────┼──────────────┼──────────────┼──────────────┼──────────────┼───────────────┼───────────────■─────────────────────────┤6 ├─╫──╫──╫──╫──╫──╫─┤M├─── ├───┤ │ │ │ │ │ │ │ │ │ ║ ║ ║ ║ ║ ║ └╥┘┌─┐ q_7: ┤ H ├───────┼──────────────┼──────────────┼──────────────┼──────────────┼───────────────┼───────────────┼────────────────■────────┤7 ├─╫──╫──╫──╫──╫──╫──╫─┤M├ ├───┤┌──────┴──────┐┌──────┴──────┐┌──────┴──────┐┌──────┴──────┐┌──────┴───────┐┌──────┴───────┐┌──────┴───────┐┌───────┴───────┐└───────┘ ║ ║ ║ ║ ║ ║ ║ └╥┘ q_8: ┤ X ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├──────────╫──╫──╫──╫──╫──╫──╫──╫─ └───┘│ ││ ││ ││ ││ ││ ││ ││ │ ║ ║ ║ ║ ║ ║ ║ ║ q_9: ─────┤1 ├┤1 ├┤1 ├┤1 ├┤1 ├┤1 ├┤1 ├┤1 ├──────────╫──╫──╫──╫──╫──╫──╫──╫─ │ 7^1 mod 15 ││ 7^2 mod 15 ││ 7^4 mod 15 ││ 7^8 mod 15 ││ 7^16 mod 15 ││ 7^32 mod 15 ││ 7^64 mod 15 ││ 7^128 mod 15 │ ║ ║ ║ ║ ║ ║ ║ ║q_10: ─────┤2 ├┤2 ├┤2 ├┤2 ├┤2 ├┤2 ├┤2 ├┤2 ├──────────╫──╫──╫──╫──╫──╫──╫──╫─ │ ││ ││ ││ ││ ││ ││ ││ │ ║ ║ ║ ║ ║ ║ ║ ║q_11: ─────┤3 ├┤3 ├┤3 ├┤3 ├┤3 ├┤3 ├┤3 ├┤3 ├──────────╫──╫──╫──╫──╫──╫──╫──╫─ └─────────────┘└─────────────┘└─────────────┘└─────────────┘└──────────────┘└──────────────┘└──────────────┘└───────────────┘ ║ ║ ║ ║ ║ ║ ║ ║ c: 8/════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩══╩══╩══╩══╩══╩══╩══╩═ 0 1 2 3 4 5 6 7Candidato: 7, r: 4, s_sobre_r: 110000007^4 mod 15: 1p: 3, q: 5 [Shor]
Factorizando números más grandes
Sección titulada «Factorizando números más grandes»A la hora de factorizar números más grandes se presentan dos problemas.
El primero es el físico. Al no existir máquinas cuánticas con cubits lógicos suficientes, se emula su funcionamiento en computadoras clásicas. Este procedimiento es muy costoso, ya que el crecimiento en cantidad de operaciones es exponencial.
El segundo problema tiene que ver con la implementación de la exponencialización modular. En el ejemplo previo se utilizó una puerta que calculaba el módulo con y se repitió de forma exponencial la utilización de esta puerta según el cubit, es decir, una vez en el primer cubit, 2 veces en el segundo cubit, 4 veces en el tercero, etc.
La aplicación exponencial de las puertas controladas de módulo deshacen la ventaja exponencial provista por el algoritmo. Existen circuitos eficientes que resuelven este problema. Debido al costo de emularlos, se decidió evitar la implementación de un algoritmo general de Shor en Qiskit.