Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add evalpoly function #32753

Merged
merged 75 commits into from
Nov 21, 2019
Merged
Show file tree
Hide file tree
Changes from 73 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
e21b5e4
implemented @big macro
MasonProtter Jan 20, 2019
6846290
added News entry
MasonProtter Jan 20, 2019
c9726de
remove whitespace
MasonProtter Jan 20, 2019
8cd2dfb
added tests
MasonProtter Jan 20, 2019
ca0dc3c
Add horner function
MasonProtter Jul 31, 2019
e2230d6
remove whitespace
MasonProtter Jul 31, 2019
23d89f0
Add tests to make sure @horner is the same as horner
MasonProtter Jul 31, 2019
6e97592
update horner function doc.
MasonProtter Jul 31, 2019
76293c1
whitespace fixes
MasonProtter Jul 31, 2019
eec71c8
More whitespace fixes
MasonProtter Aug 1, 2019
9388532
Merge https://github.com/JuliaLang/julia into patch-2
Aug 10, 2019
48888c4
replace horner func with evalpoly on real and complex
MasonProtter Sep 6, 2019
00c2829
Merge https://github.com/JuliaLang/julia into patch-2
MasonProtter Sep 6, 2019
7394731
added news and export statements
MasonProtter Sep 6, 2019
e0651ce
remove whitespace
MasonProtter Sep 6, 2019
482546f
fix broken evalpoly(x, p...)
MasonProtter Sep 7, 2019
87b487b
fix whitespace yet again
MasonProtter Sep 7, 2019
3a56c66
fix spurious [@]evalpoly(z, p) behaviour
MasonProtter Sep 9, 2019
f742648
test fixed spurious [@]evalpoly(z, p) behaviour
MasonProtter Sep 9, 2019
73a783e
change evalpoly API to take a tuple of coefficients
MasonProtter Sep 10, 2019
12eae90
fix performance regression due to promote
MasonProtter Sep 10, 2019
cf640b3
Fix borked earlier commit
MasonProtter Sep 10, 2019
a7e3ea4
fix docstrings
MasonProtter Sep 10, 2019
b5c6d98
fix docstrings again
MasonProtter Sep 10, 2019
a880e57
Cite Knuth
MasonProtter Sep 10, 2019
aabc9f8
remove @horner
MasonProtter Sep 10, 2019
d1786dc
remove whistspace
MasonProtter Sep 10, 2019
60e7d72
undo remove @horner
MasonProtter Sep 10, 2019
6d90176
fix spelling and ref in docstring
MasonProtter Sep 12, 2019
0f3ba27
fix spelling again
MasonProtter Sep 12, 2019
a1b0126
Merge https://github.com/JuliaLang/julia into patch-2
MasonProtter Sep 12, 2019
9a318fc
Merge branch 'master' into patch-2
MasonProtter Sep 13, 2019
23cbf38
Merge branch 'master' into patch-2
MasonProtter Sep 18, 2019
438c613
fix merge conflict
MasonProtter Sep 18, 2019
7529098
Add horner function
MasonProtter Jul 31, 2019
637d88d
remove whitespace
MasonProtter Jul 31, 2019
77da69e
Add tests to make sure @horner is the same as horner
MasonProtter Jul 31, 2019
39fb687
update horner function doc.
MasonProtter Jul 31, 2019
3bccf17
whitespace fixes
MasonProtter Jul 31, 2019
0ee5b0b
More whitespace fixes
MasonProtter Aug 1, 2019
33cf846
replace horner func with evalpoly on real and complex
MasonProtter Sep 6, 2019
2064b7e
added news and export statements
MasonProtter Sep 6, 2019
f8fc94a
remove whitespace
MasonProtter Sep 6, 2019
72136ef
fix broken evalpoly(x, p...)
MasonProtter Sep 7, 2019
04cceb8
fix whitespace yet again
MasonProtter Sep 7, 2019
8d4f749
fix spurious [@]evalpoly(z, p) behaviour
MasonProtter Sep 9, 2019
6e55880
test fixed spurious [@]evalpoly(z, p) behaviour
MasonProtter Sep 9, 2019
defebdc
change evalpoly API to take a tuple of coefficients
MasonProtter Sep 10, 2019
cfb65b1
fix performance regression due to promote
MasonProtter Sep 10, 2019
b2cd4ea
Fix borked earlier commit
MasonProtter Sep 10, 2019
24e93b5
fix docstrings
MasonProtter Sep 10, 2019
b7eb231
fix docstrings again
MasonProtter Sep 10, 2019
a9361c5
Cite Knuth
MasonProtter Sep 10, 2019
b234284
remove @horner
MasonProtter Sep 10, 2019
6409216
remove whistspace
MasonProtter Sep 10, 2019
068b653
undo remove @horner
MasonProtter Sep 10, 2019
5545a25
fix spelling and ref in docstring
MasonProtter Sep 12, 2019
3881f57
fix spelling again
MasonProtter Sep 12, 2019
f14b180
fix merge conflict
MasonProtter Sep 18, 2019
9373593
remove erroneous change from
MasonProtter Sep 19, 2019
87fa422
Update util.jl
MasonProtter Sep 19, 2019
1487614
Remove erroneous change
MasonProtter Sep 19, 2019
4992898
Merge branch 'master' into patch-2
MasonProtter Nov 15, 2019
bcf7ba1
add optional @generated and evalpoly(x, ::Vector)
MasonProtter Nov 15, 2019
6036c41
test evalpoly(z, ::Vector)
MasonProtter Nov 15, 2019
b619c87
Make @horner and @evalpoly call the evalpoly function
MasonProtter Nov 15, 2019
041515f
fix whitespace
MasonProtter Nov 15, 2019
c198725
fix @evalpoly and @horner
MasonProtter Nov 16, 2019
dcff580
fix @evalpoly and @horner more
MasonProtter Nov 16, 2019
6a0c8e7
typo
MasonProtter Nov 16, 2019
ff1c013
Docfix
MasonProtter Nov 17, 2019
f027680
change `esc` logic
MasonProtter Nov 17, 2019
a3ebb0c
Remove outdated test
MasonProtter Nov 18, 2019
a6bc7b4
Make @horner always use Horner's rule
MasonProtter Nov 18, 2019
0aa9f2c
fix docstring
MasonProtter Nov 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ New library functions
* `readdir` output is now guaranteed to be sorted. The `sort` keyword allows opting out of sorting to get names in OS-native order ([#33542]).
* The new `only(x)` function returns the one-and-only element of a collection `x`, and throws an `ArgumentError` if `x` contains zero or multiple elements. ([#33129])
* `takewhile` and `dropwhile` have been added to the Iterators submodule ([#33437]).

* There is a now an `evalpoly` (generated) function meant to take the role of the `@evalpoly` macro. The function is just as efficient as the macro while giving added flexibility, so it should be preferred over `@evalpoly`. `evalpoly` takes a list of coefficients as a tuple, so where one might write `@evalpoly(x, p1, p2, p3)` one would instead write `evalpoly(x, (p1, p2, p3))`.

Standard library changes
------------------------
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export

# scalar math
@evalpoly,
evalpoly,
abs,
abs2,
acos,
Expand Down
160 changes: 131 additions & 29 deletions base/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export sin, cos, sincos, tan, sinh, cosh, tanh, asin, acos, atan,
cbrt, sqrt, significand,
hypot, max, min, minmax, ldexp, frexp,
clamp, clamp!, modf, ^, mod2pi, rem2pi,
@evalpoly
@evalpoly, evalpoly

import .Base: log, exp, sin, cos, tan, sinh, cosh, tanh, asin,
acos, atan, asinh, acosh, atanh, sqrt, log2, log10,
Expand Down Expand Up @@ -81,18 +81,140 @@ function clamp!(x::AbstractArray, lo, hi)
x
end


"""
evalpoly(x, p::Tuple)
Evaluate the polynomial ``\\sum_k p[k] x^{k-1}`` for the coefficients `p[1]`, `p[2]`, ...;
that is, the coefficients are given in ascending order by power of `x`. This function
generates efficient code using Horner's method with loops unrolled at compile time.
# Example
```jldoctest
julia> evalpoly(2, (1, 2, 3))
17
```
"""
function evalpoly(x, p::Tuple)
MasonProtter marked this conversation as resolved.
Show resolved Hide resolved
if @generated
N = length(p.parameters)
ex = :(p[end])
for i in N-1:-1:1
ex = :(muladd(x, $ex, p[$i]))
end
ex
else
_evalpoly(x, p)
end
end

"""
evalpoly(x, p::AbstractVector)
Evaluate the polynomial ``\\sum_k p[k] x^{k-1}`` for the coefficients `p[1]`, `p[2]`, ...;
that is, the coefficients are given in ascending order by power of `x`. This function
uses Horner's method *without* loops unrolled at compile time. Use this method when the
number of coefficients is not known at compile time.
# Example
```jldoctest
julia> evalpoly(2, [1, 2, 3])
17
```
"""
evalpoly(x, p::AbstractVector) = _evalpoly(x, p)
Copy link
Contributor Author

@MasonProtter MasonProtter Nov 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose to restrict this method to AbstractVector, but not _evalpoly(x, p) (same for the complex case). I'm not sure if this is the preferred way to go about this, but it seemed like the most straightforward way to do this with minimal code duplication.


function _evalpoly(x, p)
N = length(p)
ex = p[end]
for i in N-1:-1:1
ex = muladd(x, ex, p[i])
end
ex
end

"""
evalpoly(z::Complex, p::Tuple)
Evaluate the polynomial ``\\sum_k p[k] z^{k-1}`` for the coefficients `p[1]`, `p[2]`, ...;
that is, the coefficients are given in ascending order by power of `z`. This function
generates efficient code using a Goertzel-like algorithm specialized for complex arguments
with loops unrolled at compile time.
MasonProtter marked this conversation as resolved.
Show resolved Hide resolved
The Goertzel-like algorthim is described in Knuth's Art of Computer Programming,
Volume 2: Seminumerical Algorithms, Sec. 4.6.4.
# Example
```jldoctest
julia> evalpoly(2 + im, (1, 2, 3))
14 + 14im
```
"""
function evalpoly(z::Complex, p::Tuple)
if @generated
N = length(p.parameters)
a = :(p[end])
b = :(p[end-1])
as = []
for i in N-2:-1:1
ai = Symbol("a", i)
push!(as, :($ai = $a))
a = :(muladd(r, $ai, $b))
b = :(p[$i] - s * $ai)
end
ai = :a0
push!(as, :($ai = $a))
C = Expr(:block,
:(x = real(z)),
:(y = imag(z)),
:(r = x + x),
:(s = muladd(x, x, y*y)),
as...,
:(muladd($ai, z, $b)))
else
_evalpoly(z, p)
end
end
evalpoly(z::Complex, p::Tuple{<:Any}) = p[1]


"""
evalpoly(z::Complex, p::AbstractVector)
Evaluate the polynomial ``\\sum_k p[k] z^{k-1}`` for the coefficients `p[1]`, `p[2]`, ...;
that is, the coefficients are given in ascending order by power of `z`. This function
generates efficient code using a Goertzel-like algorithm specialized for complex arguments
,*without* loops unrolled at compile time. Use this method when the number of coefficients
is not known at compile time.
The Goertzel-like algorthim is described in Knuth's Art of Computer Programming,
Volume 2: Seminumerical Algorithms, Sec. 4.6.4.
# Example
```jldoctest
julia> evalpoly(2 + im, (1, 2, 3))
14 + 14im
```
"""
evalpoly(z::Complex, p::AbstractVector) = _evalpoly(z, p)

function _evalpoly(z::Complex, p)
length(p) == 1 && return p[1]
N = length(p)
a = p[end]
b = p[end-1]

x = real(z)
y = imag(z)
r = 2x
s = muladd(x, x, y*y)
for i in N-2:-1:1
ai = a
a = muladd(r, ai, b)
b = p[i] - s * ai
end
ai = a
muladd(ai, z, b)
end

"""
@horner(x, p...)

Evaluate `p[1] + x * (p[2] + x * (....))`, i.e. a polynomial via Horner's rule.
"""
macro horner(x, p...)
ex = esc(p[end])
for i = length(p)-1:-1:1
ex = :(muladd(t, $ex, $(esc(p[i]))))
end
ex = quote local r = $ex end # structure this to add exactly one line number node for the macro
return Expr(:block, :(local t = $(esc(x))), ex, :r)
xesc, pesc = esc(x), esc.(p)
:(evalpoly($xesc, ($(pesc...),)))
end

# Evaluate p[1] + z*p[2] + z^2*p[3] + ... + z^(n-1)*p[n]. This uses
Expand Down Expand Up @@ -121,28 +243,8 @@ julia> @evalpoly(2, 1, 1, 1)
```
"""
macro evalpoly(z, p...)
a = :($(esc(p[end])))
b = :($(esc(p[end-1])))
as = []
for i = length(p)-2:-1:1
ai = Symbol("a", i)
push!(as, :($ai = $a))
a = :(muladd(r, $ai, $b))
b = :($(esc(p[i])) - s * $ai) # see issue #15985 on fused mul-subtract
end
ai = :a0
push!(as, :($ai = $a))
C = Expr(:block,
:(x = real(tt)),
:(y = imag(tt)),
:(r = x + x),
:(s = muladd(x, x, y*y)),
as...,
:(muladd($ai, tt, $b)))
R = Expr(:macrocall, Symbol("@horner"), (), :tt, map(esc, p)...)
:(let tt = $(esc(z))
isa(tt, Complex) ? $C : $R
end)
zesc, pesc = esc(z), esc.(p)
:(evalpoly($zesc, ($(pesc...),)))
end

"""
Expand Down
27 changes: 20 additions & 7 deletions test/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -487,17 +487,30 @@ end

@testset "evalpoly" begin
@test @evalpoly(2,3,4,5,6) == 3+2*(4+2*(5+2*6)) == @evalpoly(2+0im,3,4,5,6)
@test let evalcounts=0
@evalpoly(begin
evalcounts += 1
4
end, 1,2,3,4,5)
evalcounts
end == 1
a0 = 1
a1 = 2
c = 3
@test @evalpoly(c, a0, a1) == 7
@test @evalpoly(1, 2) == 2
end

@testset "evalpoly real" begin
for x in -1.0:2.0, p1 in -3.0:3.0, p2 in -3.0:3.0, p3 in -3.0:3.0
evpm = @evalpoly(x, p1, p2, p3)
@test evalpoly(x, (p1, p2, p3)) == evpm
@test evalpoly(x, [p1, p2, p3]) == evpm
end
end

@testset "evalpoly complex" begin
for x in -1.0:2.0, y in -1.0:2.0, p1 in -3.0:3.0, p2 in -3.0:3.0, p3 in -3.0:3.0
z = x + im * y
evpm = @evalpoly(z, p1, p2, p3)
@test evalpoly(z, (p1, p2, p3)) == evpm
@test evalpoly(z, [p1, p2, p3]) == evpm
end
@test evalpoly(1+im, (2,)) == 2
@test evalpoly(1+im, [2,]) == 2
end

@testset "cis" begin
Expand Down