Skip to content

Commit

Permalink
Merge branch 'master' into pochhammervec
Browse files Browse the repository at this point in the history
  • Loading branch information
dlfivefifty committed May 7, 2024
2 parents 09b8a63 + 2827377 commit 197bfab
Show file tree
Hide file tree
Showing 19 changed files with 1,565 additions and 513 deletions.
15 changes: 12 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name = "FastTransforms"
uuid = "057dd010-8810-581a-b7be-e3fc3b93f78c"
version = "0.15.7"
version = "0.16.1"

[deps]
AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c"
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
FastTransforms_jll = "34b6f7d7-08f9-5794-9e10-3819e4c7e49a"
Expand All @@ -13,17 +14,25 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
ToeplitzMatrices = "c751599d-da0a-543b-9d20-d0a503d91d24"

[compat]
AbstractFFTs = "1.0"
BandedMatrices = "1.5"
FFTW = "1.7"
FastGaussQuadrature = "0.4, 0.5"
FastGaussQuadrature = "0.4, 0.5, 1"
FastTransforms_jll = "0.6.2"
FillArrays = "0.9, 0.10, 0.11, 0.12, 0.13, 1"
GenericFFT = "0.1"
Reexport = "0.2, 1.0"
SpecialFunctions = "0.10, 1, 2"
ToeplitzMatrices = "0.7.1, 0.8"
julia = "1.7"


[extras]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Random"]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# FastTransforms.jl

