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

[WIP / RFC] Configuration mechanism for directed rounding and fast powers #388

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fa7aab4
Add configuration.jl
dpsanders May 31, 2020
ce61769
IntervalRounding -> DirectedRounding
dpsanders Jun 6, 2020
4f1a4b0
Move power functions to powers.jl
dpsanders Jun 6, 2020
0a5e2ac
Add powers.jl
dpsanders Jun 6, 2020
089a7e6
De-export setrounding and rename to set_directed_rounding
dpsanders Jun 6, 2020
c6feb3f
Move sqrt and cbrt back to functions.jl. Add PowerType dispatch for p…
dpsanders Jun 6, 2020
81134b9
Configuration seems to be working
dpsanders Jun 6, 2020
cfddaef
Make powers non-recursive (incorporates nonrecursive_powers branch)
dpsanders Jun 7, 2020
044c947
Bump version of FastRounding in Project.toml
dpsanders Jun 7, 2020
524cd4c
Add Base annotation
dpsanders Jun 7, 2020
de103ee
Import power_by_squaring
dpsanders Jun 7, 2020
b2fb8ec
Move configuration call to init
dpsanders Jun 7, 2020
7a8838c
Fix power ambiguity
dpsanders Jun 7, 2020
c3a07c6
Move negative power check
dpsanders Jun 7, 2020
6e9ed32
Try some inlining
dpsanders Jun 7, 2020
1e714b5
Fix name mismatch
dpsanders Jun 7, 2020
42f6c74
More inlining
dpsanders Jun 7, 2020
8989bb3
Typos
dpsanders Jun 7, 2020
d3aa7c0
Fix warn -> @warn
dpsanders Jun 9, 2020
73fc001
Fix (apparently) issues with precompilation
dpsanders Jun 9, 2020
d585a4d
Comment out unnecessary lines
dpsanders Jun 9, 2020
997e32c
Fix rational and real powers
dpsanders Jun 11, 2020
3c00c99
Simplify real power
dpsanders Jun 13, 2020
d35b415
Reinstate integer power
dpsanders Jun 22, 2020
5751634
Move configuration.jl into intervals; add call to configure
dpsanders Jun 22, 2020
b669b09
Update deps
dpsanders Jun 21, 2021
57f401f
Fix power_by_squaring so that (-Inf..(-1e300))^3 is correct
Sep 17, 2021
d40067e
Fix power_by_squaring
dpsanders Feb 11, 2022
9e36c14
Fix power_by_squaring
dpsanders Feb 11, 2022
afdd6b6
Merge branch 'master' into dps/config
lucaferranti Jun 1, 2022
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
7 changes: 5 additions & 2 deletions src/IntervalArithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import Base:
abs, abs2,
show,
isinteger, setdiff,
parse, hash
parse, hash,
power_by_squaring

import Base: # for IntervalBox
broadcast, length,
Expand Down Expand Up @@ -80,7 +81,7 @@ export

export showfull

import Base: rounding, setrounding, setprecision
import Base: setprecision
Copy link
Member

Choose a reason for hiding this comment

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

Since we are getting rid of rounding and setrounding, shouldn't we amend (in parallel) the docs? Similarly, we should add the corresponding docs of IntervalArithmetic.set_directed_rounding




Expand All @@ -106,6 +107,8 @@ function __init__()

setprecision(Interval, 256) # set up pi
setprecision(Interval, Float64)

# configure!(directed_rounding=:tight, powers=:fast)
end


Expand Down
57 changes: 57 additions & 0 deletions src/intervals/configuration.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
Holds configuration information for the package:

- `directed_rounding`:

Which method to use to implement **directed rounding**.
Possible values are:

- `:tight`: correct rounding using error-free arithmetic and CRlibm. Gives
the tightest resulting intervals, with a width of 1 ulp for each basic operation
- `:fast`: uses `prevfloat` and `nextfloat` to give a faster result, with width 2 ulps
- `:none`: no rounding; does *not* guarantee that the results are correct.

- `powers`:
Method to implement powers. Possible values are
- `:tight`: gives tightest possible result by using `BigFloat` internally, and hence is slow
- `:fast`: uses repeated multiplication based on `Base.power_by_squaring`, and hence is faster but gives slightly looser results
"""

const configuration = Dict(:directed_rounding => :tight,
:powers => :fast)
Comment on lines +20 to +21
Copy link
Member

Choose a reason for hiding this comment

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

I think we should have as default configuration that both defaults are :tight; It seems to me a bit unnatural to have (:directed_rounding => :tight and :powers => :fast. Note that tests change to this mode.


const allowed_values = Dict(:directed_rounding => (:tight, :fast, :none),
:powers => (:tight, :fast))

function configure!(; directed_rounding=nothing, powers=nothing)
Copy link
Member

Choose a reason for hiding this comment

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

What about fixing the defaults here, instead of nothings ...?

Copy link
Member

Choose a reason for hiding this comment

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

Rebump question...

if directed_rounding != nothing
if directed_rounding ∉ allowed_values[:directed_rounding]
throw(ArgumentError("directed_rounding must be one of $(allowed_values[:directed_rounding])"))
end

configuration[:directed_rounding] = directed_rounding

# name mismatch
if directed_rounding == :fast
directed_rounding = :accurate
end
Comment on lines +34 to +37
Copy link
Member

Choose a reason for hiding this comment

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

We should only use one of them, say :accurate...


set_directed_rounding(directed_rounding)
end

if powers != nothing
if powers ∉ allowed_values[:powers]
throw(ArgumentError("powers must be one of $(allowed_values[:powers])"))
end

configuration[:powers] = powers

set_power_type(powers)
end

return configuration

end


configure!(directed_rounding=:tight, powers=:fast)
258 changes: 0 additions & 258 deletions src/intervals/functions.jl
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
# This file is part of the IntervalArithmetic.jl package; MIT licensed

## Powers

# CRlibm does not contain a correctly-rounded ^ function for Float64
# Use the BigFloat version from MPFR instead, which is correctly-rounded:

# Write explicitly like this to avoid ambiguity warnings:
for T in (:Integer, :Float64, :BigFloat, :Interval)
@eval ^(a::Interval{Float64}, x::$T) = atomic(Interval{Float64}, bigequiv(a)^x)
end
^(a::Interval{Float32}, x::Interval) = atomic(Interval{Float32}, bigequiv(a)^x)

# Integer power:

# overwrite new behaviour for small integer powers from
# https://github.com/JuliaLang/julia/pull/24240:

Base.literal_pow(::typeof(^), x::Interval{T}, ::Val{p}) where {T,p} = x^p


Base.eltype(x::Interval{T}) where {T<:Real} = Interval{T}

Expand All @@ -36,206 +18,6 @@ Float64
numtype(::Interval{T}) where {T<:Real} = T


function ^(a::Interval{BigFloat}, n::Integer)
isempty(a) && return a
n == 0 && return one(a)
n == 1 && return a
# n == 2 && return sqr(a)
n < 0 && a == zero(a) && return emptyinterval(a)

T = BigFloat

if isodd(n) # odd power
isentire(a) && return a
if n > 0
a.lo == 0 && return @round(0, a.hi^n)
a.hi == 0 && return @round(a.lo^n, 0)
return @round(a.lo^n, a.hi^n)
else
if a.lo ≥ 0
a.lo == 0 && return @round(a.hi^n, Inf)
return @round(a.hi^n, a.lo^n)

elseif a.hi ≤ 0
a.hi == 0 && return @round(-Inf, a.lo^n)
return @round(a.hi^n, a.lo^n)
else
return entireinterval(a)
end
end

else # even power
if n > 0
if a.lo ≥ 0
return @round(a.lo^n, a.hi^n)
elseif a.hi ≤ 0
return @round(a.hi^n, a.lo^n)
else
return @round(mig(a)^n, mag(a)^n)
end

else
if a.lo ≥ 0
return @round(a.hi^n, a.lo^n)
elseif a.hi ≤ 0
return @round(a.lo^n, a.hi^n)
else
return @round(mag(a)^n, mig(a)^n)
end
end
end
end

function sqr(a::Interval{T}) where T<:Real
return a^2
# isempty(a) && return a
# if a.lo ≥ zero(T)
# return @round(a.lo^2, a.hi^2)
#
# elseif a.hi ≤ zero(T)
# return @round(a.hi^2, a.lo^2)
# end
#
# return @round(mig(a)^2, mag(a)^2)
end

^(a::Interval{BigFloat}, x::AbstractFloat) = a^big(x)

# Floating-point power of a BigFloat interval:
function ^(a::Interval{BigFloat}, x::BigFloat)

domain = Interval{BigFloat}(0, Inf)

if a == zero(a)
a = a ∩ domain
x > zero(x) && return zero(a)
return emptyinterval(a)
end

isinteger(x) && return a^(round(Int, x))
x == 0.5 && return sqrt(a)

a = a ∩ domain
(isempty(x) || isempty(a)) && return emptyinterval(a)

xx = atomic(Interval{BigFloat}, x)

# @round() can't be used directly, because both arguments may
# Inf or -Inf, which throws an error
# lo = @round(a.lo^xx.lo, a.lo^xx.lo)
lolod = @round_down(a.lo^xx.lo)
lolou = @round_up(a.lo^xx.lo)
lo = (lolod == Inf || lolou == -Inf) ?
wideinterval(lolod) : Interval(lolod, lolou)

# lo1 = @round(a.lo^xx.hi, a.lo^xx.hi)
lohid = @round_down(a.lo^xx.hi)
lohiu = @round_up(a.lo^xx.hi)
lo1 = (lohid == Inf || lohiu == -Inf) ?
wideinterval(lohid) : Interval(lohid, lohiu)

# hi = @round(a.hi^xx.lo, a.hi^xx.lo)
hilod = @round_down(a.hi^xx.lo)
hilou = @round_up(a.hi^xx.lo)
hi = (hilod == Inf || hilou == -Inf) ?
wideinterval(hilod) : Interval(hilod, hilou)

# hi1 = @round(a.hi^xx.hi, a.hi^xx.hi)
hihid = @round_down(a.hi^xx.hi)
hihiu = @round_up(a.hi^xx.hi)
hi1 = (hihid == Inf || hihiu == -Inf) ?
wideinterval(hihid) : Interval(hihid, hihiu)

lo = hull(lo, lo1)
hi = hull(hi, hi1)

return hull(lo, hi)
end

function ^(a::Interval{Rational{T}}, x::AbstractFloat) where T<:Integer
a = Interval(a.lo.num/a.lo.den, a.hi.num/a.hi.den)
a = a^x
atomic(Interval{Rational{T}}, a)
end

# Rational power
function ^(a::Interval{T}, x::Rational) where T
domain = Interval{T}(0, Inf)

p = x.num
q = x.den

isempty(a) && return emptyinterval(a)
x == 0 && return one(a)

if a == zero(a)
x > zero(x) && return zero(a)
return emptyinterval(a)
end

x == (1//2) && return sqrt(a)

if x >= 0
if a.lo ≥ 0
isinteger(x) && return a ^ Int64(x)
a = @biginterval(a)
ui = convert(Culong, q)
low = BigFloat()
high = BigFloat()
ccall((:mpfr_rootn_ui, :libmpfr) , Int32 , (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode) , low , a.lo , ui, MPFRRoundDown)
ccall((:mpfr_rootn_ui, :libmpfr) , Int32 , (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode) , high , a.hi , ui, MPFRRoundUp)
b = interval(low, high)
b = convert(Interval{T}, b)
return b^p
end

if a.lo < 0 && a.hi ≥ 0
isinteger(x) && return a ^ Int64(x)
a = a ∩ Interval{T}(0, Inf)
a = @biginterval(a)
ui = convert(Culong, q)
low = BigFloat()
high = BigFloat()
ccall((:mpfr_rootn_ui, :libmpfr) , Int32 , (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode) , low , a.lo , ui, MPFRRoundDown)
ccall((:mpfr_rootn_ui, :libmpfr) , Int32 , (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode) , high , a.hi , ui, MPFRRoundUp)
b = interval(low, high)
b = convert(Interval{T}, b)
return b^p
end

if a.hi < 0
isinteger(x) && return a ^ Int64(x)
return emptyinterval(a)
end

end

if x < 0
return inv(a^(-x))
end

end

# Interval power of an interval:
function ^(a::Interval{BigFloat}, x::Interval)
T = BigFloat
domain = Interval{T}(0, Inf)

a = a ∩ domain

(isempty(x) || isempty(a)) && return emptyinterval(a)

lo1 = a^x.lo
lo2 = a^x.hi
lo1 = hull(lo1, lo2)

hi1 = a^x.lo
hi2 = a^x.hi
hi1 = hull(hi1, hi2)

hull(lo1, hi1)
end


function sqrt(a::Interval{T}) where T
domain = Interval{T}(0, Inf)
Expand All @@ -248,46 +30,6 @@ end



"""
pow(x::Interval, n::Integer)

A faster implementation of `x^n`, currently using `power_by_squaring`.
`pow(x, n)` will usually return an interval that is slightly larger than that calculated by `x^n`, but is guaranteed to be a correct
enclosure when using multiplication with correct rounding.
"""
function pow(x::Interval, n::Integer) # fast integer power
if n < 0
return 1/pow(x, -n)
end

isempty(x) && return x

if iseven(n) && 0 ∈ x

return hull(zero(x),
hull(Base.power_by_squaring(Interval(mig(x)), n),
Base.power_by_squaring(Interval(mag(x)), n))
)

else

return hull( Base.power_by_squaring(Interval(x.lo), n),
Base.power_by_squaring(Interval(x.hi), n) )

end

end

function pow(x::Interval, y::Real) # fast real power, including for y an Interval

isempty(x) && return x
isinteger(y) && return pow(x, Int(y.lo))
return exp(y * log(x))

end




for f in (:exp, :expm1)
@eval begin
Expand Down
2 changes: 2 additions & 0 deletions src/intervals/intervals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ end
include("special.jl")
include("macros.jl")
include("rounding_macros.jl")
include("powers.jl")
include("rounding.jl")
include("configuration.jl")
include("conversion.jl")
include("precision.jl")
include("set_operations.jl")
Expand Down
Loading