|
| 1 | +## Root finding for Chebyshev expansions |
| 2 | +# |
| 3 | +# Contains code that is based in part on Chebfun v5's chebfun/@chebteck/roots.m, |
| 4 | +# which is distributed with the following license: |
| 5 | + |
| 6 | +# Copyright (c) 2015, The Chancellor, Masters and Scholars of the University |
| 7 | +# of Oxford, and the Chebfun Developers. All rights reserved. |
| 8 | +# |
| 9 | +# Redistribution and use in source and binary forms, with or without |
| 10 | +# modification, are permitted provided that the following conditions are met: |
| 11 | +# * Redistributions of source code must retain the above copyright |
| 12 | +# notice, this list of conditions and the following disclaimer. |
| 13 | +# * Redistributions in binary form must reproduce the above copyright |
| 14 | +# notice, this list of conditions and the following disclaimer in the |
| 15 | +# documentation and/or other materials provided with the distribution. |
| 16 | +# * Neither the name of the University of Oxford nor the names of its |
| 17 | +# contributors may be used to endorse or promote products derived from |
| 18 | +# this software without specific prior written permission. |
| 19 | +# |
| 20 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 21 | +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 22 | +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 23 | +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
| 24 | +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 25 | +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 26 | +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 27 | +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 | +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 29 | +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 | + |
| 31 | + |
| 32 | +function complexroots(f::Fun{C}) where C<:Chebyshev |
| 33 | + if ncoefficients(f)==0 || (ncoefficients(f)==1 && isapprox(f.coefficients[1],0)) |
| 34 | + throw(ArgumentError("Tried to take roots of a zero function.")) |
| 35 | + elseif ncoefficients(f)==1 |
| 36 | + ComplexF64[] |
| 37 | + elseif ncoefficients(f)==2 |
| 38 | + ComplexF64[-f.coefficients[1]/f.coefficients[2]] |
| 39 | + else |
| 40 | + fromcanonical.(f,colleague_eigvals(f.coefficients)) |
| 41 | + end |
| 42 | +end |
| 43 | + |
| 44 | +function roots(f::Fun{<:Chebyshev}) |
| 45 | + g = Fun(space(f), float.(coefficients(f))) |
| 46 | + gg = setcanonicaldomain(g) |
| 47 | + r = roots(gg) |
| 48 | + fromcanonical.(f,r) |
| 49 | +end |
| 50 | + |
| 51 | + |
| 52 | +for (BF,FF) in ((BigFloat,Float64),(Complex{BigFloat},ComplexF64)) |
| 53 | + @eval function roots( f::Fun{C,$BF} ) where C<:Chebyshev |
| 54 | + # FIND THE ROOTS OF AN IFUN. |
| 55 | + |
| 56 | + d = domain(f) |
| 57 | + c = f.coefficients |
| 58 | + vscale = maximum(abs,values(f)) |
| 59 | + if vscale == 0 |
| 60 | + throw(ArgumentError("Tried to take roots of a zero function.")) |
| 61 | + end |
| 62 | + |
| 63 | + hscale = maximum( [abs(leftendpoint(d)), abs(rightendpoint(d))] ) |
| 64 | + htol = eps(2000.)*max(hscale, 1) # TODO: choose tolerance better |
| 65 | + |
| 66 | + # calculate Flaot64 roots |
| 67 | + r = Array{$BF}(rootsunit_coeffs(strictconvert(Vector{$FF},c./vscale), Float64(htol))) |
| 68 | + # Map roots from [-1,1] to domain of f: |
| 69 | + rts = fromcanonical.(Ref(d),r) |
| 70 | + fp = differentiate(f) |
| 71 | + |
| 72 | + # do Newton 3 time |
| 73 | + for _ = 1:3 |
| 74 | + rts .-=extrapolate.(Ref(f),rts)./extrapolate.(Ref(fp),rts) |
| 75 | + end |
| 76 | + |
| 77 | + return rts |
| 78 | + end |
| 79 | +end |
| 80 | + |
| 81 | + |
| 82 | +function roots( f::Fun{C,TT} ) where {C<:Chebyshev,TT<:Union{Float64,ComplexF64}} |
| 83 | +# FIND THE ROOTS OF AN IFUN. |
| 84 | + if iszero(f) |
| 85 | + throw(ArgumentError("Tried to take roots of a zero function.")) |
| 86 | + end |
| 87 | + |
| 88 | + d = domain(f) |
| 89 | + c = f.coefficients |
| 90 | + vscale = maximum(abs,values(f)) |
| 91 | + |
| 92 | + hscale = max(abs(leftendpoint(d)), abs(rightendpoint(d)) ) |
| 93 | + htol = eps(2000.)*max(hscale, 1) # TODO: choose tolerance better |
| 94 | + |
| 95 | + |
| 96 | + cvscale=c./vscale |
| 97 | + r = rootsunit_coeffs(cvscale, Float64(htol)) |
| 98 | + |
| 99 | + # Check endpoints, as these are prone to inaccuracy |
| 100 | + # which can be deadly. |
| 101 | + if (isempty(r) || !isapprox(last(r),1.)) && abs(sum(cvscale)) < htol |
| 102 | + push!(r,1.) |
| 103 | + end |
| 104 | + if (isempty(r) || !isapprox(first(r),-1.)) && abs(alternatingsum(cvscale)) < htol |
| 105 | + insert!(r,1,-1.) |
| 106 | + end |
| 107 | + |
| 108 | + |
| 109 | + # Map roots from [-1,1] to domain of f: |
| 110 | + return fromcanonical.(Ref(d),r) |
| 111 | +end |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | +function colleague_matrix( c::Vector{T} ) where T<:Number |
| 116 | +#TODO: This is command isn't typed correctly |
| 117 | +# COMPUTE THE ROOTS OF A LOW DEGREE POLYNOMIAL BY USING THE COLLEAGUE MATRIX: |
| 118 | + c = chop(c,0) # prune exact zeros |
| 119 | + n = length(c) - 1 |
| 120 | + A=zeros(T,n,n) |
| 121 | + |
| 122 | + for k=1:n-1 |
| 123 | + A[k+1,k]=0.5 |
| 124 | + A[k,k+1]=0.5 |
| 125 | + end |
| 126 | + for k=1:n |
| 127 | + A[1,end-k+1]-=0.5*c[k]/c[end] |
| 128 | + end |
| 129 | + A[n,n-1]=one(T) |
| 130 | + |
| 131 | + # TODO: can we speed things up because A is lower-Hessenberg |
| 132 | + # Standard colleague matrix (See [Good, 1961]): |
| 133 | + A |
| 134 | +end |
| 135 | + |
| 136 | + |
| 137 | +function colleague_balance!(M) |
| 138 | + n=size(M,1) |
| 139 | + if n<2 |
| 140 | + return M |
| 141 | + end |
| 142 | + |
| 143 | + d=1+abs(M[1,2]) |
| 144 | + M[1,2]/=d;M[2,1]*=d |
| 145 | + |
| 146 | + for k=3:n |
| 147 | + M[k-1,k]*=d;M[k,k-1]/=d |
| 148 | + d+=abs(M[1,k]) |
| 149 | + M[1,k]/=d;M[k-1,k]/=d;M[k,k-1]*=d |
| 150 | + end |
| 151 | + M |
| 152 | +end |
| 153 | + |
| 154 | + |
| 155 | +# colleague_eigvals( c::Vector )=hesseneigvals(colleague_balance!(colleague_matrix(c))) |
| 156 | + |
| 157 | +colleague_eigvals( c::Vector )=eigvals(colleague_matrix(c)) |
| 158 | + |
| 159 | +function PruneOptions( r, htol::Float64 ) |
| 160 | +# ONLY KEEP ROOTS IN THE INTERVAL |
| 161 | + |
| 162 | + # Remove dangling imaginary parts: |
| 163 | + r = real( r[ abs.(imag.(r)) .< htol ] ) |
| 164 | + # Keep roots inside [-1 1]: |
| 165 | + r = sort( r[ abs.(r) .<= 1+htol ] ) |
| 166 | + # Put roots near ends onto the domain: |
| 167 | + r = min.( max.( r, -1 ), 1) |
| 168 | + |
| 169 | + # Return the pruned roots: |
| 170 | + return r |
| 171 | +end |
| 172 | + |
| 173 | +rootsunit_coeffs(c::Vector{T}, htol::Float64) where {T<:Number} = |
| 174 | + rootsunit_coeffs(c,htol,ClenshawPlan(T,Chebyshev(),length(c),length(c))) |
| 175 | +function rootsunit_coeffs(c::Vector{T}, htol::Float64,clplan::ClenshawPlan{S,T}) where {S,T<:Number} |
| 176 | +# Computes the roots of the polynomial given by the coefficients c on the unit interval. |
| 177 | + |
| 178 | + |
| 179 | + # If recursive subdivision is used, then subdivide [-1,1] into [-1,splitPoint] and [splitPoint,1]. |
| 180 | + splitPoint = -0.004849834917525 |
| 181 | + |
| 182 | + # Simplify the coefficients by chopping off the tail: |
| 183 | + nrmc=norm(c, 1) |
| 184 | + @assert nrmc > 0 # There's an error if we make it this far |
| 185 | + c = chop(c,eps()*nrmc) |
| 186 | + n = length(c) |
| 187 | + |
| 188 | + if n == 0 |
| 189 | + |
| 190 | + # EMPTY FUNCTION |
| 191 | + r = Float64[] |
| 192 | + |
| 193 | + elseif n == 1 |
| 194 | + |
| 195 | + # CONSTANT FUNCTION |
| 196 | + r = ( c[1] == 0.0 ) ? Float64[0.0] : Float64[] |
| 197 | + |
| 198 | + elseif n == 2 |
| 199 | + |
| 200 | + # LINEAR POLYNOMIAL |
| 201 | + r1 = -c[1]/c[2]; |
| 202 | + r = ( (abs(imag(r1))>htol) | (abs(real(r1))>(1+htol)) ) ? Float64[] : Float64[max(min(real(r1),1),-1)] |
| 203 | + |
| 204 | + elseif n <= 70 |
| 205 | + |
| 206 | + # COLLEAGUE MATRIX |
| 207 | + # The recursion subdividing below will keep going until we have a piecewise polynomial |
| 208 | + # of degree at most 50 and we likely end up here for each piece. |
| 209 | + |
| 210 | + # Adjust the coefficients for the colleague matrix |
| 211 | + # Prune roots depending on preferences: |
| 212 | + r = PruneOptions( colleague_eigvals(c), htol )::Vector{Float64} |
| 213 | + else |
| 214 | + |
| 215 | + # RECURSIVE SUBDIVISION: |
| 216 | + # If n > 50, then split the interval [-1,1] into [-1,splitPoint], [splitPoint,1]. |
| 217 | + # Find the roots of the polynomial on each piece and then concatenate. Recurse if necessary. |
| 218 | + |
| 219 | + # Evaluate the polynomial at Chebyshev grids on both intervals: |
| 220 | + #(clenshaw! overwrites points, which only makes sense if c is real) |
| 221 | + |
| 222 | + v1 = if isa(c, Vector{Float64}) |
| 223 | + clenshaw!(c, convert(Vector, points(-1..splitPoint,n)), clplan) |
| 224 | + else |
| 225 | + clenshaw(c, points(-1..splitPoint,n),clplan) |
| 226 | + end |
| 227 | + v2 = if isa(c, Vector{Float64}) |
| 228 | + clenshaw!(c, convert(Vector, points(splitPoint..1 ,n)), clplan) |
| 229 | + else |
| 230 | + clenshaw(c, points(splitPoint..1 ,n),clplan) |
| 231 | + end |
| 232 | + |
| 233 | + # Recurse (and map roots back to original interval): |
| 234 | + p = plan_chebyshevtransform( v1 ) |
| 235 | + r = [ (splitPoint - 1)/2 .+ (splitPoint + 1)/2 .* rootsunit_coeffs( p*v1, 2*htol,clplan) ; |
| 236 | + (splitPoint + 1)/2 .+ (1 - splitPoint)/2 .* rootsunit_coeffs( p*v2, 2*htol,clplan) ] |
| 237 | + |
| 238 | + end |
| 239 | + |
| 240 | + # Return the roots: |
| 241 | + r |
| 242 | +end |
| 243 | + |
| 244 | + |
| 245 | +extremal_args(f::Fun{S}) where {S<:ContinuousSpace} = cat(1,[extremal_args(fp) for fp in components(f)]..., dims=1) |
| 246 | + |
| 247 | +for op in (:(maximum),:(minimum)) |
| 248 | + @eval begin |
| 249 | + |
| 250 | + $op(f::Fun{ContinuousSpace{T,R},T}) where {T<:Real,R<:Real} = |
| 251 | + $op(map($op,components(f))) |
| 252 | + $op(::typeof(abs), f::Fun{ContinuousSpace{T,R},T}) where {T<:Real,R<:Real} = |
| 253 | + $op(abs, map(g -> $op(abs, g),components(f))) |
| 254 | + end |
| 255 | +end |
| 256 | + |
| 257 | +extrema(f::Fun{ContinuousSpace{T,R},T}) where {T<:Real,R<:Real} = |
| 258 | + mapreduce(extrema,(x,y)->extrema([x...;y...]),components(f)) |
| 259 | + |
| 260 | + |
| 261 | +function roots(f::Fun{P}) where P<:ContinuousSpace |
| 262 | + rts=mapreduce(roots,vcat,components(f)) |
| 263 | + k=1 |
| 264 | + while k < length(rts) |
| 265 | + if isapprox(rts[k],rts[k+1]) |
| 266 | + rts=rts[[1:k;k+2:end]] |
| 267 | + else |
| 268 | + k+=1 |
| 269 | + end |
| 270 | + end |
| 271 | + |
| 272 | + rts |
| 273 | +end |
| 274 | + |
| 275 | + |
| 276 | + |
0 commit comments