Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: is multipliciation of polynomials implemented with FFT #519

Open
mathprofessor opened this issue Aug 11, 2023 · 6 comments
Open

Comments

@mathprofessor
Copy link

How is multiplication of Polynomials implemented? With the basic O(n^2) algorithm or the Fast Fourier Transform O(n log n) algorithm?

@jverzani
Copy link
Member

@jverzani
Copy link
Member

Sorry, I misread the question. Multiplication is currently done with the basic algorithm. It would be nice to have it done more efficiently.

@mathprofessor
Copy link
Author

mathprofessor commented Aug 15, 2023 via email

@jverzani
Copy link
Member

I had hopes this would be reasonably quick, but it doesn't seem to be. Maybe you can play around?

using Polynomials, Test

recursive_fft(as, ys=zeros(Complex{Float64}, length(as)), s=1) = recursive_fft!(ys,as,s)

function recursive_fft!(ys, as, s=1)

    n = length(as)
    if isone(n)
        ys[1] = as[1]
        return ys
    end

    o = 1
    eidx = o .+ range(0, n-2, step=2)
    oidx = o .+ range(1, n-1, step=2)

    n2 = n ÷ 2

    ye = recursive_fft!(view(ys, 1:n2), view(as, eidx), s)
    yo = recursive_fft!(view(ys, (n2+1):n), view(as, oidx), s)

    ωₙ = exp(sign(s) * im * 2pi/n)
    ω = one(ωₙ)

    @inbounds for k ∈ o .+ range(0, n2 - 1)
        yₑ, yₒ = ye[k], yo[k]
        ys[k     ] = yₑ + ω * yₒ
        ys[k + n2] = yₑ - ω * yₒ
        ω = ω * ωₙ
    end
    return ys
end
inverse_fft(as, ys=zeros(Complex{Float64}, length(as))) = recursive_fft!(ys,as,-1) / length(as)


function poly_mul(as::AbstractVector{T}, bs::AbstractVector{S}) where {T,S}
    n = maximum(length, (as, bs))
    N = 2^ceil(Int, log2(n))

    as′ = zeros(promote_type(T,S), 2N)
    copy!(view(as′, 1:length(as)), as)
    âs = recursive_fft(as′)

    as′ .= 0
    copy!(view(as′, 1:length(bs)), bs)
    b̂s = recursive_fft(as′)

    âb̂s = âs .* b̂s

    inverse_fft(âb̂s)
end

P = Polynomial
@testset for n ∈ (4,8,16)
    as = rand(n)
    @test norm(P(poly_mul(as, as)) - P(as)*P(as)) <= 1e-10
end

@mathprofessor
Copy link
Author

mathprofessor commented Aug 16, 2023 via email

@jverzani
Copy link
Member

I like how Cyclotomics is exact, but I can't seem to get the tricks from MutableArithmetics to work to eliminate all the allocations of using big numbers. Here is basically your function, with a slightly different flavor:

function recursive_fft(as::Vector{T}, inverse=false) where {T}
    n = length(as)
    N = 2^ceil(Int, log2(n))
    @assert n == N
    R = typeof(as[1]*E(N))
    ys = Vector{R}(undef, N)
    recursive_fft!(ys, as, inverse)
    ys
end

inverse_fft(as) = recursive_fft(as, true) // length(as)

function recursive_fft!(ys, as, inverse=false)

    n = length(as)
    isone(n) && (ys[1] = as[1]; return ys)

    o = 1
    eidx = o .+ range(0, n-2, step=2)
    oidx = o .+ range(1, n-1, step=2)

    n2 = n ÷ 2

    ye = recursive_fft!(view(ys, 1:n2), view(as, eidx), inverse)
    yo = recursive_fft!(view(ys, (n2+1):n), view(as, oidx), inverse)

    ωₙ = E(n)
    inverse && (ωₙ = ωₙ^(n-1)) # inv(ωₙ) changes type)
    ω = one(ωₙ)

    @inbounds for k ∈ o .+ range(0, n2 - 1)
        yₑ, yₒ = ye[k], yo[k]
        ys[k     ] = yₑ + ω * yₒ
        ys[k + n2] = yₑ - ω * yₒ
        ω *= ωₙ
    end
    return ys
end

We can see it is working as expected and the time:

julia> as = collect(big.(1:2^11));

julia> @time b = inverse_fft(recursive_fft(a));
 29.592997 seconds (188.26 M allocations: 4.266 GiB, 45.06% gc time)

julia> maximum(abs, b-as)
0.0

I'd bet there is a good way to do this, but it seems the Cyclotomic exactness means each omega is really a sparse vector with N terms and that makes it a bit of work to get working with the BigInt tricks in MutableArithmetics.

I thought maybe trying with ArbNumerics might help, but no luck.

jverzani added a commit to jverzani/Polynomials.jl that referenced this issue Aug 23, 2023
@jverzani jverzani mentioned this issue Aug 25, 2023
jverzani added a commit that referenced this issue Aug 25, 2023
* better job with 519, but still not what is wanted...

* more comments on #519

* did extensions get copied incorrectly?

* version bump

* extension testing

* add extensions to test/Project.toml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants