Resumen histórico
La hipótesis de Riemann es el más importante problema no resuelto de las matemáticas, fue formulada por un discípulo de Gauss y matemático alemán Bernhard Riemann en un trabajo para aplicar como profesor ordinario en la universidad de Göttingen (Ueber die Anzahl der Primzahlem unter einer gegebenen Grösse (Sobre el número de primos menores que una cantidad dada, 1859) ).
El presente artículo tiene como objetivo verificar la Hipótesis de Riemann mediante el uso de potentes librerías de Python (SymPy y mpmath) y su visualización usando hvplot con matplotlib y bokeh. La verificación implica calcular la cantidad de ceros no triviales de la función zeta de Riemann en la línea critica 1/2+it.
Fundamentos Matemáticos:
Fórmula del Producto de Euler
En el famoso documento presentado por Riemann inicia con la famosa fórmula descubierta por Euler en 1740 que relaciona la función zeta con un producto infinito que recorre todos los números primos.
La función zeta analizada por Euler tiene la siguiente expresión:

Donde y converge para
El producto de Euler recorre todos los números primos mediante la expresión:

Recordando la serie geométrica

que converge para , podemos relacionarla con este producto infinito y verificar que converge pues
.
Euler demostró una importante relación que según historiadores y matemáticos fue la que inicia la Teoría Analítica de Números

Continuación analítica
En el documento mencionado Riemann extendió la función zeta (que luego se llamo como el en su honor) al campo de los complejos. Por lo tanto la función:

Con
Riemann estudió lo ceros de la función zeta y como para el dominio Re(s) > 1 no existen ceros (porque si existieran también debería existir ceros en el producto de Euler lo que es imposible).
Para analizar con mas detalles la funciona zeta, Riemann extendió analíticamente el dominio de zeta a todo el plano complejo manteniendola la propiedad de ser diferenciables para todo el campo complejo excepto en s = 1 , este procedimiento se denomina continuación (o extensión ) analítica.
A continuación analizaremls la ecuación (una) funcional de la función zeta de Riemann:

Esta ecuación muestra que para valores de n = -2k con y k > 0 la función zeta se anula, estos infinitos ceros se denominan ceros triviales de la función zeta.
Mediante mecanismos de prolongación analítica se puede extender la función zeta a todo el plano complejo y de esa forma se pueden analizar los ceros no triviales de la función zeta de Riemann. La siguiente es otra ecuación funcional de zeta denominada Zeta completa.

Donde es la función gamma compleja.
Ceros de la función Z(s).
Analizando esta ecuación de vemos que tenemos tres partes , los ceros y polos de
excepto en la expresión
que no tiene ni ceros ni polos. La función gamma no tiene ceros y solo tiene polos simples en cero y en los enteros negativos.

