Skip to content

Commit 66b198c

Browse files
committed
Merge branch 'dl/legendreconstruct' of https://github.com/JuliaApproximation/ClassicalOrthogonalPolynomials.jl into dl/legendreconstruct
2 parents 7b3d4c9 + a9edc47 commit 66b198c

File tree

5 files changed

+201
-90
lines changed

5 files changed

+201
-90
lines changed

src/ClassicalOrthogonalPolynomials.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import LazyArrays: MemoryLayout, Applied, ApplyStyle, flatten, _flatten, adjoint
2121
import ArrayLayouts: MatMulVecAdd, materialize!, _fill_lmul!, sublayout, sub_materialize, lmul!, ldiv!, ldiv, transposelayout, triangulardata,
2222
subdiagonaldata, diagonaldata, supdiagonaldata, mul, rowsupport, colsupport
2323
import LazyBandedMatrices: SymTridiagonal, Bidiagonal, Tridiagonal, unitblocks, BlockRange1, AbstractLazyBandedLayout
24-
import LinearAlgebra: pinv, factorize, qr, adjoint, transpose, dot, mul!
24+
import LinearAlgebra: pinv, factorize, qr, adjoint, transpose, dot, mul!, reflectorApply!
2525
import BandedMatrices: AbstractBandedLayout, AbstractBandedMatrix, _BandedMatrix, bandeddata
2626
import FillArrays: AbstractFill, getindex_value, SquareEye
2727
import DomainSets: components

src/choleskyQR.jl

Lines changed: 134 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ OrthogonalPolynomial(w::AbstractQuasiVector) = OrthogonalPolynomial(w, orthogona
2323
function OrthogonalPolynomial(w::AbstractQuasiVector, P::AbstractQuasiMatrix)
2424
Q = normalized(P)
2525
X = cholesky_jacobimatrix(w, Q)
26-
ConvertedOrthogonalPolynomial(w, X, X.dv.U, Q)
26+
ConvertedOrthogonalPolynomial(w, X, parent(X.dv).U, Q)
2727
end
2828

2929
orthogonalpolynomial(w::AbstractQuasiVector) = OrthogonalPolynomial(w)
@@ -35,7 +35,7 @@ orthogonalpolynomial(w::SubQuasiArray) = orthogonalpolynomial(parent(w))[parenti
3535
cholesky_jacobimatrix(w, P)
3636
3737
returns the Jacobi matrix `X` associated to a quasi-matrix of polynomials
38-
orthogonal with respect to `w(x) w_p(x)` where `w_p(x)` is the weight of the polynomials in `P`.
38+
orthogonal with respect to `w(x) w_p(x)` where `w_p(x)` is the weight of the polynomials in `P` by computing a Cholesky decomposition of the weight modification.
3939
4040
The resulting polynomials are orthonormal on the same domain as `P`. The supplied `P` must be normalized. Accepted inputs are `w` as a function or `W` as an infinite matrix representing multiplication with the function `w` on the basis `P`.
4141
"""
@@ -51,61 +51,62 @@ function cholesky_jacobimatrix(W::AbstractMatrix, Q)
5151
U = cholesky(W).U
5252
X = jacobimatrix(Q)
5353
UX = ApplyArray(*,U,X)
54-
return SymTridiagonal(CholeskyJacobiBand{:dv}(U, UX),CholeskyJacobiBand{:ev}(U, UX))
54+
CJD = CholeskyJacobiData(U,UX)
55+
return SymTridiagonal(view(CJD,:,1),view(CJD,:,2))
5556
end
5657

57-
# The generated Jacobi operators are symmetric tridiagonal, so we store their data in cached bands
58-
mutable struct CholeskyJacobiBand{dv,T} <: AbstractCachedVector{T}
59-
data::Vector{T} # store band entries, :dv for diagonal, :ev for off-diagonal
58+
# The generated Jacobi operators are symmetric tridiagonal, so we store their data in two cached bands which are generated in tandem but can be accessed separately.
59+
mutable struct CholeskyJacobiData{T} <: AbstractMatrix{T}
60+
dv::AbstractVector{T} # store diagonal band entries in adaptively sized vector
61+
ev::AbstractVector{T} # store off-diagonal band entries in adaptively sized vector
6062
U::UpperTriangular{T} # store upper triangular conversion matrix (needed to extend available entries)
6163
UX::ApplyArray{T} # store U*X, where X is the Jacobi matrix of the original P (needed to extend available entries)
6264
datasize::Int # size of so-far computed block
6365
end
6466

6567
# Computes the initial data for the Jacobi operator bands
66-
function CholeskyJacobiBand{:dv}(U::AbstractMatrix{T}, UX) where T
67-
dv = zeros(T,2) # compute a length 2 vector on first go
68-
dv[1] = dot(view(UX,1,1), U[1,1] \ [one(T)])
69-
dv[2] = dot(view(UX,2,1:2), U[1:2,1:2] \ [zero(T); one(T)])
70-
return CholeskyJacobiBand{:dv,T}(dv, U, UX, 2)
71-
end
72-
function CholeskyJacobiBand{:ev}(U::AbstractMatrix{T}, UX) where T
73-
ev = zeros(T,2) # compute a length 2 vector on first go
74-
ev[1] = dot(view(UX,1,1:2), U[1:2,1:2] \ [zero(T); one(T)])
75-
ev[2] = dot(view(UX,2,1:3), U[1:3,1:3] \ [zeros(T,2); one(T)])
76-
return CholeskyJacobiBand{:ev,T}(ev, U, UX, 2)
68+
function CholeskyJacobiData(U::AbstractMatrix{T}, UX) where T
69+
dv = Vector{T}(undef,2) # compute a length 2 vector on first go
70+
ev = Vector{T}(undef,2)
71+
dv[1] = UX[1,1]/U[1,1] # this is dot(view(UX,1,1), U[1,1] \ [one(T)])
72+
dv[2] = -U[1,2]*UX[2,1]/(U[1,1]*U[2,2])+UX[2,2]/U[2,2] # this is dot(view(UX,2,1:2), U[1:2,1:2] \ [zero(T); one(T)])
73+
ev[1] = -UX[1,1]*U[1,2]/(U[1,1]*U[2,2])+UX[1,2]/U[2,2] # this is dot(view(UX,1,1:2), U[1:2,1:2] \ [zero(T); one(T)])
74+
ev[2] = UX[2,1]/U[3,3]*(-U[1,3]/U[1,1]+U[1,2]*U[2,3]/(U[1,1]*U[2,2]))+UX[2,2]/U[3,3]*(-U[2,3]/U[2,2])+UX[2,3]/U[3,3] # this is dot(view(UX,2,1:3), U[1:3,1:3] \ [zeros(T,2); one(T)])[1:3,1:3] \ [zeros(T,2); one(T)])
75+
return CholeskyJacobiData{T}(dv, ev, U, UX, 2)
7776
end
7877

79-
size(::CholeskyJacobiBand) = (ℵ₀,) # Stored as an infinite cached vector
78+
size(::CholeskyJacobiData) = (ℵ₀,2) # Stored as two infinite cached bands
79+
80+
function getindex(K::CholeskyJacobiData, n::Integer, m::Integer)
81+
@assert (m==1) || (m==2)
82+
resizedata!(K,n,m)
83+
m == 1 && return K.dv[n]
84+
m == 2 && return K.ev[n]
85+
end
8086

8187
# Resize and filling functions for cached implementation
82-
function resizedata!(K::CholeskyJacobiBand, nm::Integer)
88+
function resizedata!(K::CholeskyJacobiData, n::Integer, m::Integer)
89+
nm = max(n,m)
8390
νμ = K.datasize
8491
if nm > νμ
85-
resize!(K.data,nm)
86-
cache_filldata!(K, νμ:nm)
92+
resize!(K.dv,nm)
93+
resize!(K.ev,nm)
94+
_fillcholeskybanddata!(K, νμ:nm)
8795
K.datasize = nm
8896
end
8997
K
9098
end
91-
function cache_filldata!(J::CholeskyJacobiBand{:dv,T}, inds::UnitRange{Int}) where T
92-
# pre-fill U and UX to prevent expensive step-by-step filling in of cached U and UX in the loop
93-
getindex(J.U,inds[end]+1,inds[end]+1)
94-
getindex(J.UX,inds[end]+1,inds[end]+1)
9599

96-
ek = [zero(T); one(T)]
97-
@inbounds for k in inds
98-
J.data[k] = dot(view(J.UX,k,k-1:k), J.U[k-1:k,k-1:k] \ ek)
99-
end
100-
end
101-
function cache_filldata!(J::CholeskyJacobiBand{:ev, T}, inds::UnitRange{Int}) where T
100+
function _fillcholeskybanddata!(J::CholeskyJacobiData{T}, inds::UnitRange{Int}) where T
102101
# pre-fill U and UX to prevent expensive step-by-step filling in of cached U and UX in the loop
103-
getindex(J.U,inds[end]+1,inds[end]+1)
104-
getindex(J.UX,inds[end]+1,inds[end]+1)
102+
resizedata!(J.U,inds[end]+1,inds[end]+1)
103+
resizedata!(J.UX,inds[end]+1,inds[end]+1)
105104

106-
ek = [zeros(T,2); one(T)]
105+
dv, ev, UX, U = J.dv, J.ev, J.UX, J.U
107106
@inbounds for k in inds
108-
J.data[k] = dot(view(J.UX,k,k-1:k+1), J.U[k-1:k+1,k-1:k+1] \ ek)
107+
# this is dot(view(UX,k,k-1:k), U[k-1:k,k-1:k] \ ek)
108+
dv[k] = -U[k-1,k]*UX[k,k-1]/(U[k-1,k-1]*U[k,k])+UX[k,k]/U[k,k]
109+
ev[k] = UX[k,k-1]/U[k+1,k+1]*(-U[k-1,k+1]/U[k-1,k-1]+U[k-1,k]*U[k,k+1]/(U[k-1,k-1]*U[k,k]))+UX[k,k]/U[k+1,k+1]*(-U[k,k+1]/U[k,k])+UX[k,k+1]/U[k+1,k+1]
109110
end
110111
end
111112

@@ -114,80 +115,132 @@ end
114115
qr_jacobimatrix(sqrtw, P)
115116
116117
returns the Jacobi matrix `X` associated to a quasi-matrix of polynomials
117-
orthogonal with respect to `w(x) w_p(x)` where `w_p(x)` is the weight of the polynomials in `P`.
118+
orthogonal with respect to `w(x) w_p(x)` where `w_p(x)` is the weight of the polynomials in `P` by computing a QR decomposition of the square root weight modification.
118119
119120
The resulting polynomials are orthonormal on the same domain as `P`. The supplied `P` must be normalized. Accepted inputs for `sqrtw` are the square root of the weight modification as a function or `sqrtW` as an infinite matrix representing multiplication with the function `sqrt(w)` on the basis `P`.
121+
122+
The underlying QR approach allows two methods, one which uses the Q matrix and one which uses the R matrix. To change between methods, an optional argument :Q or :R may be supplied. The default is to use the Q method.
120123
"""
121-
function qr_jacobimatrix(sqrtw::Function, P)
124+
function qr_jacobimatrix(sqrtw::Function, P, method = :Q)
122125
Q = normalized(P)
123126
x = axes(P,1)
124127
sqrtW = (Q \ (sqrtw.(x) .* Q)) # Compute weight multiplication via Clenshaw
125-
return qr_jacobimatrix(sqrtW, Q)
128+
return qr_jacobimatrix(sqrtW, Q, method)
126129
end
127-
function qr_jacobimatrix(sqrtW::AbstractMatrix, Q)
130+
function qr_jacobimatrix(sqrtW::AbstractMatrix{T}, Q, method = :Q) where T
128131
isnormalized(Q) || error("Polynomials must be orthonormal")
129-
SymTridiagonal(QRJacobiBand{:dv}(sqrtW,Q),QRJacobiBand{:ev}(sqrtW,Q))
130-
end
131-
132-
# The generated Jacobi operators are symmetric tridiagonal, so we store their data in cached bands
133-
mutable struct QRJacobiBand{dv,T} <: AbstractCachedVector{T}
134-
data::Vector{T} # store band entries, :dv for diagonal, :ev for off-diagonal
135-
U::ApplyArray{T} # store upper triangular conversion matrix (needed to extend available entries)
136-
UX::ApplyArray{T} # store U*X, where X is the Jacobi matrix of the original P (needed to extend available entries)
132+
F = qr(sqrtW)
133+
QRJD = QRJacobiData{method,T}(F,Q)
134+
SymTridiagonal(view(QRJD,:,1),view(QRJD,:,2))
135+
end
136+
137+
# The generated Jacobi operators are symmetric tridiagonal, so we store their data in two cached bands which are generated in tandem but can be accessed separately.
138+
mutable struct QRJacobiData{method,T} <: AbstractMatrix{T}
139+
dv::AbstractVector{T} # store diagonal band entries in adaptively sized vector
140+
ev::AbstractVector{T} # store off-diagonal band entries in adaptively sized vector
141+
U # store conversion, Q method: stores QR object. R method: only stores R.
142+
UX # Auxilliary matrix. Q method: stores in-progress incomplete modification. R method: stores U*X for efficiency.
143+
P # Remember original polynomials
137144
datasize::Int # size of so-far computed block
138145
end
139146

140147
# Computes the initial data for the Jacobi operator bands
141-
function QRJacobiBand{:dv}(sqrtW, P::OrthogonalPolynomial{T}) where T
142-
U = qr(sqrtW).R
143-
U = ApplyArray(*,Diagonal(sign.(view(U,band(0)))),U)
148+
function QRJacobiData{:Q,T}(F, P) where T
149+
b = 3+bandwidths(F.R)[2]÷2
144150
X = jacobimatrix(P)
145-
UX = ApplyArray(*,U,X)
146-
dv = zeros(T,2) # compute a length 2 vector on first go
147-
dv[1] = dot(view(UX,1,1), U[1,1] \ [one(T)])
148-
dv[2] = dot(view(UX,2,1:2), U[1:2,1:2] \ [zero(T); one(T)])
149-
return QRJacobiBand{:dv,T}(dv, U, UX, 2)
150-
end
151-
function QRJacobiBand{:ev}(sqrtW, P::OrthogonalPolynomial{T}) where T
152-
U = qr(sqrtW).R
153-
U = ApplyArray(*,Diagonal(sign.(view(U,band(0)))),U)
151+
# we fill 1 entry on the first run
152+
dv = zeros(T,2)
153+
ev = zeros(T,1)
154+
# fill first entry (special case)
155+
M = Matrix(X[1:b,1:b])
156+
resizedata!(F.factors,b,b)
157+
# special case for first entry double Householder product
158+
v = view(F.factors,1:b,1)
159+
reflectorApply!(v, F.τ[1], M)
160+
reflectorApply!(v, F.τ[1], M')
161+
dv[1] = M[1,1]
162+
# fill second entry
163+
# computes H*M*H in-place, overwriting M
164+
v = view(F.factors,2:b,2)
165+
reflectorApply!(v, F.τ[2], view(M,1,2:b))
166+
M[1,2:b] .= view(M,1,2:b) # symmetric matrix, avoid recomputation
167+
reflectorApply!(v, F.τ[2], view(M,2:b,2:b))
168+
reflectorApply!(v, F.τ[2], view(M,2:b,2:b)')
169+
ev[1] = M[1,2]*sign(F.R[1,1]*F.R[2,2]) # includes possible correction for sign (only needed in off-diagonal case), since the QR decomposition does not guarantee positive diagonal on R
170+
K = Matrix(X[2:b+1,2:b+1])
171+
K[1:end-1,1:end-1] .= view(M,2:b,2:b)
172+
return QRJacobiData{:Q,T}(dv, ev, F, K, P, 1)
173+
end
174+
function QRJacobiData{:R,T}(F, P) where T
175+
U = F.R
176+
U = ApplyArray(*,Diagonal(sign.(view(U,band(0)))),U) # QR decomposition does not force positive diagonals on R by default
154177
X = jacobimatrix(P)
155178
UX = ApplyArray(*,U,X)
156-
ev = zeros(T,2) # compute a length 2 vector on first go
157-
ev[1] = dot(view(UX,1,1:2), U[1:2,1:2] \ [zero(T); one(T)])
158-
ev[2] = dot(view(UX,2,1:3), U[1:3,1:3] \ [zeros(T,2); one(T)])
159-
return QRJacobiBand{:ev,T}(ev, U, UX, 2)
179+
dv = Vector{T}(undef,2) # compute a length 2 vector on first go
180+
ev = Vector{T}(undef,2)
181+
dv[1] = UX[1,1]/U[1,1] # this is dot(view(UX,1,1), U[1,1] \ [one(T)])
182+
dv[2] = -U[1,2]*UX[2,1]/(U[1,1]*U[2,2])+UX[2,2]/U[2,2] # this is dot(view(UX,2,1:2), U[1:2,1:2] \ [zero(T); one(T)])
183+
ev[1] = -UX[1,1]*U[1,2]/(U[1,1]*U[2,2])+UX[1,2]/U[2,2] # this is dot(view(UX,1,1:2), U[1:2,1:2] \ [zero(T); one(T)])
184+
ev[2] = UX[2,1]/U[3,3]*(-U[1,3]/U[1,1]+U[1,2]*U[2,3]/(U[1,1]*U[2,2]))+UX[2,2]/U[3,3]*(-U[2,3]/U[2,2])+UX[2,3]/U[3,3] # this is dot(view(UX,2,1:3), U[1:3,1:3] \ [zeros(T,2); one(T)])
185+
return QRJacobiData{:R,T}(dv, ev, U, UX, P, 2)
160186
end
161187

162-
size(::QRJacobiBand) = (ℵ₀,) # Stored as an infinite cached vector
188+
189+
size(::QRJacobiData) = (ℵ₀,2) # Stored as two infinite cached bands
190+
191+
function getindex(K::QRJacobiData, n::Integer, m::Integer)
192+
@assert (m==1) || (m==2)
193+
resizedata!(K,n,m)
194+
m == 1 && return K.dv[n]
195+
m == 2 && return K.ev[n]
196+
end
163197

164198
# Resize and filling functions for cached implementation
165-
function resizedata!(K::QRJacobiBand, nm::Integer)
199+
function resizedata!(K::QRJacobiData, n::Integer, m::Integer)
200+
nm = max(n,m)
166201
νμ = K.datasize
167202
if nm > νμ
168-
resize!(K.data,nm)
169-
cache_filldata!(K, νμ:nm)
203+
resize!(K.dv,nm)
204+
resize!(K.ev,nm)
205+
_fillqrbanddata!(K, νμ:nm)
170206
K.datasize = nm
171207
end
172208
K
173209
end
174-
function cache_filldata!(J::QRJacobiBand{:dv,T}, inds::UnitRange{Int}) where T
175-
# pre-fill U and UX to prevent expensive step-by-step filling in of cached U and UX in the loop
176-
getindex(J.U,inds[end]+1,inds[end]+1)
177-
getindex(J.UX,inds[end]+1,inds[end]+1)
178-
179-
ek = [zero(T); one(T)]
180-
@inbounds for k in inds
181-
J.data[k] = dot(view(J.UX,k,k-1:k), J.U[k-1:k,k-1:k] \ ek)
210+
function _fillqrbanddata!(J::QRJacobiData{:Q,T}, inds::UnitRange{Int}) where T
211+
b = bandwidths(J.U.factors)[2]÷2
212+
# pre-fill cached arrays to avoid excessive cost from expansion in loop
213+
m, jj = 1+inds[end], inds[2:end]
214+
X = jacobimatrix(J.P)[1:m+b+2,1:m+b+2]
215+
resizedata!(J.U.factors,m+b,m+b)
216+
resizedata!(J.U.τ,m)
217+
K, τ, F, dv, ev = J.UX, J.U.τ, J.U.factors, J.dv, J.ev
218+
D = sign.(view(J.U.R,band(0)).*view(J.U.R,band(0))[2:end])
219+
M = Matrix{T}(undef,b+3,b+3)
220+
@inbounds for n in jj
221+
dv[n] = K[1,1] # no sign correction needed on diagonal entry due to cancellation
222+
# doublehouseholderapply!(K,τ[n+1],view(F,n+2:n+b+2,n+1),w)
223+
v = view(F,n+1:n+b+2,n+1)
224+
reflectorApply!(v, τ[n+1], view(K,1,2:b+3))
225+
M[1,2:b+3] .= view(M,1,2:b+3) # symmetric matrix, avoid recomputation
226+
reflectorApply!(v, τ[n+1], view(K,2:b+3,2:b+3))
227+
reflectorApply!(v, τ[n+1], view(K,2:b+3,2:b+3)')
228+
ev[n] = K[1,2]*D[n] # contains sign correction from QR not forcing positive diagonals
229+
M .= view(X,n+1:n+b+3,n+1:n+b+3)
230+
M[1:end-1,1:end-1] .= view(K,2:b+3,2:b+3)
231+
K .= M
182232
end
183233
end
184-
function cache_filldata!(J::QRJacobiBand{:ev, T}, inds::UnitRange{Int}) where T
234+
235+
function _fillqrbanddata!(J::QRJacobiData{:R,T}, inds::UnitRange{Int}) where T
185236
# pre-fill U and UX to prevent expensive step-by-step filling in of cached U and UX in the loop
186-
getindex(J.U,inds[end]+1,inds[end]+1)
187-
getindex(J.UX,inds[end]+1,inds[end]+1)
237+
m = inds[end]+1
238+
resizedata!(J.U,m,m)
239+
resizedata!(J.UX,m,m)
188240

189-
ek = [zeros(T,2); one(T)]
241+
dv, ev, UX, U = J.dv, J.ev, J.UX, J.U
190242
@inbounds for k in inds
191-
J.data[k] = dot(view(J.UX,k,k-1:k+1), J.U[k-1:k+1,k-1:k+1] \ ek)
243+
dv[k] = -U[k-1,k]*UX[k,k-1]/(U[k-1,k-1]*U[k,k])+UX[k,k]./U[k,k] # this is dot(view(UX,k,k-1:k), U[k-1:k,k-1:k] \ ek)
244+
ev[k] = UX[k,k-1]/U[k+1,k+1]*(-U[k-1,k+1]/U[k-1,k-1]+U[k-1,k]*U[k,k+1]/(U[k-1,k-1]*U[k,k]))+UX[k,k]/U[k+1,k+1]*(-U[k,k+1]/U[k,k])+UX[k,k+1]/U[k+1,k+1] # this is dot(view(UX,k,k-1:k+1), U[k-1:k+1,k-1:k+1] \ ek)
192245
end
193-
end
246+
end

src/clenshaw.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ _p0(A) = one(eltype(A))
1414
function initiateforwardrecurrence(N, A, B, C, x, μ)
1515
T = promote_type(eltype(A), eltype(B), eltype(C), typeof(x))
1616
p0 = convert(T, μ)
17+
N == 0 && return zero(T), p0
1718
p1 = convert(T, muladd(A[1],x,B[1])*p0)
1819
@inbounds for n = 2:N
1920
p1,p0 = _forwardrecurrence_next(n, A, B, C, x, p0, p1),p1
@@ -69,7 +70,11 @@ end
6970
getindex(P::OrthogonalPolynomial, x::Number, n::AbstractVector) = layout_getindex(P, x, n)
7071
getindex(P::OrthogonalPolynomial, x::AbstractVector, n::AbstractVector) = layout_getindex(P, x, n)
7172
getindex(P::SubArray{<:Any,1,<:OrthogonalPolynomial}, x::AbstractVector) = layout_getindex(P, x)
72-
getindex(P::OrthogonalPolynomial, x::Number, n::Number) = P[x,oneto(n)][end]
73+
Base.@propagate_inbounds function getindex(P::OrthogonalPolynomial, x::Number, n::Number)
74+
@boundscheck checkbounds(P, x, n)
75+
Base.unsafe_getindex(P, x, n)
76+
end
77+
7378

7479
unsafe_layout_getindex(A...) = sub_materialize(Base.unsafe_view(A...))
7580

@@ -79,7 +84,7 @@ Base.unsafe_getindex(P::OrthogonalPolynomial, x::Number, n::AbstractVector) = Ba
7984
Base.unsafe_getindex(P::OrthogonalPolynomial, x::AbstractVector, n::AbstractVector) = Base.unsafe_getindex(P,x,oneto(maximum(n)))[:,n]
8085
Base.unsafe_getindex(P::OrthogonalPolynomial, x::AbstractVector, n::Number) = Base.unsafe_getindex(P, x, 1:n)[:,end]
8186
Base.unsafe_getindex(P::OrthogonalPolynomial, x::Number, ::Colon) = Base.unsafe_getindex(P, x, axes(P,2))
82-
Base.unsafe_getindex(P::OrthogonalPolynomial, x::Number, n::Number) = Base.unsafe_getindex(P,x,oneto(n))[end]
87+
Base.unsafe_getindex(P::OrthogonalPolynomial, x::Number, n::Number) = initiateforwardrecurrence(n-1, recurrencecoefficients(P)..., x, _p0(P))[end]
8388

8489
getindex(P::OrthogonalPolynomial, x::Number, jr::AbstractInfUnitRange{Int}) = view(P, x, jr)
8590
getindex(P::OrthogonalPolynomial, x::AbstractVector, jr::AbstractInfUnitRange{Int}) = view(P, x, jr)

test/test_chebyshev.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import ContinuumArrays: MappedWeightedBasisLayout, Map, WeightedBasisLayout
3838
@test axes(T[1:1,:]) === (oneto(1), oneto(∞))
3939
@test T[1:1,:][:,1:5] == ones(1,5)
4040
@test T[0.1,:][1:10] T[0.1,1:10] (T')[1:10,0.1]
41+
@test T[0.1,10] cos(9acos(0.1))
42+
@test @allocated(T[0.1,10]) 32
4143

4244
@testset "inf-range-indexing" begin
4345
@test T[[begin,end],2:∞][:,2:5] == T[[-1,1],3:6]

0 commit comments

Comments
 (0)