diff --git a/Project.toml b/Project.toml index 11bc8361..2959561e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,7 @@ name = "FastTransforms" uuid = "057dd010-8810-581a-b7be-e3fc3b93f78c" -version = "0.16.8" +version = "0.17" + [deps] AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c" @@ -15,7 +16,6 @@ LazyArrays = "5078a376-72f3-5289-bfd5-ec5146d43c02" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" RecurrenceRelationships = "807425ed-42ea-44d6-a357-6771516d7b2c" -Reexport = "189a3867-3050-52da-a836-e630ba90ab69" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" ToeplitzMatrices = "c751599d-da0a-543b-9d20-d0a503d91d24" @@ -29,8 +29,7 @@ FastTransforms_jll = "0.6.2" FillArrays = "0.9, 0.10, 0.11, 0.12, 0.13, 1" GenericFFT = "0.1" LazyArrays = "2.2" -RecurrenceRelationships = "0.1" -Reexport = "0.2, 1.0" +RecurrenceRelationships = "0.2" SpecialFunctions = "0.10, 1, 2" ToeplitzMatrices = "0.7.1, 0.8" julia = "1.7" diff --git a/docs/Project.toml b/docs/Project.toml index ed412282..169ffdb7 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,4 @@ [deps] -ApproxFun = "28f2ccd6-bb30-5033-b560-165f7b14dc2f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" FastTransforms = "057dd010-8810-581a-b7be-e3fc3b93f78c" LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" diff --git a/docs/make.jl b/docs/make.jl index b6a44b03..dd530e8f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -13,7 +13,6 @@ examples = [ "halfrange.jl", "nonlocaldiffusion.jl", "padua.jl", - "semiclassical.jl", "sphere.jl", "spinweighted.jl", "subspaceangles.jl", @@ -48,7 +47,6 @@ makedocs( "generated/halfrange.md", "generated/nonlocaldiffusion.md", "generated/padua.md", - "generated/semiclassical.md", "generated/sphere.md", "generated/spinweighted.md", "generated/subspaceangles.md", diff --git a/examples/semiclassical.jl b/examples/semiclassical.jl deleted file mode 100644 index 9acecaca..00000000 --- a/examples/semiclassical.jl +++ /dev/null @@ -1,113 +0,0 @@ -# # Semi-classical Jacobi polynomials -# In this example, we will consider the semi-classical orthogonal polynomials with respect to the inner product: -# ```math -# \langle f, g \rangle = \int_{-1}^1 f(x) g(x) w(x){\rm d} x, -# ``` -# where $w(x) = w^{(\alpha,\beta,\gamma,\delta,\epsilon)}(x) = (1-x)^\alpha(1+x)^\beta(2+x)^\gamma(3+x)^\delta(5-x)^\epsilon$ is a modification of the Jacobi weight. -# We shall use results from [this paper](https://arxiv.org/abs/2302.08448) to consider these semi-classical orthogonal polynomials as modifications of the orthonormalized Jacobi polynomials $\tilde{P}_n^{(\alpha,\beta)}(x)$. - -using ApproxFun, FastTransforms, LazyArrays, LinearAlgebra, Plots, LaTeXStrings -const GENFIGS = joinpath(pkgdir(FastTransforms), "docs/src/generated") -!isdir(GENFIGS) && mkdir(GENFIGS) -plotlyjs() - -# We set the five parameters: -α,β,γ,δ,ϵ = -0.125, -0.25, 0.123, 0.456, 0.789 - -# We use `ApproxFun` to construct a finite normalized Jacobi series as a proxy for $(2+x)^\gamma(3+x)^\delta(5-x)^\epsilon$. -u = Fun(x->(2+x)^γ*(3+x)^δ*(5-x)^ϵ, NormalizedJacobi(β, α)) - -# Our working polynomial degree will be: -n = 100 - -# We compute the connection coefficients between the modified orthogonal polynomials and the Jacobi polynomials: -P = plan_modifiedjac2jac(Float64, n+1, α, β, u.coefficients) - -# We store the connection to first kind Chebyshev polynomials: -P1 = plan_jac2cheb(Float64, n+1, α, β; normjac = true) - -# We compute the Chebyshev series for the degree-$k\le n$ modified polynomial and its values at the Chebyshev points: -q = k -> lmul!(P1, lmul!(P, [zeros(k); 1.0; zeros(n-k)])) -qvals = k -> ichebyshevtransform(q(k)) - -# With the symmetric Jacobi matrix for $\tilde{P}_n^{(\alpha, \beta)}(x)$ and the modified plan, we may compute the modified Jacobi matrix and the corresponding roots (as eigenvalues): -x = Fun(x->x, NormalizedJacobi(β, α)) -XP = SymTridiagonal(Symmetric(Multiplication(x, space(x))[1:n+1, 1:n+1])) -XQ = FastTransforms.modified_jacobi_matrix(P, XP) -view(XQ, 1:7, 1:7) - -# And we plot: -x = chebyshevpoints(Float64, n+1, Val(1)) -p = plot(x, qvals(0); linewidth=2.0, legend = false, xlim=(-1,1), xlabel=L"x", - ylabel=L"Q_n(x)", title="Semi-classical Jacobi Polynomials and Their Roots", - extra_plot_kwargs = KW(:include_mathjax => "cdn")) -for k in 1:10 - λ = eigvals(SymTridiagonal(XQ.dv[1:k], XQ.ev[1:k-1])) - plot!(x, qvals(k); linewidth=2.0, color=palette(:default)[k+1]) - scatter!(λ, zero(λ); markersize=2.5, color=palette(:default)[k+1]) -end -p -savefig(joinpath(GENFIGS, "semiclassical.html")) -###```@raw html -### -###``` - -# By [Theorem 2.20](https://arxiv.org/abs/2302.08448) it turns out that the *derivatives* of these particular semi-classical Jacobi polynomials are a linear combination of at most four polynomials orthogonal with respect to the weight $w^{(\alpha+1,\beta+1,\gamma+1,\delta+1,\epsilon+1)}(x)$ on $(-1,1)$. This fact enables us to compute the banded differentiation matrix: -v = Fun(x->(2+x)^(γ+1)*(3+x)^(δ+1)*(5-x)^(ϵ+1), NormalizedJacobi(β+1, α+1)) -function threshold!(A::AbstractArray, ϵ) - for i in eachindex(A) - if abs(A[i]) < ϵ A[i] = 0 end - end - A -end -P′ = plan_modifiedjac2jac(Float64, n+1, α+1, β+1, v.coefficients) -DP = UpperTriangular(diagm(1=>[sqrt(n*(n+α+β+1)) for n in 1:n])) # The classical differentiation matrix representing 𝒟 P^{(α,β)}(x) = P^{(α+1,β+1)}(x) D_P. -DQ = UpperTriangular(threshold!(P′\(DP*(P*I)), 100eps())) # The semi-classical differentiation matrix representing 𝒟 Q(x) = Q̂(x) D_Q. -UpperTriangular(DQ[1:9, 1:9]) - -# A faster method now exists via the `GramMatrix` architecture and its associated displacement equation. Given the modified orthogonal polynomial moments implied by the normalized Jacobi series for $u(x)$, we pad this vector to the necessary size and construct the `GramMatrix` with these moments, the multiplication operator, and the constant $\tilde{P}_0^{(\alpha,\beta)}(x)$: -μ = PaddedVector(u.coefficients, 2n+1) -x = Fun(x->x, NormalizedJacobi(β, α)) -XP2 = SymTridiagonal(Symmetric(Multiplication(x, space(x))[1:2n+1, 1:2n+1])) -p0 = Fun(NormalizedJacobi(β, α), [1])(0) -G = GramMatrix(μ, XP2, p0) -view(G, 1:7, 1:7) - -# And compute its cholesky factorization. The upper-triangular Cholesky factor represents the connection between original Jacobi and semi-classical Jacobi as ${\bf P}^{(\alpha,\beta)}(x) = {\bf Q}(x) R$. -R = cholesky(G).U -UpperTriangular(view(R, 1:7, 1:7)) - -# Every else works almost as before, including evaluation on a Chebyshev grid: -q = k -> lmul!(P1, ldiv!(R, [zeros(k); 1.0; zeros(n-k)])) -qvals = k -> ichebyshevtransform(q(k)) - -# Computation of the modified Jacobi matrix: -XQ1 = FastTransforms.modified_jacobi_matrix(R, XP) -norm(XQ-XQ1)/norm(XQ) - -# Plotting: -x = chebyshevpoints(Float64, n+1, Val(1)) -p = plot(x, qvals(0); linewidth=2.0, legend = false, xlim=(-1,1), xlabel=L"x", - ylabel=L"Q_n(x)", title="Semi-classical Jacobi Polynomials and Their Roots", - extra_plot_kwargs = KW(:include_mathjax => "cdn")) -for k in 1:10 - λ = eigvals(SymTridiagonal(XQ1.dv[1:k], XQ1.ev[1:k-1])) - plot!(x, qvals(k); linewidth=2.0, color=palette(:default)[k+1]) - scatter!(λ, zero(λ); markersize=2.5, color=palette(:default)[k+1]) -end -p -savefig(joinpath(GENFIGS, "semiclassical1.html")) -###```@raw html -### -###``` - -# And banded differentiation: -μ′ = PaddedVector(v.coefficients, 2n+1) -x′ = Fun(x->x, NormalizedJacobi(β+1, α+1)) -XP′ = SymTridiagonal(Symmetric(Multiplication(x′, space(x′))[1:2n+1, 1:2n+1])) -p0′ = Fun(NormalizedJacobi(β+1, α+1), [1])(0) -G′ = GramMatrix(μ′, XP′, p0′) -R′ = cholesky(G′).U -DP = UpperTriangular(diagm(1=>[sqrt(n*(n+α+β+1)) for n in 1:n])) # The classical differentiation matrix representing 𝒟 P^{(α,β)}(x) = P^{(α+1,β+1)}(x) D_P. -DQ = UpperTriangular(threshold!(R′*(DP*(R\I)), 100eps())) # The semi-classical differentiation matrix representing 𝒟 Q(x) = Q̂(x) D_Q. -UpperTriangular(DQ[1:9, 1:9]) diff --git a/examples/sphere.jl b/examples/sphere.jl index 916975b5..a28a36a2 100644 --- a/examples/sphere.jl +++ b/examples/sphere.jl @@ -61,7 +61,7 @@ C = [k/(k+1) for k in 0:N] c = zeros(N); c[N] = 1 pts = vec([z(θ, φ)⋅y for θ in θ, φ in φ]) phi0 = ones(N*M) -F = reshape(FastTransforms.clenshaw!(c, A, B, C, pts, phi0, zeros(N*M)), N, M) +F = reshape(FastTransforms.clenshaw!(zeros(N*M), c, A, B, C, pts, phi0), N, M) # We superpose a surface plot of $f$ on top of the grid: X = [sinpi(θ)*cospi(φ) for θ in θ, φ in φ] @@ -91,7 +91,7 @@ U = threshold!(P\V, 400*eps()) nrm1 = norm(U) # Similarly, on the tensor product grid, our function samples are: -Pnxy = FastTransforms.clenshaw!(c, A, B, C, [x⋅y], [1.0], [0.0])[1] +Pnxy = FastTransforms.clenshaw!([0.0], c, A, B, C, [x⋅y], [1.0])[1] F = [(F[n, m] - Pnxy)/(z(θ[n], φ[m])⋅y - x⋅y) for n in 1:N, m in 1:M] # We superpose a surface plot of $f$ on top of the grid: @@ -108,7 +108,7 @@ U = threshold!(P\V, 400*eps()) # Finally, the Legendre polynomial $P_n(z\cdot x)$ is aligned with the grid: pts = vec([z(θ, φ)⋅x for θ in θ, φ in φ]) -F = reshape(FastTransforms.clenshaw!(c, A, B, C, pts, phi0, zeros(N*M)), N, M) +F = reshape(FastTransforms.clenshaw!(zeros(N*M), c, A, B, C, pts, phi0), N, M) # We superpose a surface plot of $f$ on top of the grid: scatter3d(vec(X), vec(Y), vec(Z); markersize=1.25, markercolor=:violetred) diff --git a/src/FastTransforms.jl b/src/FastTransforms.jl index 8a3d5a3c..4eef13ad 100644 --- a/src/FastTransforms.jl +++ b/src/FastTransforms.jl @@ -1,11 +1,11 @@ module FastTransforms using ArrayLayouts, BandedMatrices, FastGaussQuadrature, FillArrays, LazyArrays, LinearAlgebra, - Reexport, SpecialFunctions, ToeplitzMatrices, RecurrenceRelationships + SpecialFunctions, ToeplitzMatrices, RecurrenceRelationships -@reexport using AbstractFFTs -@reexport using FFTW -@reexport using GenericFFT +using AbstractFFTs +using FFTW +using GenericFFT import Base: convert, unsafe_convert, eltype, ndims, adjoint, transpose, show, *, \, inv, length, size, view, getindex, tail, OneTo @@ -34,11 +34,8 @@ import LinearAlgebra: cholesky, issymmetric, isposdef, mul!, lmul!, ldiv! import GenericFFT: interlace # imported in downstream packages -import RecurrenceRelationships: clenshaw!, check_clenshaw_recurrences +import RecurrenceRelationships: check_clenshaw_recurrences -const _forwardrecurrence! = RecurrenceRelationships.forwardrecurrence! -const _clenshaw_next = RecurrenceRelationships.clenshaw_next -const _forwardrecurrence_next = RecurrenceRelationships.forwardrecurrence_next export leg2cheb, cheb2leg, ultra2ultra, jac2jac, lag2lag, jac2ultra, ultra2jac, jac2cheb, diff --git a/src/libfasttransforms.jl b/src/libfasttransforms.jl index d89f0490..3ce492d9 100644 --- a/src/libfasttransforms.jl +++ b/src/libfasttransforms.jl @@ -49,13 +49,13 @@ function renew!(x::AbstractArray{BigFloat}) return x end -function horner!(c::StridedVector{Float64}, x::Vector{Float64}, f::Vector{Float64}) +function horner!(f::Vector{Float64}, c::StridedVector{Float64}, x::Vector{Float64}) @assert length(x) == length(f) ccall((:ft_horner, libfasttransforms), Cvoid, (Cint, Ptr{Float64}, Cint, Cint, Ptr{Float64}, Ptr{Float64}), length(c), c, stride(c, 1), length(x), x, f) f end -function horner!(c::StridedVector{Float32}, x::Vector{Float32}, f::Vector{Float32}) +function horner!(f::Vector{Float32}, c::StridedVector{Float32}, x::Vector{Float32}) @assert length(x) == length(f) ccall((:ft_hornerf, libfasttransforms), Cvoid, (Cint, Ptr{Float32}, Cint, Cint, Ptr{Float32}, Ptr{Float32}), length(c), c, stride(c, 1), length(x), x, f) f @@ -69,19 +69,19 @@ function check_clenshaw_points(x, f) length(x) == length(f) || throw(ArgumentError("Dimensions must match")) end -function clenshaw!(c::StridedVector{Float64}, x::Vector{Float64}, f::Vector{Float64}) +function clenshaw!(f::Vector{Float64}, c::StridedVector{Float64}, x::Vector{Float64}) @boundscheck check_clenshaw_points(x, f) ccall((:ft_clenshaw, libfasttransforms), Cvoid, (Cint, Ptr{Float64}, Cint, Cint, Ptr{Float64}, Ptr{Float64}), length(c), c, stride(c, 1), length(x), x, f) f end -function clenshaw!(c::StridedVector{Float32}, x::Vector{Float32}, f::Vector{Float32}) +function clenshaw!(f::Vector{Float32}, c::StridedVector{Float32}, x::Vector{Float32}) @boundscheck check_clenshaw_points(x, f) ccall((:ft_clenshawf, libfasttransforms), Cvoid, (Cint, Ptr{Float32}, Cint, Cint, Ptr{Float32}, Ptr{Float32}), length(c), c, stride(c, 1), length(x), x, f) f end -function clenshaw!(c::StridedVector{Float64}, A::Vector{Float64}, B::Vector{Float64}, C::Vector{Float64}, x::Vector{Float64}, ϕ₀::Vector{Float64}, f::Vector{Float64}) +function clenshaw!(f::Vector{Float64}, c::StridedVector{Float64}, A::Vector{Float64}, B::Vector{Float64}, C::Vector{Float64}, x::Vector{Float64}, ϕ₀::Vector{Float64}) N = length(c) @boundscheck check_clenshaw_recurrences(N, A, B, C) @boundscheck check_clenshaw_points(x, ϕ₀, f) @@ -89,7 +89,7 @@ function clenshaw!(c::StridedVector{Float64}, A::Vector{Float64}, B::Vector{Floa f end -function clenshaw!(c::StridedVector{Float32}, A::Vector{Float32}, B::Vector{Float32}, C::Vector{Float32}, x::Vector{Float32}, ϕ₀::Vector{Float32}, f::Vector{Float32}) +function clenshaw!(f::Vector{Float32}, c::StridedVector{Float32}, A::Vector{Float32}, B::Vector{Float32}, C::Vector{Float32}, x::Vector{Float32}, ϕ₀::Vector{Float32}) N = length(c) @boundscheck check_clenshaw_recurrences(N, A, B, C) @boundscheck check_clenshaw_points(x, ϕ₀, f) diff --git a/test/libfasttransformstests.jl b/test/libfasttransformstests.jl index de9d5f78..545da7d9 100644 --- a/test/libfasttransformstests.jl +++ b/test/libfasttransformstests.jl @@ -8,10 +8,10 @@ FastTransforms.ft_set_num_threads(ceil(Int, Base.Sys.CPU_THREADS/2)) c = one(T) ./ (1:n) x = collect(-1 .+ 2*(0:n-1)/T(n)) f = similar(x) - @test FastTransforms.horner!(c, x, f) == f + @test FastTransforms.horner!(f, c, x) == f fd = T[sum(c[k]*x^(k-1) for k in 1:length(c)) for x in x] @test f ≈ fd - @test FastTransforms.clenshaw!(c, x, f) == f + @test FastTransforms.clenshaw!(f, c, x) == f fd = T[sum(c[k]*cos((k-1)*acos(x)) for k in 1:length(c)) for x in x] @test f ≈ fd A = T[(2k+one(T))/(k+one(T)) for k in 0:length(c)-1] @@ -19,7 +19,7 @@ FastTransforms.ft_set_num_threads(ceil(Int, Base.Sys.CPU_THREADS/2)) C = T[k/(k+one(T)) for k in 0:length(c)] phi0 = ones(T, length(x)) c = FastTransforms.lib_cheb2leg(c) - @test FastTransforms.clenshaw!(c, A, B, C, x, phi0, f) == f + @test FastTransforms.clenshaw!(f, c, A, B, C, x, phi0) == f @test f ≈ fd end