Skip to content

Implementing the Riemann-Siegel Formula to find zeros of the Riemann Zeta Function with Roots.jl #186

@matthewshawnkehoe

Description

@matthewshawnkehoe

I am interested in implementing a version of the Riemann-Siegel formula for calculating non-trivial zeros of the Riemann zeta function in Julia. I can "successfully" get "manually finding roots by stepping through an interval and looking for sign changes" to work. I can also "successfully" get find_zeros from the Roots.jl package to work (although, this is a "bad" approach for solving this problem and appears to auto-default to the Bisection method). This method might work better for this problem if one had access to other root-finding methods such as Brent's method.

However, I cannot seem to get roots.jl (from IntervalRootFinding) to work.The issue appears to be the way I pass through rtsArray = roots(f, a, b) on line 40 of the code below. Through looking at the example here, one can write

f(x) = sin(x) - 0.1*x^2 + 1

and then

roots(f, -10..10)

which returns

4-element Array{Root{Interval{Float64}},1}:
 Root([3.14959, 3.1496], :unique)
 Root([-4.42654, -4.42653], :unique)
 Root([-1.08205, -1.08204], :unique)
 Root([-3.10682, -3.10681], :unique)

I don't understand why I cannot do the same thing with RiemennZ (as is done in findRootsSlow and findRoots in my code below). The second version of my code is pasted below.

#=**
**
**    Riemann-Siegel Formula for roots of Zeta(s) on the critical line.
**
**************************************************************************
**    Axion004
**    10/29/2022
**
**    This program finds roots of Zeta(s) using the Riemann-Siegel
**    formula. The Riemann–Siegel theta function is approximated by
**    Stirling's approximation. It also uses an interpolation method to
**    locate zeros. The coefficients for R(t) are handled by the Taylor
**    Series approximation originally listed by Haselgrove in the 1960s.
**    It is necessary to approximate these coefficients in order to
**    increase computational speed.

Future Work

1. Implement a cleaner version of findRoots(). Investigate interval methods.
2. Use GPU and parallel processing capability. A lot of good ideas are discussed in
https://www.youtube.com/watch?v=8E0qdO_jRZ0.
3. Calculate all of the Riemann-Siegel coefficients explicitly.
4. Implement the Odlyzko–Schönhage algorithm.
**************************************************************************=#

using Roots
using IntervalArithmetic, IntervalRootFinding

"""
* Search for zeros of the Riemann-Siegel Z function in the interval [a,b] by
    the roots function in  IntervalRootFinding.jl. See
    https://github.com/JuliaIntervals/IntervalRootFinding.jl and
    https://juliaintervals.github.io/pages/packages/.
* @param a - the starting value of the interval.
* @param b - the ending value of the interval.
* @return an array of zeros of zeta(s) on the critical line.
"""
function findRootsIntervalArithmetic(a::Float64, b::Float64)
    f(x) = RiemennZ(x, 4);
    rtsArray = roots(f, a, b) # I don't know why this doesn't work.
    println(size(rtsArray))
    #for root in rtsArray
     #   println(root)
    #end
end

"""
* Search for zeros of the Riemann-Siegel Z function in the interval [a,b] by
    the find_zeros function in Roots.jl. See
    https://juliamath.github.io/Roots.jl/dev/roots/#Searching-for-all-zeros-in-an-interval.
* @param a - the starting value of the interval.
* @param b - the ending value of the interval.
* @return an array of zeros of zeta(s) on the critical line.
"""
function findRoots(a::Float64, b::Float64)
    f(x) = RiemennZ(x, 4);
    rtsArray = find_zeros(f, a, b) # This isn't accurate when b > 1000.
    # rtsArray = find_zeros(f, a, b, Roots.Brent()) # I don't know why this doesn't work.
    println(size(rtsArray))
    #for root in rtsArray
        #println(root)
    #end
end

"""
    * Extremely inefficient way of calculating roots manually.
"""
function findRootsSlow(a::Float64, b::Float64)
    f(x) = RiemennZ(x, 4);
    step_size = 0.01
    while a < b
        a += step_size
        # This is a very inefficient way of looking for roots
        if sign(f(a)) != sign(f(a + step_size))
            x = fzero(f, [a, a + step_size])
            # Print the roots to the console
            println(x)
            #io = open("roots.txt","a")
            #    println(io,x)
            #close(io)
        end
    end
end