Teniendo en cuenta esto tenemos las siguientes propiedades:
tiene un polo simple en 1, al igual que ζ(s). Esto no es una sorpresa porque la suma infinita que define a la función ζ(s) diverge para s = 1.
- Z(s) tiene un polo simple en 0. Esto es debido a que
tiene un polo simple en 0, por lo tanto en la ecuación anterior ambos polos se cancelan y ζ(s) no tiene ni polos , ni cero en 0 , en efecto ζ(0)=−1/2).
- Dado que
no tiene ceros, no hay otros polos en ζ(s), por lo tanto es una funciona entera excepto en s = 1, que como ya habíamos dicho tiene un polo simple.
tiene polos en todos los enteros negativos pares (debido al
). Dado que
no tiene polos en ese conjunto, los polos de
se convierten en ceros para ζ(s). Estos ceros se llaman ceros triviales.
- Los ceros nos triviales son más difíciles de calcular y están íntimamente relacionado con la distribución de los números primos.
- ζ(s)≠0 si Re(s)>1. Una forma de ver esto es a través de la formula del producto de Euler, donde cada término del producto no puede ser cero. esto implica que
es esa región. Podemos reflejar Re(s) > 1 sobre la línea
considerando
. Dado que
,
no puede ser cero en Re(s) < 0 a excepción de los ceros triviales ya mencionados.
Riemman en el mencionado documento conjetura que:
Todos los ceros no triviales de la función ζ(s) tiene su parte real igual a latex \frac{1}{2}$
Bernhard Riemann, 1859
Verificación numérica
La hipotesis de Riemman sigue siendo una conjetura, pues luego de mas de 170 años aún no se pudo demostrar. Riemamn mismo calculó los primeros ceros no triviales de la función que ahora lleva su nombrre.
Actualmente se han calculado billones de ceros no triviales y todos tiene su parte real igual a . Esto implica una demostración?, para nada!!!.
En 1914, el matemático ingles G.H Hardy (1887,1947) demostró que existen infinitos numerables ceros no triviales de la función ζ(s). Billones de ceros es un números exorbitante, pero comparado con el infinito es insignificante. los métodos numéricos solo sirven para encontrar un contra ejemplo que demuestro que una conjetura es falsa, no para demostrar que es cierta.
Como podríamos verificar que todos los ceros no triviales esta en la linea ?. Podemos encontrar ceros de la función ζ(s) mediante métodos numéricos, pero ¿Cómo sabemos que la parte real de esa función es 0.5 y no 0.5000000000000000000000000001?, o bien encontrar una gran cantidad de ceros en la linea critica no implica que podremos encontrar más, y en efecto sera así, pues como deciamos hay infinitos ceros en esa linea.
Además no tenemos la certeza de que no existan otros ceros fuera de la línea critica.
Contando ceros. Parte 1
Debemos encontrar en forma rigurosa las respuestas a las siguientes preguntas:
- ¿Cómo contar el número de ceros de ζ(s) entre Im(s) = 0 e Im(s) = N.
- ¡Como verificar que todos los ceros se posicionan en la línea critica, es decir exactamente en
?
Para contestar la primera pregunta debemos usar herramientas de análisis complejo, en especial el principio del argumento. Este principio dice que si f es una función meromorfa en algún contorno cerrado C, que no existan polos y ceros en el, entonces:

Donde N y P son la cantidad de polos y ceros dentro del C respectivamente
La expresión se denomina derivada logarítmica de f.
Lo bello de esta ecuación es apreciar la vinculación entre una integral de contorno en el dominio complejo y un resultado en el dominio de los enteros. Muchos teoremas de la teoría de números involucra análisis real y complejo, en ese caso se esta trabajando en el ámbito de la Teoría Analítica de Números.
Desde el punto de vista práctico la integral de arriba es igual a un entero. Si la calculamos usando métodos numéricos con una computadora podríamos obtener resultados aproximados, pero que se acercan mucho a un resultado entero.
Podemos definir el contorno C de forma tal que contenga la banda critica hasta un determinado valor de Im(s) = N, lo que nos permitiría contar la cantidad de ceros no triviales de Z(s) hasta N. Se ha demostrado que no existen polos en la banda critica.
Contando ceros. Parte 2
Usando el principio de argumento , podemos contar el número de ceros en una región. Ahora, ¿Cómo verificamos que todos estén ubicados en la línea critica?. La respuesta está en la función Z(s).
De la ecuación (8) que relaciona con Z(s) podemos observar cuanto
es cero en la región critica también los es Z(s) y que no hay ceros fuera de esa region. en otras palabras ,
Los ceros de Z(s) son exactamente los ceros no triviales de
Este razonamiento es de suma importancia pues Z(s) tiene interesante propiedades que podemos usar para nuestro objetivo. Primero Z(s) conmuta con la conjugación , es decir:

Sobre la linea critica tenemos:

Pero como:



Lo que nos indica que el valor de Z en la banda critica para t real es también real. Esto es una gran ventaja, pues simplifica mucho los cálculos, es mas simple calcular ceros de una funciona real que de una compleja.
Como Z es una función continua podemos usar herramienta de calculo elemental para calcular sus ceros, una forma seria mediante métodos de aproximación usando el método de Newton Rapshon , pero como solo nos interesa calcular su cantidad no sus valores vamos a medir los cambios de signo en el intervalo analizado. Esta última opción es la utilizada en este articulo.
Si la funciona cambia de signo positivo a negativo y de negativo a positivo n veces en un intervalo podemos afirmar que existen n ceros en ese intervalo.
Este será el abordaje para verificar la Hipótesis de Riemann
- Integrar

