Skip to content

Commit af8018b

Browse files
TSGutdlfivefifty
andauthored
Pointwise dot() for powerlaw Legendre (#34)
* move over legendredot code * generic types * remove todo * minor tidying * minor tidying * Update test_fourier.jl * Fix broadcasting notation * Try avoiding Clenshaw * big stability overhaul * minor adjustments * add dot() * adjust dot * Fix inconsistent resizedata! * generalised weighted OP re-expansion * Fix \(::Weighted, ::Weighted) * Tests pass * v0.4.1 * trigger build * increase coverage Co-authored-by: Sheehan Olver <[email protected]>
1 parent b4512c7 commit af8018b

File tree

10 files changed

+396
-38
lines changed

10 files changed

+396
-38
lines changed

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ClassicalOrthogonalPolynomials"
22
uuid = "b30e2e7b-c4ee-47da-9d5f-2c5c27239acd"
33
authors = ["Sheehan Olver <[email protected]>"]
4-
version = "0.4"
4+
version = "0.4.1"
55

66
[deps]
77
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
@@ -29,7 +29,7 @@ ArrayLayouts = "0.7"
2929
BandedMatrices = "0.16.5"
3030
BlockArrays = "0.15"
3131
BlockBandedMatrices = "0.10"
32-
ContinuumArrays = "0.8"
32+
ContinuumArrays = "0.8.1"
3333
DomainSets = "0.4, 0.5"
3434
FFTW = "1.1"
3535
FastGaussQuadrature = "0.4.3"
@@ -42,7 +42,7 @@ IntervalSets = "0.5"
4242
LazyArrays = "0.21"
4343
LazyBandedMatrices = "0.5.5"
4444
QuasiArrays = "0.6"
45-
SpecialFunctions = "1"
45+
SpecialFunctions = "1.0"
4646
julia = "1.6"
4747

4848
[extras]

src/ClassicalOrthogonalPolynomials.jl

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module ClassicalOrthogonalPolynomials
2+
using IntervalSets: UnitRange
23
using ContinuumArrays, QuasiArrays, LazyArrays, FillArrays, BandedMatrices, BlockArrays,
34
IntervalSets, DomainSets, ArrayLayouts, SpecialFunctions,
45
InfiniteLinearAlgebra, InfiniteArrays, LinearAlgebra, FastGaussQuadrature, FastTransforms, FFTW,
@@ -11,8 +12,8 @@ import Base: @_inline_meta, axes, getindex, convert, prod, *, /, \, +, -,
1112
import Base.Broadcast: materialize, BroadcastStyle, broadcasted
1213
import LazyArrays: MemoryLayout, Applied, ApplyStyle, flatten, _flatten, colsupport, adjointlayout,
1314
sub_materialize, arguments, sub_paddeddata, paddeddata, PaddedLayout, resizedata!, LazyVector, ApplyLayout, call,
14-
_mul_arguments, CachedVector, CachedMatrix, LazyVector, LazyMatrix, axpy!, AbstractLazyLayout, BroadcastLayout,
15-
AbstractCachedVector, AbstractCachedMatrix
15+
_mul_arguments, CachedVector, CachedMatrix, LazyVector, LazyMatrix, axpy!, AbstractLazyLayout, BroadcastLayout,
16+
AbstractCachedVector, AbstractCachedMatrix, paddeddata
1617
import ArrayLayouts: MatMulVecAdd, materialize!, _fill_lmul!, sublayout, sub_materialize, lmul!, ldiv!, ldiv, transposelayout, triangulardata,
1718
subdiagonaldata, diagonaldata, supdiagonaldata
1819
import LazyBandedMatrices: SymTridiagonal, Bidiagonal, Tridiagonal, AbstractLazyBandedLayout
@@ -39,20 +40,17 @@ import FastGaussQuadrature: jacobimoment
3940
import BlockArrays: blockedrange, _BlockedUnitRange, unblock, _BlockArray
4041
import BandedMatrices: bandwidths
4142

42-
export OrthogonalPolynomial, Normalized, orthonormalpolynomial, LanczosPolynomial,
43+
export OrthogonalPolynomial, Normalized, orthonormalpolynomial, LanczosPolynomial,
4344
Hermite, Jacobi, Legendre, Chebyshev, ChebyshevT, ChebyshevU, ChebyshevInterval, Ultraspherical, Fourier, Laguerre,
4445
HermiteWeight, JacobiWeight, ChebyshevWeight, ChebyshevGrid, ChebyshevTWeight, ChebyshevUWeight, UltrasphericalWeight, LegendreWeight, LaguerreWeight,
4546
WeightedUltraspherical, WeightedChebyshev, WeightedChebyshevT, WeightedChebyshevU, WeightedJacobi,
46-
∞, Derivative, .., Inclusion,
47+
∞, Derivative, .., Inclusion,
4748
chebyshevt, chebyshevu, legendre, jacobi,
4849
legendrep, jacobip, ultrasphericalc, laguerrel,hermiteh, normalizedjacobip,
4950
jacobimatrix, jacobiweight, legendreweight, chebyshevtweight, chebyshevuweight, Weighted
5051

51-
if VERSION < v"1.6-"
52-
oneto(n) = Base.OneTo(n)
53-
else
54-
import Base: oneto
55-
end
52+
53+
import Base: oneto
5654

5755

5856
include("interlace.jl")
@@ -278,10 +276,10 @@ end
278276

279277
_tritrunc(X, n) = _tritrunc(MemoryLayout(X), X, n)
280278

281-
jacobimatrix(V::SubQuasiArray{<:Any,2,<:Any,<:Tuple{Inclusion,OneTo}}) =
279+
jacobimatrix(V::SubQuasiArray{<:Any,2,<:Any,<:Tuple{Inclusion,OneTo}}) =
282280
_tritrunc(jacobimatrix(parent(V)), maximum(parentindices(V)[2]))
283281

284-
grid(P::SubQuasiArray{<:Any,2,<:OrthogonalPolynomial,<:Tuple{Inclusion,AbstractUnitRange}}) =
282+
grid(P::SubQuasiArray{<:Any,2,<:OrthogonalPolynomial,<:Tuple{Inclusion,AbstractUnitRange}}) =
285283
eigvals(symtridiagonalize(jacobimatrix(P)))
286284

287285
function golubwelsch(X)
@@ -326,11 +324,11 @@ function \(A::SubQuasiArray{<:Any,2,<:OrthogonalPolynomial,<:Tuple{Any,Slice}},
326324
parent(A) \ parent(B)
327325
end
328326

327+
# assume we can expand w_B in wA to reduce to polynomial multiplication
329328
function \(wA::WeightedOrthogonalPolynomial, wB::WeightedOrthogonalPolynomial)
330-
w_A,A = arguments(wA)
329+
_,A = arguments(wA)
331330
w_B,B = arguments(wB)
332-
w_A == w_B || error("Not implemented")
333-
A\B
331+
A \ ((A * (wA \ w_B)) .* B)
334332
end
335333

336334

src/classical/jacobi.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,13 @@ broadcasted(::LazyQuasiArrayStyle{2}, ::typeof(*), x::Inclusion, Q::HalfWeighted
177177
\(w_A::HalfWeighted, B::AbstractQuasiArray) = convert(WeightedOrthogonalPolynomial, w_A) \ B
178178
\(A::AbstractQuasiArray, w_B::HalfWeighted) = A \ convert(WeightedOrthogonalPolynomial, w_B)
179179

180-
function \(A::AbstractQuasiArray, w_B::WeightedOrthogonalPolynomial{<:Any,<:Weight,<:Normalized})
180+
function _norm_expand_ldiv(A, w_B)
181181
w,B = w_B.args
182182
B̃,D = arguments(ApplyLayout{typeof(*)}(), B)
183183
(A \ (w .* B̃)) * D
184184
end
185+
\(A::AbstractQuasiArray, w_B::WeightedOrthogonalPolynomial{<:Any,<:Weight,<:Normalized}) = _norm_expand_ldiv(A, w_B)
186+
\(A::WeightedOrthogonalPolynomial, w_B::WeightedOrthogonalPolynomial{<:Any,<:Weight,<:Normalized}) = _norm_expand_ldiv(A, w_B)
185187

186188
axes(::AbstractJacobi{T}) where T = (Inclusion{T}(ChebyshevInterval{real(T)}()), oneto(∞))
187189
==(P::Jacobi, Q::Jacobi) = P.a == Q.a && P.b == Q.b
@@ -292,6 +294,17 @@ function recurrencecoefficients(P::Jacobi)
292294
(A,B,C)
293295
end
294296

297+
# explicit special case for normalized Legendre
298+
# todo: do we want these explicit constructors for normalized Legendre?
299+
# function jacobimatrix(::Normalized{<:Any,<:Legendre{T}}) where T
300+
# b = (one(T):∞) ./sqrt.(4 .*(one(T):∞).^2 .-1)
301+
# Symmetric(_BandedMatrix(Vcat(zeros(∞)', (b)'), ∞, 1, 0), :L)
302+
# end
303+
# function recurrencecoefficients(::Normalized{<:Any,<:Legendre{T}}) where T
304+
# n = zero(T):∞
305+
# nn = one(T):∞
306+
# ((2n .+ 1) ./ (n .+ 1) ./ sqrt.(1 .-2 ./(3 .+2n)), Zeros{T}(∞), Vcat(zero(T),nn ./ (nn .+ 1) ./ sqrt.(1 .-4 ./(3 .+2nn))))
307+
# end
295308

296309
@simplify *(X::Identity, P::Legendre) = ApplyQuasiMatrix(*, P, P\(X*P))
297310

src/lanczos.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function LanczosData(w::AbstractQuasiVector, P::AbstractQuasiMatrix)
4949
x = axes(P,1)
5050
wP = weighted(P)
5151
X = jacobimatrix(P)
52-
W = Clenshaw(P * (wP \ w), P)
52+
W = wP \ (w .* P)
5353
LanczosData(X, W)
5454
end
5555

@@ -58,6 +58,7 @@ function resizedata!(L::LanczosData, N)
5858
resizedata!(L.R, N, N)
5959
resizedata!(L.γ, N)
6060
resizedata!(L.β, N)
61+
resizedata!(L.W, N, N)
6162
lanczos!(L.ncols+1:N, L.X, L.W, L.γ, L.β, L.R)
6263
L.ncols = N
6364
L

src/normalized.jl

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,27 @@ broadcasted(::LazyQuasiArrayStyle{2}, ::typeof(*), x::Inclusion, Q::OrthonormalW
166166

167167

168168
abstract type AbstractWeighted{T} <: Basis{T} end
169+
struct WeightedOPLayout <: AbstractBasisLayout end
169170

170-
MemoryLayout(::Type{<:AbstractWeighted}) = WeightedBasisLayout()
171+
# make act like WeightedBasisLayout
172+
ContinuumArrays._grid(::WeightedOPLayout, P) = ContinuumArrays._grid(WeightedBasisLayout(), P)
173+
ContinuumArrays._factorize(::WeightedOPLayout, P) = ContinuumArrays._factorize(WeightedBasisLayout(), P)
174+
ContinuumArrays.sublayout(::WeightedOPLayout, inds::Type{<:Tuple{<:AbstractAffineQuasiVector,<:AbstractVector}}) = sublayout(WeightedBasisLayout(), inds)
175+
ContinuumArrays.sublayout(::WeightedOPLayout, inds::Type{<:Tuple{<:Inclusion,<:AbstractVector}}) = sublayout(WeightedBasisLayout(), inds)
176+
177+
MemoryLayout(::Type{<:AbstractWeighted}) = WeightedOPLayout()
171178
ContinuumArrays.unweightedbasis(wP::AbstractWeighted) = wP.P
172-
\(w_A::AbstractWeighted, w_B::AbstractWeighted) = convert(WeightedOrthogonalPolynomial, w_A) \ convert(WeightedOrthogonalPolynomial, w_B)
173-
\(w_A::AbstractWeighted, B::AbstractQuasiArray) = convert(WeightedOrthogonalPolynomial, w_A) \ B
174-
\(A::AbstractQuasiArray, w_B::AbstractWeighted) = A \ convert(WeightedOrthogonalPolynomial, w_B)
179+
function copy(L::Ldiv{WeightedOPLayout,WeightedOPLayout})
180+
L.A.P == L.B.P && return Eye{eltype(L)}(∞)
181+
convert(WeightedOrthogonalPolynomial, L.A) \ convert(WeightedOrthogonalPolynomial, L.B)
182+
end
183+
184+
copy(L::Ldiv{WeightedOPLayout,<:AbstractLazyLayout}) = convert(WeightedOrthogonalPolynomial, L.A) \ L.B
185+
copy(L::Ldiv{WeightedOPLayout,<:AbstractBasisLayout}) = convert(WeightedOrthogonalPolynomial, L.A) \ L.B
186+
copy(L::Ldiv{<:AbstractLazyLayout,WeightedOPLayout}) = L.A \ convert(WeightedOrthogonalPolynomial, L.B)
187+
copy(L::Ldiv{<:AbstractBasisLayout,WeightedOPLayout}) = L.A \ convert(WeightedOrthogonalPolynomial, L.B)
188+
copy(L::Ldiv{WeightedOPLayout,ApplyLayout{typeof(*)}}) = copy(Ldiv{UnknownLayout,ApplyLayout{typeof(*)}}(L.A, L.B))
189+
copy(L::Ldiv{WeightedOPLayout,ApplyLayout{typeof(*)},<:Any,<:AbstractQuasiVector}) = copy(Ldiv{UnknownLayout,ApplyLayout{typeof(*)}}(L.A, L.B))
175190

176191
"""
177192
Weighted(P)
@@ -186,7 +201,6 @@ axes(Q::Weighted) = axes(Q.P)
186201
copy(Q::Weighted) = Q
187202

188203
==(A::Weighted, B::Weighted) = A.P == B.P
189-
190204
weight(wP::Weighted) = orthogonalityweight(wP.P)
191205

192206
convert(::Type{WeightedOrthogonalPolynomial}, P::Weighted) = weight(P) .* unweightedbasis(P)

src/stieltjes.jl

Lines changed: 197 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,6 @@ end
175175
T[kr,:] * Diagonal(Vcat(2*convert(V,π)*c*log(c), 2c*D.diag.args[2]))
176176
end
177177

178-
179-
180178
### generic fallback
181179
for Op in (:Hilbert, :StieltjesPoint, :LogKernel, :PowKernel)
182180
@eval @simplify function *(H::$Op, wP::WeightedBasis{<:Any,<:Weight,<:Any})
@@ -185,3 +183,200 @@ for Op in (:Hilbert, :StieltjesPoint, :LogKernel, :PowKernel)
185183
(H * Weighted(Q)) * (Q \ P)
186184
end
187185
end
186+
187+
188+
#################################################
189+
# ∫f(x)g(x)(t-x)^a dx evaluation where f and g given in coefficients
190+
#################################################
191+
# recognize structure of W = ((t .- x).^a
192+
const PowKernelPoint{T,V,D,F} = BroadcastQuasiVector{T, typeof(^), Tuple{ContinuumArrays.AffineQuasiVector{T, V, Inclusion{V, D}, T}, F}}
193+
194+
####
195+
# cached operator implementation
196+
####
197+
# Constructors support BigFloat and it's recommended to use them for high orders.
198+
mutable struct PowerLawMatrix{T, PP<:Normalized{<:Any,<:Legendre{<:Any}}} <: AbstractCachedMatrix{T}
199+
P::PP # OPs - only normalized Legendre supported for now
200+
a::T # naming scheme follows (t-x)^a
201+
t::T
202+
data::Matrix{T}
203+
datasize::Tuple{Int,Int}
204+
function PowerLawMatrix{T, PP}(P::PP, a::T, t::T) where {T, PP<:AbstractQuasiMatrix}
205+
new{T, PP}(P,a,t, gennormalizedpower(a,t,50),(50,50))
206+
end
207+
end
208+
PowerLawMatrix(P::AbstractQuasiMatrix, a::T, t::T) where T = PowerLawMatrix{T,typeof(P)}(P,a,t)
209+
size(K::PowerLawMatrix) = (ℵ₀,ℵ₀) # potential to add maximum size of operator
210+
copy(K::PowerLawMatrix{T,PP}) where {T,PP} = K # Immutable entries
211+
212+
# data filling
213+
#TODO: fix the weird inds
214+
cache_filldata!(K::PowerLawMatrix, inds, _) = fillcoeffmatrix!(K, inds)
215+
216+
# because it really only makes sense to compute this symmetric operator in square blocks, we have to slightly rework some of LazyArrays caching and resizing
217+
function getindex(K::PowerLawMatrix, k::Int, j::Int)
218+
resizedata!(K, k, j)
219+
K.data[k, j]
220+
end
221+
function getindex(K::PowerLawMatrix, kr::AbstractUnitRange, jr::AbstractUnitRange)
222+
resizedata!(K, maximum(kr),maximum(jr))
223+
K.data[kr, jr]
224+
end
225+
function resizedata!(K::PowerLawMatrix, n::Integer, m::Integer)
226+
olddata = K.data
227+
νμ = size(olddata)
228+
nm = max.(νμ,max(n,m))
229+
if νμ nm
230+
K.data = similar(K.data, nm...)
231+
K.data[axes(olddata)...] = olddata
232+
end
233+
if maximum(nm) > maximum(νμ)
234+
inds = maximum(νμ):maximum(nm)
235+
cache_filldata!(K, inds, inds)
236+
K.datasize = nm
237+
end
238+
K
239+
end
240+
241+
####
242+
# methods
243+
####
244+
function broadcasted(::LazyQuasiArrayStyle{2}, ::typeof(*), K::PowKernelPoint, Q::Normalized{<:Any,<:Legendre})
245+
tx,a = K.args
246+
t = tx[zero(typeof(a))]
247+
return Q*PowerLawMatrix(Q,a,t)
248+
end
249+
250+
####
251+
# operator generation
252+
####
253+
254+
# evaluates ∫(t-x)^a Pn(x)P_m(x) dx for the case m=0, i.e. first row of operator, via backwards recursion
255+
function powerleg_backwardsfirstrow(a::T, t::T, ℓ::Int) where T <: Real
256+
=+200
257+
coeff = zeros(BigFloat,ℓ)
258+
coeff[end-1] = one(BigFloat)
259+
for m = reverse(1:-2)
260+
coeff[m] = (coeff[m+2]-t/((a+m+2)/(2*m+1))*normconst_Pnadd1(m,t)*coeff[m+1])/(((a-m+1)/(2*m+1))/((a+m+2)/(2*m+1))*normconst_Pnsub1(m,t))
261+
end
262+
coeff = PLnorminitial00(t,a)/coeff[1].*coeff
263+
return T.(coeff[1:-200])
264+
end
265+
# modify recurrence coefficients to work for normalized Legendre
266+
normconst_Pnadd1(m::Int, settype::T) where T<:Real = sqrt(2*m+3*one(T))/sqrt(2*m+one(T))
267+
normconst_Pnsub1(m::Int, settype::T) where T<:Real = sqrt(2*m+3*one(T))/sqrt(2*m-one(T))
268+
normconst_Pmnmix(n::Int, m::Int, settype::T) where T<:Real = sqrt(2*m+3*one(T))*sqrt(2*n+one(T))/(sqrt(2*m+one(T))*sqrt(2*n-one(T)))
269+
# useful explicit initial case
270+
function PLnorminitial00(t::Real, a::Real)
271+
return ((t+1)^(a+1)-(t-1)^(a+1))/(2*(a+1))
272+
end
273+
274+
# compute r-th coefficient of product expansion of order p and order q normalized Legendre polynomials
275+
productseriescfs(p::T, q::T, r::T) where T = sqrt((2*p+1)*(2*q+1)/((2*(p+q-2*r)+1)))*(2*(p+q-2*r)+1)/(2*(p+q-r)+1)*exp(loggamma(r+one(T)/2)+loggamma(p-r+one(T)/2)+loggamma(q-r+one(T)/2)-loggamma(q-r+one(T))-loggamma(p-r+one(T))-loggamma(r+one(T))-loggamma(q+p-r+one(T)/2)+loggamma(q+p-r+one(T)))/π
276+
277+
# # this generates the entire operator via normalized product Legendre decomposition
278+
# # this is very stable but scales rather poorly with high orders, so we only use it for testing
279+
# function productoperator(a::T, t::T, ℓ::Int) where T
280+
# op::Matrix{T} = zeros(T,ℓ,ℓ)
281+
# # first row where n arbitrary and m==0
282+
# first = powerleg_backwardsfirstrow(a,t,2*ℓ+1)
283+
# op[1,:] = first[1:ℓ]
284+
# # generate remaining rows
285+
# for p = 1:ℓ-1
286+
# for q = p:ℓ-1
287+
# productcfs = zeros(T,2*ℓ+1)
288+
# for i = 0:min(p,q)
289+
# productcfs[1+q+p-2*i] = productseriescfs(p,q,i)
290+
# end
291+
# op[p+1,q+1] = dot(first,productcfs)
292+
# end
293+
# end
294+
# # matrix is symmetric
295+
# for m = 1:ℓ
296+
# for n = m+1:ℓ
297+
# op[n,m] = op[m,n]
298+
# end
299+
# end
300+
# return op
301+
# end
302+
303+
# This function returns the full ℓ×ℓ dot product operator, relying on several different methods for first row, second row, diagonal and remaining elements. We don't use this outside of the initial block.
304+
function gennormalizedpower(a::T, t::T, ℓ::Int) where T <: Real
305+
# initialization
306+
=+3
307+
coeff = zeros(T,ℓ,ℓ)
308+
# construct first row via stable backwards recurrence
309+
first = powerleg_backwardsfirstrow(a,t,2*+1)
310+
coeff[1,:] = first[1:ℓ]
311+
# contruct second row via normalized product Legendre decomposition
312+
@inbounds for q = 1:-1
313+
productcfs = zeros(T,2*+1)
314+
productcfs[q+2] = productseriescfs(1,q,0)
315+
productcfs[q] = productseriescfs(1,q,1)
316+
coeff[2,q+1] = dot(first,productcfs)
317+
end
318+
# contruct the diagonal via normalized product Legendre decomposition
319+
@inbounds for q = 2:-1
320+
productcfs = zeros(T,2*+1)
321+
@inbounds for i = 0:q
322+
productcfs[1+2*q-2*i] = productseriescfs(q,q,i)
323+
end
324+
coeff[q+1,q+1] = dot(first,productcfs)
325+
end
326+
#the remaining cases can be constructed iteratively by means of a T-shaped recurrence
327+
@inbounds for m = 2:-2
328+
# build remaining row elements
329+
@inbounds for j = 1:m-2
330+
coeff[j+2,m+1] = (t/((j+1)*(a+m+j+2)/((2*j+1)*(m+j+1)))*normconst_Pnadd1(j,t)*coeff[j+1,m+1]+((a+1)*m/(m*(m+1)-j*(j+1)))/((j+1)*(a+m+j+2)/((2*j+1)*(m+j+1)))*normconst_Pmnmix(m,j,t)*coeff[j+1,m]-(j*(a+m-j+1)/((2*j+1)*(m-j)))*1/((j+1)*(a+m+j+2)/((2*j+1)*(m+j+1)))*normconst_Pnsub1(j,t)*coeff[j,m+1])
331+
end
332+
end
333+
#matrix is symmetric
334+
@inbounds for m = 1:
335+
@inbounds for n = m+1:
336+
coeff[n,m] = coeff[m,n]
337+
end
338+
end
339+
return coeff[1:-3,1:-3]
340+
end
341+
342+
# the following version takes a previously computed block that has been resized and fills in the missing data guided by indices in inds
343+
function fillcoeffmatrix!(K::PowerLawMatrix, inds::AbstractUnitRange)
344+
# the remaining cases can be constructed iteratively
345+
a = K.a; t = K.t; T = eltype(promote(a,t));
346+
= maximum(inds)
347+
# fill in first row via stable backwards recurrence
348+
first = powerleg_backwardsfirstrow(a,t,2*+1)
349+
K.data[1,inds] = first[inds]
350+
# fill in second row via normalized product Legendre decomposition
351+
@inbounds for q = minimum(inds):-1
352+
productcfs = zeros(T,2*+1)
353+
productcfs[q+2] = productseriescfs(1,q,0)
354+
productcfs[q] = productseriescfs(1,q,1)
355+
K.data[2,q+1] = dot(first,productcfs)
356+
end
357+
# fill in the diagonal via normalized product Legendre decomposition
358+
@inbounds for q = minimum(inds):-1
359+
productcfs = zeros(T,2*+1)
360+
@inbounds for i = 0:q
361+
productcfs[1+2*q-2*i] = productseriescfs(q,q,i)
362+
end
363+
K.data[q+1,q+1] = dot(first,productcfs)
364+
end
365+
@inbounds for m in inds
366+
m = m-2
367+
# build remaining row elements
368+
@inbounds for j = 1:m-1
369+
K.data[j+2,m+2] = (t/((j+1)*(a+m+j+3)/((2*j+1)*(m+j+2)))*normconst_Pnadd1(j,t)*K.data[j+1,m+2]+((a+1)*(m+1)/((m+1)*(m+2)-j*(j+1)))/((j+1)*(a+m+j+3)/((2*j+1)*(m+j+2)))*normconst_Pmnmix(m+1,j,t)*K.data[j+1,m+1]-(j*(a+m-j+2)/((2*j+1)*(m+1-j)))*1/((j+1)*(a+m+j+3)/((2*j+1)*(m+j+2)))*normconst_Pnsub1(j,t)*K.data[j,m+2])
370+
end
371+
end
372+
# matrix is symmetric
373+
@inbounds for m in reverse(inds)
374+
K.data[m,1:end] = K.data[1:end,m]
375+
end
376+
end
377+
378+
function dot(v::AbstractVector{T}, W::PowerLawMatrix, q::AbstractVector{T}) where T
379+
vpad, qpad = paddeddata(v), paddeddata(q)
380+
vl, ql = length(vpad), length(qpad)
381+
return dot(vpad,W[1:vl,1:ql]*qpad)
382+
end

0 commit comments

Comments
 (0)