Skip to content

Commit 197bfab

Browse files
authored
Merge branch 'master' into pochhammervec
2 parents 09b8a63 + 2827377 commit 197bfab

19 files changed

+1565
-513
lines changed

Project.toml

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
name = "FastTransforms"
22
uuid = "057dd010-8810-581a-b7be-e3fc3b93f78c"
3-
version = "0.15.7"
3+
version = "0.16.1"
44

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

1919
[compat]
2020
AbstractFFTs = "1.0"
21+
BandedMatrices = "1.5"
2122
FFTW = "1.7"
22-
FastGaussQuadrature = "0.4, 0.5"
23+
FastGaussQuadrature = "0.4, 0.5, 1"
2324
FastTransforms_jll = "0.6.2"
2425
FillArrays = "0.9, 0.10, 0.11, 0.12, 0.13, 1"
2526
GenericFFT = "0.1"
2627
Reexport = "0.2, 1.0"
2728
SpecialFunctions = "0.10, 1, 2"
2829
ToeplitzMatrices = "0.7.1, 0.8"
2930
julia = "1.7"
31+
32+
33+
[extras]
34+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
35+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
36+
37+
[targets]
38+
test = ["Test", "Random"]

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# FastTransforms.jl
22

33
[![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)
4+
[![pkgeval](https://juliahub.com/docs/General/FastTransforms/stable/pkgeval.svg)](https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html)
45

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

docs/Project.toml

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[deps]
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
33
FastTransforms = "057dd010-8810-581a-b7be-e3fc3b93f78c"
4+
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
45
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
56
PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a"
67
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

docs/make.jl

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ examples = [
1010
"automaticdifferentiation.jl",
1111
"chebyshev.jl",
1212
"disk.jl",
13+
"halfrange.jl",
1314
"nonlocaldiffusion.jl",
1415
"padua.jl",
1516
"sphere.jl",
@@ -43,6 +44,7 @@ makedocs(
4344
"generated/automaticdifferentiation.md",
4445
"generated/chebyshev.md",
4546
"generated/disk.md",
47+
"generated/halfrange.md",
4648
"generated/nonlocaldiffusion.md",
4749
"generated/padua.md",
4850
"generated/sphere.md",

examples/halfrange.jl

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# # Half-range Chebyshev polynomials
2+
# In [this paper](https://doi.org/10.1137/090752456), [Daan Huybrechs](https://github.com/daanhb) introduced the so-called half-range Chebyshev polynomials
3+
# as the semi-classical orthogonal polynomials with respect to the inner product:
4+
# ```math
5+
# \langle f, g \rangle = \int_0^1 f(x) g(x)\frac{{\rm d} x}{\sqrt{1-x^2}}.
6+
# ```
7+
# By the variable transformation $y = 2x-1$, the resulting polynomials can be related to
8+
# orthogonal polynomials on $(-1,1)$ with the Jacobi weight $(1-y)^{-\frac{1}{2}}$ modified by the weight $(3+y)^{-\frac{1}{2}}$.
9+
#
10+
# We shall use the fact that:
11+
# ```math
12+
# \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,
13+
# ```
14+
# and results from [this paper](https://arxiv.org/abs/2302.08448) to consider the half-range Chebyshev polynomials as
15+
# modifications of the Jacobi polynomials $P_n^{(-\frac{1}{2},0)}(y)$.
16+
17+
using FastTransforms, LinearAlgebra, Plots, LaTeXStrings
18+
const GENFIGS = joinpath(pkgdir(FastTransforms), "docs/src/generated")
19+
!isdir(GENFIGS) && mkdir(GENFIGS)
20+
plotlyjs()
21+
22+
# We truncate the generating function to ensure a relative error less than `eps()` in the uniform norm on $(-1,1)$:
23+
z = -1/(3+sqrt(8))
24+
K = sqrt(-2z)
25+
N = ceil(Int, log(abs(z), eps()/2*(1-abs(z))/K) - 1)
26+
d = K .* z .^(0:N)
27+
28+
# Then, we convert this representation to the expansion in Jacobi polynomials $P_n^{(-\frac{1}{2}, 0)}(y)$:
29+
u = jac2jac(d, 0.0, 0.0, -0.5, 0.0; norm1 = false, norm2 = true)
30+
31+
# Our working polynomial degree will be:
32+
n = 100
33+
34+
# We compute the connection coefficients between the modified orthogonal polynomials and the Jacobi polynomials:
35+
P = plan_modifiedjac2jac(Float64, n+1, -0.5, 0.0, u)
36+
37+
# We store the connection to first kind Chebyshev polynomials:
38+
P1 = plan_jac2cheb(Float64, n+1, -0.5, 0.0; normjac = true)
39+
40+
# We compute the Chebyshev series for the degree-$k\le n$ modified polynomial and its values at the Chebyshev points:
41+
q = k -> lmul!(P1, lmul!(P, [zeros(k); 1.0; zeros(n-k)]))
42+
qvals = k-> ichebyshevtransform(q(k))
43+
44+
# 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):
45+
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])
46+
XQ = FastTransforms.modified_jacobi_matrix(P, XP)
47+
SymTridiagonal(XQ.dv[1:10], XQ.ev[1:9])
48+
49+
# And we plot:
50+
x = (chebyshevpoints(Float64, n+1, Val(1)) .+ 1 ) ./ 2
51+
p = plot(x, qvals(0); linewidth=2.0, legend = false, xlim=(0,1), xlabel=L"x",
52+
ylabel=L"T^h_n(x)", title="Half-Range Chebyshev Polynomials and Their Roots",
53+
extra_plot_kwargs = KW(:include_mathjax => "cdn"))
54+
for k in 1:10
55+
λ = (eigvals(SymTridiagonal(XQ.dv[1:k], XQ.ev[1:k-1])) .+ 1) ./ 2
56+
plot!(x, qvals(k); linewidth=2.0, color=palette(:default)[k+1])
57+
scatter!(λ, zero(λ); markersize=2.5, color=palette(:default)[k+1])
58+
end
59+
p
60+
savefig(joinpath(GENFIGS, "halfrange.html"))
61+
###```@raw html
62+
###<object type="text/html" data="../halfrange.html" style="width:100%;height:400px;"></object>
63+
###```
64+
65+
# 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:
66+
= 3*[u; 0]+XP[1:N+2, 1:N+1]*u
67+
v = jac2jac(v̂, -0.5, 0.0, 0.5, 1.0; norm1 = true, norm2 = true)
68+
function threshold!(A::AbstractArray, ϵ)
69+
for i in eachindex(A)
70+
if abs(A[i]) < ϵ A[i] = 0 end
71+
end
72+
A
73+
end
74+
P′ = plan_modifiedjac2jac(Float64, n+1, 0.5, 1.0, v)
75+
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.
76+
DQ = UpperTriangular(threshold!(P′\(DP*(P*I)), 100eps())) # The semi-classical differentiation matrix representing 𝒟 Q(y) = Q̂(y) D_Q.
77+
UpperTriangular(DQ[1:10,1:10])

src/FastTransforms.jl

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
module FastTransforms
22

3-
using FastGaussQuadrature, FillArrays, LinearAlgebra,
3+
using BandedMatrices, FastGaussQuadrature, FillArrays, LinearAlgebra,
44
Reexport, SpecialFunctions, ToeplitzMatrices
55

66
@reexport using AbstractFFTs
77
@reexport using FFTW
88
@reexport using GenericFFT
99

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

1313
import Base.GMP: Limb
1414

@@ -19,14 +19,16 @@ import AbstractFFTs: Plan, ScaledPlan,
1919
fftshift, ifftshift, rfft_output_size, brfft_output_size,
2020
normalization
2121

22+
import BandedMatrices: bandwidths
23+
2224
import FFTW: dct, dct!, idct, idct!, plan_dct!, plan_idct!,
2325
plan_dct, plan_idct, fftwNumber
2426

2527
import FastGaussQuadrature: unweightedgausshermite
2628

2729
import FillArrays: AbstractFill, getindex_value
2830

29-
import LinearAlgebra: mul!, lmul!, ldiv!
31+
import LinearAlgebra: mul!, lmul!, ldiv!, cholesky
3032

3133
import GenericFFT: interlace # imported in downstream packages
3234

@@ -112,6 +114,8 @@ include("specialfunctions.jl")
112114
include("toeplitzplans.jl")
113115
include("toeplitzhankel.jl")
114116

117+
include("SymmetricToeplitzPlusHankel.jl")
118+
115119
# following use libfasttransforms by default
116120
for f in (:jac2jac,
117121
:lag2lag, :jac2ultra, :ultra2jac, :jac2cheb,
@@ -134,5 +138,6 @@ end
134138
# end
135139
# end
136140

141+
include("docstrings.jl")
137142

138143
end # module

src/PaduaTransform.jl

+33-8
Original file line numberDiff line numberDiff line change
@@ -209,21 +209,46 @@ function paduapoints(::Type{T}, n::Integer) where T
209209
MM=Matrix{T}(undef,N,2)
210210
m=0
211211
delta=0
212-
NN=fld(n+2,2)
213-
@inbounds for k=n:-1:0
214-
if isodd(n)>0
215-
delta=mod(k,2)
212+
NN=div(n,2)+1
213+
# x coordinates
214+
for k=n:-1:0
215+
if isodd(n)
216+
delta = Int(isodd(k))
216217
end
218+
x = -cospi(T(k)/n)
217219
@inbounds for j=NN+delta:-1:1
218220
m+=1
219-
MM[m,1]=sinpi(T(k)/n-T(0.5))
220-
if isodd(n-k)>0
221-
MM[m,2]=sinpi((2j-one(T))/(n+1)-T(0.5))
221+
MM[m,1]=x
222+
end
223+
end
224+
# y coordinates
225+
# populate the first two sets, and copy the rest
226+
m=0
227+
for k=n:-1:n-1
228+
if isodd(n)
229+
delta = Int(isodd(k))
230+
end
231+
for j=NN+delta:-1:1
232+
m+=1
233+
@inbounds if isodd(n-k)
234+
MM[m,2]=-cospi((2j-one(T))/(n+1))
222235
else
223-
MM[m,2]=sinpi(T(2j-2)/(n+1)-T(0.5))
236+
MM[m,2]=-cospi(T(2j-2)/(n+1))
224237
end
225238
end
226239
end
240+
m += 1
241+
# number of y coordinates between k=n and k=n-2
242+
Ny_shift = 2NN+isodd(n)
243+
for k in n-2:-1:0
244+
if isodd(n)
245+
delta = Int(isodd(k))
246+
end
247+
for j in range(m, length=NN+delta)
248+
@inbounds MM[j,2] = MM[j-Ny_shift,2]
249+
end
250+
m += NN+delta
251+
end
227252
return MM
228253
end
229254

src/SymmetricToeplitzPlusHankel.jl

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
struct SymmetricToeplitzPlusHankel{T} <: AbstractMatrix{T}
2+
v::Vector{T}
3+
n::Int
4+
end
5+
6+
function SymmetricToeplitzPlusHankel(v::Vector{T}) where T
7+
n = (length(v)+1)÷2
8+
SymmetricToeplitzPlusHankel{T}(v, n)
9+
end
10+
11+
size(A::SymmetricToeplitzPlusHankel{T}) where T = (A.n, A.n)
12+
getindex(A::SymmetricToeplitzPlusHankel{T}, i::Integer, j::Integer) where T = A.v[abs(i-j)+1] + A.v[i+j-1]
13+
14+
struct SymmetricBandedToeplitzPlusHankel{T} <: BandedMatrices.AbstractBandedMatrix{T}
15+
v::Vector{T}
16+
n::Int
17+
b::Int
18+
end
19+
20+
function SymmetricBandedToeplitzPlusHankel(v::Vector{T}, n::Integer) where T
21+
SymmetricBandedToeplitzPlusHankel{T}(v, n, length(v)-1)
22+
end
23+
24+
size(A::SymmetricBandedToeplitzPlusHankel{T}) where T = (A.n, A.n)
25+
function getindex(A::SymmetricBandedToeplitzPlusHankel{T}, i::Integer, j::Integer) where T
26+
v = A.v
27+
if abs(i-j) < length(v)
28+
if i+j-1 length(v)
29+
v[abs(i-j)+1] + v[i+j-1]
30+
else
31+
v[abs(i-j)+1]
32+
end
33+
else
34+
zero(T)
35+
end
36+
end
37+
bandwidths(A::SymmetricBandedToeplitzPlusHankel{T}) where T = (A.b, A.b)
38+
39+
#
40+
# Jac*W-W*Jac' = G*J*G'
41+
# This returns G and J, where J = [0 I; -I 0], respecting the skew-symmetry of the right-hand side.
42+
#
43+
function compute_skew_generators(A::SymmetricToeplitzPlusHankel{T}) where T
44+
v = A.v
45+
n = size(A, 1)
46+
J = [zero(T) one(T); -one(T) zero(T)]
47+
G = zeros(T, n, 2)
48+
G[n, 1] = one(T)
49+
u2 = reverse(v[2:n+1])
50+
u2[1:n-1] .+= v[n+1:2n-1]
51+
G[:, 2] .= -u2
52+
G, J
53+
end
54+
55+
function cholesky(A::SymmetricToeplitzPlusHankel{T}) where T
56+
n = size(A, 1)
57+
G, J = compute_skew_generators(A)
58+
L = zeros(T, n, n)
59+
r = A[:, 1]
60+
r2 = zeros(T, n)
61+
l = zeros(T, n)
62+
v = zeros(T, n)
63+
col1 = zeros(T, n)
64+
STPHcholesky!(L, G, r, r2, l, v, col1, n)
65+
return Cholesky(L, 'L', 0)
66+
end
67+
68+
function STPHcholesky!(L::Matrix{T}, G, r, r2, l, v, col1, n) where T
69+
@inbounds @simd for k in 1:n-1
70+
x = sqrt(r[1])
71+
for j in 1:n-k+1
72+
L[j+k-1, k] = l[j] = r[j]/x
73+
end
74+
for j in 1:n-k+1
75+
v[j] = G[j, 1]*G[1,2]-G[j,2]*G[1,1]
76+
end
77+
F12 = k == 1 ? T(2) : T(1)
78+
r2[1] = (r[2] - v[1])/F12
79+
for j in 2:n-k
80+
r2[j] = (r[j+1]+r[j-1] + r[1]*col1[j] - col1[1]*r[j] - v[j])/F12
81+
end
82+
r2[n-k+1] = (r[n-k] + r[1]*col1[n-k+1] - col1[1]*r[n-k+1] - v[n-k+1])/F12
83+
cst = r[2]/x
84+
for j in 1:n-k
85+
r[j] = r2[j+1] - cst*l[j+1]
86+
end
87+
for j in 1:n-k
88+
col1[j] = -F12/x*l[j+1]
89+
end
90+
c1 = G[1, 1]
91+
c2 = G[1, 2]
92+
for j in 1:n-k
93+
G[j, 1] = G[j+1, 1] - l[j+1]*c1/x
94+
G[j, 2] = G[j+1, 2] - l[j+1]*c2/x
95+
end
96+
end
97+
L[n, n] = sqrt(r[1])
98+
end
99+
100+
function cholesky(A::SymmetricBandedToeplitzPlusHankel{T}) where T
101+
n = size(A, 1)
102+
b = A.b
103+
R = BandedMatrix{T}(undef, (n, n), (0, bandwidth(A, 2)))
104+
r = A[1:b+2, 1]
105+
r2 = zeros(T, b+3)
106+
l = zeros(T, b+3)
107+
col1 = zeros(T, b+2)
108+
SBTPHcholesky!(R, r, r2, l, col1, n, b)
109+
return Cholesky(R, 'U', 0)
110+
end
111+
112+
function SBTPHcholesky!(R::BandedMatrix{T}, r, r2, l, col1, n, b) where T
113+
@inbounds @simd for k in 1:n
114+
x = sqrt(r[1])
115+
for j in 1:b+1
116+
l[j] = r[j]/x
117+
end
118+
for j in 1:min(n-k+1, b+1)
119+
R[k, j+k-1] = l[j]
120+
end
121+
F12 = k == 1 ? T(2) : T(1)
122+
r2[1] = r[2]/F12
123+
for j in 2:b+1
124+
r2[j] = (r[j+1]+r[j-1] + r[1]*col1[j] - col1[1]*r[j])/F12
125+
end
126+
r2[b+2] = (r[b+1] + r[1]*col1[b+2] - col1[1]*r[b+2])/F12
127+
cst = r[2]/x
128+
for j in 1:b+2
129+
r[j] = r2[j+1] - cst*l[j+1]
130+
end
131+
for j in 1:b+2
132+
col1[j] = -F12/x*l[j+1]
133+
end
134+
end
135+
end

0 commit comments

Comments
 (0)