[![Build Status](https://github.com/JuliaApproximation/FastTransforms.jl/workflows/CI/badge.svg)](https://github.com/JuliaApproximation/FastTransforms.jl/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/JuliaApproximation/FastTransforms.jl/branch/master/graph/badge.svg?token=BxTvSNgmLL)](https://codecov.io/gh/JuliaApproximation/FastTransforms.jl) [![](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaApproximation.github.io/FastTransforms.jl/stable) [![](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaApproximation.github.io/FastTransforms.jl/dev)
[![pkgeval](https://juliahub.com/docs/General/FastTransforms/stable/pkgeval.svg)](https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html)

`FastTransforms.jl` allows the user to conveniently work with orthogonal polynomials with degrees well into the millions.

Expand Down
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
FastTransforms = "057dd010-8810-581a-b7be-e3fc3b93f78c"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Expand Down
2 changes: 2 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ examples = [
"automaticdifferentiation.jl",
"chebyshev.jl",
"disk.jl",
"halfrange.jl",
"nonlocaldiffusion.jl",
"padua.jl",
"sphere.jl",
Expand Down Expand Up @@ -43,6 +44,7 @@ makedocs(
"generated/automaticdifferentiation.md",
"generated/chebyshev.md",
"generated/disk.md",
"generated/halfrange.md",
"generated/nonlocaldiffusion.md",
"generated/padua.md",
"generated/sphere.md",
Expand Down
77 changes: 77 additions & 0 deletions examples/halfrange.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# # Half-range Chebyshev polynomials
# In [this paper](https://doi.org/10.1137/090752456), [Daan Huybrechs](https://github.com/daanhb) introduced the so-called half-range Chebyshev polynomials
# as the semi-classical orthogonal polynomials with respect to the inner product:
# ```math
# \langle f, g \rangle = \int_0^1 f(x) g(x)\frac{{\rm d} x}{\sqrt{1-x^2}}.
# ```
# By the variable transformation $y = 2x-1$, the resulting polynomials can be related to
# orthogonal polynomials on $(-1,1)$ with the Jacobi weight $(1-y)^{-\frac{1}{2}}$ modified by the weight $(3+y)^{-\frac{1}{2}}$.
#
# We shall use the fact that:
# ```math
# \frac{1}{\sqrt{3+y}} = \sqrt{\frac{2}{3+\sqrt{8}}}\sum_{n=0}^\infty P_n(y) \left(\frac{-1}{3+\sqrt{8}}\right)^n,
# ```
# and results from [this paper](https://arxiv.org/abs/2302.08448) to consider the half-range Chebyshev polynomials as
# modifications of the Jacobi polynomials $P_n^{(-\frac{1}{2},0)}(y)$.

using FastTransforms, LinearAlgebra, Plots, LaTeXStrings
const GENFIGS = joinpath(pkgdir(FastTransforms), "docs/src/generated")
!isdir(GENFIGS) && mkdir(GENFIGS)
plotlyjs()

# We truncate the generating function to ensure a relative error less than `eps()` in the uniform norm on $(-1,1)$:
z = -1/(3+sqrt(8))
K = sqrt(-2z)
N = ceil(Int, log(abs(z), eps()/2*(1-abs(z))/K) - 1)
d = K .* z .^(0:N)

# Then, we convert this representation to the expansion in Jacobi polynomials $P_n^{(-\frac{1}{2}, 0)}(y)$:
u = jac2jac(d, 0.0, 0.0, -0.5, 0.0; norm1 = false, norm2 = true)

# 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, -0.5, 0.0, u)

# We store the connection to first kind Chebyshev polynomials:
P1 = plan_jac2cheb(Float64, n+1, -0.5, 0.0; 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 $P_n^{(-\frac{1}{2}, 0)}(y)$ and the modified plan, we may compute the modified Jacobi matrix and the corresponding roots (as eigenvalues):
XP = SymTridiagonal([-inv((4n-1)*(4n-5)) for n in 1:n+1], [4n*(2n-1)/(4n-1)/sqrt((4n-3)*(4n+1)) for n in 1:n])
XQ = FastTransforms.modified_jacobi_matrix(P, XP)
SymTridiagonal(XQ.dv[1:10], XQ.ev[1:9])

# And we plot:
x = (chebyshevpoints(Float64, n+1, Val(1)) .+ 1 ) ./ 2
p = plot(x, qvals(0); linewidth=2.0, legend = false, xlim=(0,1), xlabel=L"x",
ylabel=L"T^h_n(x)", title="Half-Range Chebyshev 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])) .+ 1) ./ 2
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, "halfrange.html"))
###```@raw html
###<object type="text/html" data="../halfrange.html" style="width:100%;height:400px;"></object>
###```

# By [Theorem 2.20](https://arxiv.org/abs/2302.08448) it turns out that the *derivatives* of the half-range Chebyshev polynomials are a linear combination of at most two polynomials orthogonal with respect to $\sqrt{(3+y)(1-y)}(1+y)$ on $(-1,1)$. This fact enables us to compute the banded differentiation matrix:
= 3*[u; 0]+XP[1:N+2, 1:N+1]*u
v = jac2jac(v̂, -0.5, 0.0, 0.5, 1.0; norm1 = true, norm2 = true)
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, 0.5, 1.0, v)
DP = UpperTriangular(diagm(1=>[sqrt(n*(n+1/2)) for n in 1:n])) # The classical differentiation matrix representing 𝒟 P^{(-1/2,0)}(y) = P^{(1/2,1)}(y) D_P.
DQ = UpperTriangular(threshold!(P′\(DP*(P*I)), 100eps())) # The semi-classical differentiation matrix representing 𝒟 Q(y) = Q̂(y) D_Q.
UpperTriangular(DQ[1:10,1:10])
11 changes: 8 additions & 3 deletions src/FastTransforms.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
module FastTransforms

using FastGaussQuadrature, FillArrays, LinearAlgebra,
using BandedMatrices, FastGaussQuadrature, FillArrays, LinearAlgebra,
Reexport, SpecialFunctions, ToeplitzMatrices

@reexport using AbstractFFTs
@reexport using FFTW
@reexport using GenericFFT

import Base: convert, unsafe_convert, eltype, ndims, adjoint, transpose, show,
*, \, inv, length, size, view, getindex
*, \, inv, length, size, view, getindex, tail, OneTo

import Base.GMP: Limb

Expand All @@ -19,14 +19,16 @@ import AbstractFFTs: Plan, ScaledPlan,
fftshift, ifftshift, rfft_output_size, brfft_output_size,
normalization

import BandedMatrices: bandwidths

import FFTW: dct, dct!, idct, idct!, plan_dct!, plan_idct!,
plan_dct, plan_idct, fftwNumber

import FastGaussQuadrature: unweightedgausshermite

import FillArrays: AbstractFill, getindex_value

import LinearAlgebra: mul!, lmul!, ldiv!
import LinearAlgebra: mul!, lmul!, ldiv!, cholesky

import GenericFFT: interlace # imported in downstream packages

Expand Down Expand Up @@ -112,6 +114,8 @@ include("specialfunctions.jl")
include("toeplitzplans.jl")
include("toeplitzhankel.jl")

include("SymmetricToeplitzPlusHankel.jl")

# following use libfasttransforms by default
for f in (:jac2jac,
:lag2lag, :jac2ultra, :ultra2jac, :jac2cheb,
Expand All @@ -134,5 +138,6 @@ end
# end
# end

include("docstrings.jl")

end # module
41 changes: 33 additions & 8 deletions src/PaduaTransform.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,21 +209,46 @@ function paduapoints(::Type{T}, n::Integer) where T
MM=Matrix{T}(undef,N,2)
m=0
delta=0
NN=fld(n+2,2)
@inbounds for k=n:-1:0
if isodd(n)>0
delta=mod(k,2)
NN=div(n,2)+1
# x coordinates
for k=n:-1:0
if isodd(n)
delta = Int(isodd(k))
end
x = -cospi(T(k)/n)
@inbounds for j=NN+delta:-1:1
m+=1
MM[m,1]=sinpi(T(k)/n-T(0.5))
if isodd(n-k)>0
MM[m,2]=sinpi((2j-one(T))/(n+1)-T(0.5))
MM[m,1]=x
end
end
# y coordinates
# populate the first two sets, and copy the rest
m=0
for k=n:-1:n-1
if isodd(n)
delta = Int(isodd(k))
end
for j=NN+delta:-1:1
m+=1
@inbounds if isodd(n-k)
MM[m,2]=-cospi((2j-one(T))/(n+1))
else
MM[m,2]=sinpi(T(2j-2)/(n+1)-T(0.5))
MM[m,2]=-cospi(T(2j-2)/(n+1))
end
end
end
m += 1
# number of y coordinates between k=n and k=n-2
Ny_shift = 2NN+isodd(n)
for k in n-2:-1:0
if isodd(n)
delta = Int(isodd(k))
end
for j in range(m, length=NN+delta)
@inbounds MM[j,2] = MM[j-Ny_shift,2]
end
m += NN+delta
end
return MM
end

Expand Down
135 changes: 135 additions & 0 deletions src/SymmetricToeplitzPlusHankel.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
struct SymmetricToeplitzPlusHankel{T} <: AbstractMatrix{T}
v::Vector{T}
n::Int
end

function SymmetricToeplitzPlusHankel(v::Vector{T}) where T
n = (length(v)+1)÷2
SymmetricToeplitzPlusHankel{T}(v, n)
end

size(A::SymmetricToeplitzPlusHankel{T}) where T = (A.n, A.n)
getindex(A::SymmetricToeplitzPlusHankel{T}, i::Integer, j::Integer) where T = A.v[abs(i-j)+1] + A.v[i+j-1]

struct SymmetricBandedToeplitzPlusHankel{T} <: BandedMatrices.AbstractBandedMatrix{T}
v::Vector{T}
n::Int
b::Int
end

function SymmetricBandedToeplitzPlusHankel(v::Vector{T}, n::Integer) where T
SymmetricBandedToeplitzPlusHankel{T}(v, n, length(v)-1)
end

size(A::SymmetricBandedToeplitzPlusHankel{T}) where T = (A.n, A.n)
function getindex(A::SymmetricBandedToeplitzPlusHankel{T}, i::Integer, j::Integer) where T
v = A.v
if abs(i-j) < length(v)
if i+j-1 length(v)
v[abs(i-j)+1] + v[i+j-1]
else
v[abs(i-j)+1]
end
else
zero(T)
end
end
bandwidths(A::SymmetricBandedToeplitzPlusHankel{T}) where T = (A.b, A.b)

#
# Jac*W-W*Jac' = G*J*G'
# This returns G and J, where J = [0 I; -I 0], respecting the skew-symmetry of the right-hand side.
#
function compute_skew_generators(A::SymmetricToeplitzPlusHankel{T}) where T
v = A.v
n = size(A, 1)
J = [zero(T) one(T); -one(T) zero(T)]
G = zeros(T, n, 2)
G[n, 1] = one(T)
u2 = reverse(v[2:n+1])
u2[1:n-1] .+= v[n+1:2n-1]
G[:, 2] .= -u2
G, J
end

function cholesky(A::SymmetricToeplitzPlusHankel{T}) where T
n = size(A, 1)
G, J = compute_skew_generators(A)
L = zeros(T, n, n)
r = A[:, 1]
r2 = zeros(T, n)
l = zeros(T, n)
v = zeros(T, n)
col1 = zeros(T, n)
STPHcholesky!(L, G, r, r2, l, v, col1, n)
return Cholesky(L, 'L', 0)
end

function STPHcholesky!(L::Matrix{T}, G, r, r2, l, v, col1, n) where T
@inbounds @simd for k in 1:n-1
x = sqrt(r[1])
for j in 1:n-k+1
L[j+k-1, k] = l[j] = r[j]/x
end
for j in 1:n-k+1
v[j] = G[j, 1]*G[1,2]-G[j,2]*G[1,1]
end
F12 = k == 1 ? T(2) : T(1)
r2[1] = (r[2] - v[1])/F12
for j in 2:n-k
r2[j] = (r[j+1]+r[j-1] + r[1]*col1[j] - col1[1]*r[j] - v[j])/F12
end
r2[n-k+1] = (r[n-k] + r[1]*col1[n-k+1] - col1[1]*r[n-k+1] - v[n-k+1])/F12
cst = r[2]/x
for j in 1:n-k
r[j] = r2[j+1] - cst*l[j+1]
end
for j in 1:n-k
col1[j] = -F12/x*l[j+1]
end
c1 = G[1, 1]
c2 = G[1, 2]
for j in 1:n-k
G[j, 1] = G[j+1, 1] - l[j+1]*c1/x
G[j, 2] = G[j+1, 2] - l[j+1]*c2/x
end
end
L[n, n] = sqrt(r[1])
end

function cholesky(A::SymmetricBandedToeplitzPlusHankel{T}) where T
n = size(A, 1)
b = A.b
R = BandedMatrix{T}(undef, (n, n), (0, bandwidth(A, 2)))
r = A[1:b+2, 1]
r2 = zeros(T, b+3)
l = zeros(T, b+3)
col1 = zeros(T, b+2)
SBTPHcholesky!(R, r, r2, l, col1, n, b)
return Cholesky(R, 'U', 0)
end

function SBTPHcholesky!(R::BandedMatrix{T}, r, r2, l, col1, n, b) where T
@inbounds @simd for k in 1:n
x = sqrt(r[1])
for j in 1:b+1
l[j] = r[j]/x
end
for j in 1:min(n-k+1, b+1)
R[k, j+k-1] = l[j]
end
F12 = k == 1 ? T(2) : T(1)
r2[1] = r[2]/F12
for j in 2:b+1
r2[j] = (r[j+1]+r[j-1] + r[1]*col1[j] - col1[1]*r[j])/F12
end
r2[b+2] = (r[b+1] + r[1]*col1[b+2] - col1[1]*r[b+2])/F12
cst = r[2]/x
for j in 1:b+2
r[j] = r2[j+1] - cst*l[j+1]
end
for j in 1:b+2
col1[j] = -F12/x*l[j+1]
end
end
end
Loading

0 comments on commit 197bfab

Please sign in to comment.