@@ -23,7 +23,7 @@ OrthogonalPolynomial(w::AbstractQuasiVector) = OrthogonalPolynomial(w, orthogona
23
23
function OrthogonalPolynomial (w:: AbstractQuasiVector , P:: AbstractQuasiMatrix )
24
24
Q = normalized (P)
25
25
X = cholesky_jacobimatrix (w, Q)
26
- ConvertedOrthogonalPolynomial (w, X, X. dv. U, Q)
26
+ ConvertedOrthogonalPolynomial (w, X, parent ( X. dv) . U, Q)
27
27
end
28
28
29
29
orthogonalpolynomial (w:: AbstractQuasiVector ) = OrthogonalPolynomial (w)
@@ -35,7 +35,7 @@ orthogonalpolynomial(w::SubQuasiArray) = orthogonalpolynomial(parent(w))[parenti
35
35
cholesky_jacobimatrix(w, P)
36
36
37
37
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 .
39
39
40
40
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`.
41
41
"""
@@ -51,61 +51,62 @@ function cholesky_jacobimatrix(W::AbstractMatrix, Q)
51
51
U = cholesky (W). U
52
52
X = jacobimatrix (Q)
53
53
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 ))
55
56
end
56
57
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
60
62
U:: UpperTriangular{T} # store upper triangular conversion matrix (needed to extend available entries)
61
63
UX:: ApplyArray{T} # store U*X, where X is the Jacobi matrix of the original P (needed to extend available entries)
62
64
datasize:: Int # size of so-far computed block
63
65
end
64
66
65
67
# 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 )
77
76
end
78
77
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
80
86
81
87
# 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)
83
90
νμ = K. datasize
84
91
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 )
87
95
K. datasize = nm
88
96
end
89
97
K
90
98
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 )
95
99
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
102
101
# 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 )
105
104
106
- ek = [ zeros (T, 2 ); one (T)]
105
+ dv, ev, UX, U = J . dv, J . ev, J . UX, J . U
107
106
@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 ]
109
110
end
110
111
end
111
112
@@ -114,80 +115,132 @@ end
114
115
qr_jacobimatrix(sqrtw, P)
115
116
116
117
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 .
118
119
119
120
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.
120
123
"""
121
- function qr_jacobimatrix (sqrtw:: Function , P)
124
+ function qr_jacobimatrix (sqrtw:: Function , P, method = :Q )
122
125
Q = normalized (P)
123
126
x = axes (P,1 )
124
127
sqrtW = (Q \ (sqrtw .(x) .* Q)) # Compute weight multiplication via Clenshaw
125
- return qr_jacobimatrix (sqrtW, Q)
128
+ return qr_jacobimatrix (sqrtW, Q, method )
126
129
end
127
- function qr_jacobimatrix (sqrtW:: AbstractMatrix , Q)
130
+ function qr_jacobimatrix (sqrtW:: AbstractMatrix{T} , Q, method = :Q ) where T
128
131
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
137
144
datasize:: Int # size of so-far computed block
138
145
end
139
146
140
147
# 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
144
150
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
154
177
X = jacobimatrix (P)
155
178
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 )
160
186
end
161
187
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
163
197
164
198
# 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)
166
201
νμ = K. datasize
167
202
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 )
170
206
K. datasize = nm
171
207
end
172
208
K
173
209
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
182
232
end
183
233
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
185
236
# 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)
188
240
189
- ek = [ zeros (T, 2 ); one (T)]
241
+ dv, ev, UX, U = J . dv, J . ev, J . UX, J . U
190
242
@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)
192
245
end
193
- end
246
+ end
0 commit comments