"""
    * Riemann-Siegel theta function using the approximation by the
      Stirling series.
    * @param t - the value of t inside the Z(t) function.
    * @return Stirling's approximation for theta(t).
"""
function theta(t::Float64)
    return (t/2.0 * log(t/(2.0*pi)) - t/2.0 - pi/8.0
            + 1.0/(48.0*t) + 7.0/(5760*t^3))
end

"""
    * Computes the floor of the absolute value term passed in as t.
    * @param t - the value of t inside the Z(t) function.
    * @return floor of the absolute value of t.
"""
function fAbs(t::Float64)
    return floor(abs(t))
end


"""
    * Riemann-Siegel Z(t) function implemented per the Riemenn Siegel
         formula. See http://mathworld.wolfram.com/Riemann-SiegelFormula.html
         for details.
    * @param t - the value of t inside the Z(t) function.
         @param r - referenced for calculating the remainder terms by the
         Taylor series approximations.
    * @return the approximate value of Z(t) through the Riemann-Siegel
         formula
"""
function RiemennZ(t::Float64, r::Int)
    twopi = pi * 2.0;
    val = sqrt(t/twopi)
    n = fAbs(val)
    sum = 0.0

    for i in 1:n
          sum += (cos(theta(t) - t * log(i))) / sqrt(i)
    end
    sum = 2.0 * sum

    frac = val - n
    k = 0
    R = 0.0

    # Calculate every remainder term through the Taylor Series coefficients.
    # The coefficients are defined in the Riemann-Siegel C function.
    while k <= r
        R = R + C(k, 2.0*frac-1.0) * (t / twopi)^(k * (-0.5))
        k += 1
    end

    remainder = (-1)^(n-1) * (t / twopi)^(-0.25) * R
    return sum + remainder
end


