Skip to content

Commit a1d1393

Browse files
authored
Add tests for functions of Fun (#497)
* Add tests for functions of Fun * Special function tests
1 parent 1b1de29 commit a1d1393

File tree

3 files changed

+313
-0
lines changed

3 files changed

+313
-0
lines changed

test/AFOP/PolynomialSpaces.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,5 +750,7 @@ function Integral(d::IntervalOrSegment, n::Number)
750750
Integral(Ultraspherical(1,d), n)
751751
end
752752

753+
include("roots.jl")
754+
753755

754756
end

test/AFOP/roots.jl

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
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+

test/PolynomialSpacesTests.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ using BandedMatrices
99
using IntervalSets: AbstractInterval
1010
import IntervalSets: endpoints, closedendpoints
1111

12+
using SpecialFunctions
13+
1214
include("AFOP/PolynomialSpaces.jl")
1315
using .PolynomialSpaces
1416

@@ -387,4 +389,37 @@ Base.:(==)(a::UniqueInterval, b::UniqueInterval) = (@assert a.parentinterval ==
387389
end
388390
end
389391

392+
@testset "Special Functions" begin
393+
g = Fun(x ->x^2, 0.5..1)
394+
@testset for f in [
395+
sinpi, cospi, sin, cos, cosh, exp2, exp10, log2, log10, csc, sec,
396+
cot, acot, sinh, csch, asinh, acsch,
397+
sech, tanh, coth,
398+
sinc, cosc, log1p, log, expm1, tan,
399+
cbrt, sqrt, abs, abs2, sign, inv,
400+
angle]
401+
402+
@test f(g)(0.75) f((0.75)^2)
403+
end
404+
@test abs2(Fun())(-0.4) 0.4^2
405+
@test sign(Fun())(-0.4) -1
406+
407+
@test argmax(Fun()) == 1
408+
@test argmin(Fun()) == -1
409+
@test maximum(Fun()) == 1
410+
@test minimum(Fun()) == -1
411+
@test extrema(Fun()) == (-1,1)
412+
413+
@testset "SpecialFunctions" begin
414+
g = Fun(0.1..0.4)
415+
for f in [erfcx, dawson, erf, erfi,
416+
airyai, airybi, airyaiprime, airybiprime,
417+
erfcinv, eta, gamma, invdigamma, digamma,
418+
trigamma,
419+
]
420+
@test f(g)(0.3) f(0.3)
421+
end
422+
end
423+
end
424+
390425
end # module

0 commit comments

Comments
 (0)