A lo largo del control C que recorra a lo largo de la banda critica hasta un determina Im(s) = N. La integral nos devuelve los n ceros en el contorno incluyendo las multiplicidades.
2. Intentar encontrar los n cambios de signo de Z(1/2 + it) para t entre 0 y N. Si podemos encontrar n ceros en este intervalo, tendremos confirmado todos los ceros en la línea critica hasta Im(s) = N.
El segundo paso podría fallar si se encuentras ceros que no caen en la línea critica o que estén en la línea critica pero con multiplicidad mayor a uno. Esto se detecta si la integral devuelve mas ceros que los calculados por el conteo de cambio de signo.
Afortunadamente mediante proyectos de computación distribuida se ha logrado calcular más 1 billón de ceros y no se ha encontrado ninguno con multiplicidad mayor a 1, y no se espera que pase.
Verificación con SymPy y mpmath
Ahora usaremos las librerías de Python Sympy y mppath para calcular esas cantidades de ceros. Sympy usa mpmath, pero lamentablemente la función scipy.special
zeta no soporta argumentos complejos.
Primero debemos incluir imports básicos.
>>> from sympy import *
>>> import mpmath
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> s = symbols('s')
Definimos la zeta de Riemann completa
>>> Z = pi**(-s/2)*gamma(s/2)*zeta(s)
Verificamos si su valor es real para s=1/2+it.
>>> Z.subs(s, 1/2 + 0.5j).evalf()
-1.97702795164031 + 5.49690501450151e-17*I
Dado que la parte imaginaria es muy pequeña la podríamos considerar igual a cero. En la práctica valores menores a 10-15 se pueden ignorar en forma segura.
D
será la derivada logarítmica de Z.
>>> D = simplify(Z.diff(s)/Z)
>>> D
polygamma(0, s/2)/2 - log(pi)/2 + Derivative(zeta(s), s)/zeta(s)
Notar que la derivada logarítmica se comporta en forma similar al logaritmo, o sea la derivada logarítmica de un producto es igual a la suma de sus derivada logarítmicas. Cabe destacar que la función poligamma es la derivada de la función gamma Γ.
Ahora usamos lambdify
para convertir la expresión SymPy z y D en funciones que puedan ser evaluadas por mpmath. aquí hay una dificultad técnica dado que la derivada de la función zeta , o sea ζ′(s), no tiene una expresión cerrada. mpmath’s zeta
puede evaluar ζ′(s) pero aún no trabaja con sympy.lambdify
(ver SymPy issue 11802). Por lo tanto tenemos ue trabajar manualmente definiendo “Derivative” in lambdify , conociendo que cuando se llame será la derivada de ζ(s). Tener cuidado que esto solo es correcto para esta expresión especifica tal que Derivative será Derivative(zeta(s)).
>>> Z_func = lambdify(s, Z, 'mpmath')
>>> D_func = lambdify(s, D, modules=['mpmath',
... {'Derivative': lambda expr, z: mpmath.zeta(z, derivative=1)}])
Ahora definimos una función para usar el principio del argumento para contar los números de ceros hasta iN. Debido a la simetría Z(s)=Z(1−s), solo es necesario contar en el semiplano positivo.
Debemos tener cuidado con los polos de Z(s) en 0 y 1. Podemos integrar por encima de ellos, o extender el contorno para incluirlos. elegimos la primera opción , comenzando en 0.1i. Sabemos que la función ζ(s) no tiene ceros en el eje real en la línea critica.
Se ha demostrado que no existen ceros en la rectas Re(s) = 0 y Re(s) = 1, por lo tanto no tenemos que preocuparnos en contener esa dos rectas. Si justo existen ceros en la parte superior de contorno seria un caso no deseado, en ese caso solo debemos ajustar un poco los limites del contorno de integración.
(Contorno para N=10. creado con Geogebra)
La función mpmath.quad
puede tomar una lista de puntos para calcular la integral de contorno. El parámetro maxdegree
nos permite incrementar el grado de precisión del resultado en el caso de ser necesario.
>>> def argument_count(func, N, maxdegree=6):
... return 1/(2*mpmath.pi*1j)*(mpmath.quad(func,
... [1 + 0.1j, 1 + N*1j, 0 + N*1j, 0 + 0.1j, 1 + 0.1j],
... maxdegree=maxdegree))
Ahora podemos probarlo para poder contar los ceros de:

En el contorno formado por el rectángulo de arriba hasta N = 10.
>>> expr = s**2 - s + S(1)/2
>>> argument_count(lambdify(s, expr.diff(s)/expr), 10)
mpc(real='1.0', imag='3.4287545414000525e-24')
La integral da 1, lo que confirma que hay un solo cero en 1/2+i/2
>>> solve(s**2 - s + S(1)/2)
[1/2 - I/2, 1/2 + I/2]
Ya estamos en condiciones para calcular los puntos de Z a los largo de la línea critica mediante la cuenta de los cambios de signo. En este caso vamos a incrementara la precisión de los resultados para evitar falsos cambios de signo, mpmath utiliza el parámetro dps para determinar el números de dígitos , por defecto es 15, pero mpmath puede calcular cualquier número de dígitos. La función mpmath.chop
pone a cero los valores muy cercanos a cero, de esa manera elimina cualquier parte imaginaria que tenga un valor insignificante.
>>> def compute_points(Z_func, N, npoints=10000, dps=15):
... import warnings
... old_dps = mpmath.mp.dps
... points = np.linspace(0, N, npoints)
... try:
... mpmath.mp.dps = dps
... L = [mpmath.chop(Z_func(i)) for i in 1/2 + points*1j]
... finally:
... mpmath.mp.dps = old_dps
... if L[-1] == 0:
... # mpmath will give 0 if the precision is not high enough, since Z
... # decays rapidly on the critical line.
... warnings.warn("You may need to increase the precision")
... return L
Definimos unja funciona para contar el numero de cambios de signo de valores reales.
>>> def sign_changes(L):
... """
... Count the number of sign changes in L
...
... Values of L should all be real.
... """
... changes = 0
... assert im(L[0]) == 0, L[0]
... s = sign(L[0])
... for i in L[1:]:
... assert im(i) == 0, i
... s_ = sign(i)
... if s_ == 0:
... # Assume these got chopped to 0
... continue
... if s_ != s:
... changes += 1
... s = s_
... return changes
Por ejemplo, para sin(s) desde-10 a 10, hay 7 ceros (3π≈9.42).
>>> sign_changes(lambdify(s, sin(s))(np.linspace(-10, 10)))
7
Aahora podemos verificar cuantos ceros de Zs)(s) podemos encontrar. De acuerdo con Wikipedia, los primeros ceros no triviaesl de la función zeta en el semiplano superior son 14.35,21.022,25.011
Primero probemos hasta N = 20.
>>> argument_count(D_func, 20)
mpc(real='0.99999931531867581', imag='-3.2332902529067346e-24')
Matemáticamente el valor de arriba equivale a un entero de valor 1.
Calculamos el número de cambios de signo de Z(s) desde 1/2+01.i hasta 1/2+20i
>>> L = compute_points(Z_func, 20)
>>> sign_changes(L)
1
Y efectivamente es la cantidad esperada.
Verifcamos para otros ceros.
>>> argument_count(D_func, 25)
mpc(real='1.9961479945577916', imag='-3.2332902529067346e-24')
>>> L = compute_points(Z_func, 25)
>>> sign_changes(L)
2
>>> argument_count(D_func, 30)
mpc(real='2.9997317058520916', imag='-3.2332902529067346e-24')
>>> L = compute_points(Z_func, 30)
>>> sign_changes(L)
3
Estamos verificando lo que queremos.
Podemos seguir calculando la cantidad de puntos y también graficarlos. Tenemos otra dificultad técnica, si queremos representar gráficamente Z(1/2+it), veremos que decae rápidamente, y no se puede ver cuando cruza el cero.
>>> def plot_points_bad(L, N):
... npoints = len(L)
... points = np.linspace(0, N, npoints)
... plt.figure()
... plt.plot(points, L)
... plt.plot(points, [0]*npoints, linestyle=':')
>>> plot_points_bad(L, 30)
En vez del grafico anterior podríamos representar log(|Z(1/2+it)|), en vez de ceros veremos −∞, pero es mas fácil de analizar.
>>> def plot_points(L, N):
... npoints = len(L)
... points = np.linspace(0, N, npoints)
... p = [mpmath.log(abs(i)) for i in L]
... plt.figure()
... plt.plot(points, p)
... plt.plot(points, [0]*npoints, linestyle=':')
>>> plot_points(L, 30)
Los picos hacia abajo son los ceros.
Teniendo en cuenta la secuencia OEIS A072080 que muestra los ceros no triviales de ζ(s) en el semiplano superior hasta Im(s) = i10n . De esa secuencia podemos saber que existen 29 ceros en ese intervalo.
>>> argument_count(D_func, 100)
mpc(real='28.248036536895913', imag='-3.2332902529067346e-24')
Este valor no esta cerca de un entero, por lo tanto necesitamos incrementar la precisión del calculo de la integral de contorno.
>>> argument_count(D_func, 100, maxdegree=9)
mpc(real='29.000000005970151', imag='-3.2332902529067346e-24')
A pesar de eso cuando calculamos la cantida de cambio de signo tenemos:
>>> L = compute_points(Z_func, 100)
__main__:11: UserWarning: You may need to increase the precision
Lo que nos dice que la presicion elegida es demasiado baja, Subamos dsp a 50.
>>> L = compute_points(Z_func, 100, dps=50)
>>> sign_changes(L)
29
Perfecto!!, acabamos de verificar la Hipótesis de Riemann hasta 100i
Grafiquemos esos ceros.
>>> plot_points(L, 100)
Conclusiones:
N = 100 es un valor chico, pero a pesar de eso se tarda algunos minutos en calcular, para valores mas grandes de N se necesita mas precisión , mas capacidad de computo e implementaciones mas eficientes, pero el método sigue siendo el mismo.
En este articulo no se calcula el valor de los ceros, sino que se toma valores publoicados, el caclulo de los valores implica la aplicacion de algoritmo de aproximacion de raices que vamos a ver en otro post.
Tambien es importan analizar porque estan importante la Hiopotesis de Riemann,. Basicamente la demostracion de esta hipostesis implicaria conocer a la exactitud la distribucion de los numeros primos ene l cnjunto de los enteros. Un problema que data desde la epoca de los Euclides hace mas de 2000 años.
Fuentes:
El actual es una adaptación al español del articulo:
https://www.asmeurer.com/blog/posts/verifying-the-riemann-hypothesis-with-sympy-and-mpmath/
Libros y papers:
- Sobre el número de primos menores que una cantidad dada https://personal.us.es/arias/TAN2002-3/08-Riemann.pdf (Excelente documento en español que profundiza el documento original de Riemann de 1859.)
- Breviario de la Teoría Analítica de Números, Eugenio P. Balanziario
- An Introduction to the Theory of Number, G. H. Hardy and E. M. Wright. 1928.
- Introducción a la Teoría Analítica de Números , T.M Apostol.
- Fundamentos de la teoría de números , Vinogradov Ed Mir 1977
- Introducción a la Teoría de Números , Ejemplos y Algoritmos, Walter Mora.
- The Riemann Zeta Function, A.A Karatsuba, S.M Vonorin
- Variable Compleja y Aplicaciones, Churchill
Videos:
Clase magistral sobre la Hipótesis de Riemann en español . https://www.youtube.com/watch?v=Y1WpLrNTRyg