"""
    * C terms for the Riemann-Siegel formula. See
         Haselgrove (1960): Tables of the Riemann Zeta Function,
         doi:10.2307/3614148  for details.
         Calculates the Taylor Series coefficients for C0, C1, C2, C3,
         and C4.
    * @param n - the number of coefficient terms to use.
    * @param z - referenced per the Taylor series calculations.
    * @return the Taylor series approximation of the remainder terms.
"""
function C(n::Int, z::Float64)
    if n==0
        return (.38268343236508977173 * z^0.0
            +.43724046807752044936 * z^2.0
            +.13237657548034352332 * z^4.0
            -.01360502604767418865 * z^6.0
            -.01356762197010358089 * z^8.0
            -.00162372532314446528 * z^10.0
            +.00029705353733379691 * z^12.0
            +.00007943300879521470 * z^14.0
            +.00000046556124614505 * z^16.0
            -.00000143272516309551 * z^18.0
            -.00000010354847112313 * z^20.0
            +.00000001235792708386 * z^22.0
            +.00000000178810838580 * z^24.0
            -.00000000003391414390 * z^26.0
            -.00000000001632663390 * z^28.0
            -.00000000000037851093 * z^30.0
            +.00000000000009327423 * z^32.0
            +.00000000000000522184 * z^34.0
            -.00000000000000033507 * z^36.0
            -.00000000000000003412 * z^38.0
            +.00000000000000000058 * z^40.0
            +.00000000000000000015 * z^42.0)
    elseif n==1
        return (-.02682510262837534703 * z^1.0
            +.01378477342635185305 * z^3.0
            +.03849125048223508223 * z^5.0
            +.00987106629906207647 * z^7.0
            -.00331075976085840433 * z^9.0
            -.00146478085779541508 * z^11.0
            -.00001320794062487696 * z^13.0
            +.00005922748701847141 * z^15.0
            +.00000598024258537345 * z^17.0
            -.00000096413224561698 * z^19.0
            -.00000018334733722714 * z^21.0
            +.00000000446708756272 * z^23.0
            +.00000000270963508218 * z^25.0
            +.00000000007785288654 * z^27.0
            -.00000000002343762601 * z^29.0
            -.00000000000158301728 * z^31.0
            +.00000000000012119942 * z^33.0
            +.00000000000001458378 * z^35.0
            -.00000000000000028786 * z^37.0
            -.00000000000000008663 * z^39.0
            -.00000000000000000084 * z^41.0
            +.00000000000000000036 * z^43.0
            +.00000000000000000001 * z^45.0)
    elseif n==2
        return(+.00518854283029316849 * z^0.0
            +.00030946583880634746 * z^2.0
            -.01133594107822937338 * z^4.0
            +.00223304574195814477 * z^6.0
            +.00519663740886233021 * z^8.0
            +.00034399144076208337 * z^10.0
            -.00059106484274705828 * z^12.0
            -.00010229972547935857 * z^14.0
            +.00002088839221699276 * z^16.0
            +.00000592766549309654 * z^18.0
            -.00000016423838362436 * z^20.0
            -.00000015161199700941 * z^22.0
            -.00000000590780369821 * z^24.0
            +.00000000209115148595 * z^26.0
            +.00000000017815649583 * z^28.0
            -.00000000001616407246 * z^30.0
            -.00000000000238069625 * z^32.0
            +.00000000000005398265 * z^34.0
            +.00000000000001975014 * z^36.0
            +.00000000000000023333 * z^38.0
            -.00000000000000011188 * z^40.0
            -.00000000000000000416 * z^42.0
            +.00000000000000000044 * z^44.0
            +.00000000000000000003 * z^46.0)
    elseif n==3
        return (-.00133971609071945690 * z^1.0
            +.00374421513637939370 * z^3.0
            -.00133031789193214681 * z^5.0
            -.00226546607654717871 * z^7.0
            +.00095484999985067304 * z^9.0
            +.00060100384589636039 * z^11.0
            -.00010128858286776622 * z^13.0
            -.00006865733449299826 * z^15.0
            +.00000059853667915386 * z^17.0
            +.00000333165985123995 * z^19.0
            +.00000021919289102435 * z^21.0
            -.00000007890884245681 * z^23.0
            -.00000000941468508130 * z^25.0
            +.00000000095701162109 * z^27.0
            +.00000000018763137453 * z^29.0
            -.00000000000443783768 * z^31.0
            -.00000000000224267385 * z^33.0
            -.00000000000003627687 * z^35.0
            +.00000000000001763981 * z^37.0
            +.00000000000000079608 * z^39.0
            -.00000000000000009420 * z^41.0
            -.00000000000000000713 * z^43.0
            +.00000000000000000033 * z^45.0
            +.00000000000000000004 * z^47.0)
    else
        return (+.00046483389361763382 * z^0.0
            -.00100566073653404708 * z^2.0
            +.00024044856573725793 * z^4.0
            +.00102830861497023219 * z^6.0
            -.00076578610717556442 * z^8.0
            -.00020365286803084818 * z^10.0
            +.00023212290491068728 * z^12.0
            +.00003260214424386520 * z^14.0
            -.00002557906251794953 * z^16.0
            -.00000410746443891574 * z^18.0
            +.00000117811136403713 * z^20.0
            +.00000024456561422485 * z^22.0
            -.00000002391582476734 * z^24.0
            -.00000000750521420704 * z^26.0
            +.00000000013312279416 * z^28.0
            +.00000000013440626754 * z^30.0
            +.00000000000351377004 * z^32.0
            -.00000000000151915445 * z^34.0
            -.00000000000008915418 * z^36.0
            +.00000000000001119589 * z^38.0
            +.00000000000000105160 * z^40.0
            -.00000000000000005179 * z^42.0
            -.00000000000000000807 * z^44.0
            +.00000000000000000011 * z^46.0
            +.00000000000000000004 * z^48.0)
    end
end


# Main
function main()
    println("Zeroes on the critical line Zeta(1/2 + it).")
    a = 1.0
    b = 100.0
    #findRootsSlow(a, b)
    findRoots(a, b)
    #findRootsIntervalArithmetic(a, b)
end

main()

Uncommenting findRootsIntervalArithmetic(a, b)on line 288 returns

ERROR: MethodError: no method matching roots(::var"#f#72", ::Float64, ::Float64)
Closest candidates are:
  roots(::Function, ::Function, ::Any) at ~/.julia/packages/IntervalRootFinding/OdQlQ/src/roots.jl:126
  roots(::Function, ::Function, ::Any, ::Type{C}) where C<:Contractor at ~/.julia/packages/IntervalRootFinding/OdQlQ/src/roots.jl:126
  roots(::Function, ::Function, ::Any, ::Type{C}, ::Float64) where C<:Contractor at ~/.julia/packages/IntervalRootFinding/OdQlQ/src/roots.jl:139
  ...
Stacktrace:
 [1] findRootsIntervalArithmetic(a::Float64, b::Float64)
   @ Main ~/RiemannSiegel/RiemannSiegel.jl:40
 [2] main()
   @ Main ~/RiemannSiegel/RiemannSiegel.jl:288
 [3] top-level scope
   @ ~/RiemannSiegel/RiemannSiegel.jl:291

So, I am likely doing something wrong on line 40 when I write rtsArray = roots(f, a, b). I think that I have incorrectly called roots and this is expected to error out.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions