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

treat intervals more like the numbers they represent #181

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions src/IntervalArithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export ×, dot

import Base:
+, -, *, /, //, fma,
<, >, ==, !=, ⊆, ^, <=,
<, >, ==, !=, ⊆, ^, <=, isequal,
in, zero, one, eps, typemin, typemax, abs, abs2, real, min, max,
sqrt, exp, log, sin, cos, tan, inv,
exp2, exp10, log2, log10,
Expand All @@ -45,12 +45,12 @@ import Base: # for IntervalBox
import .Broadcast: broadcasted

export
AbstractInterval, Interval,
AbstractInterval, Interval, SetInterval, NumberInterval,
interval,
@interval, @biginterval, @floatinterval, @make_interval,
diam, radius, mid, mag, mig, hull,
emptyinterval, ∅, ∞, isempty, isinterior, isdisjoint, ⪽,
precedes, strictprecedes, ≺, ⊂, ⊃, ⊇, contains_zero,
precedes, strictprecedes, ≺, ⊂, ⊃, ⊇, contains_zero, isequal,
entireinterval, isentire, nai, isnai, isthin, iscommon, isatomic,
widen, inf, sup, bisect,
parameters, eps, dist,
Expand Down
8 changes: 3 additions & 5 deletions src/intervals/arithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@

Checks if the intervals `a` and `b` are equal.
"""
function ==(a::Interval, b::Interval)
function ==(a::SetInterval, b::SetInterval)
isempty(a) && isempty(b) && return true
a.lo == b.lo && a.hi == b.hi
end
!=(a::Interval, b::Interval) = !(a==b)


# Auxiliary functions: equivalent to </<=, but Inf <,<= Inf returning true
function islessprime(a::T, b::T) where T<:Real
Expand All @@ -22,14 +20,14 @@ function islessprime(a::T, b::T) where T<:Real
end

# Weakly less, \le, <=
function <=(a::Interval, b::Interval)
function <=(a::SetInterval, b::SetInterval)
isempty(a) && isempty(b) && return true
(isempty(a) || isempty(b)) && return false
(a.lo ≤ b.lo) && (a.hi ≤ b.hi)
end

# Strict less: <
function <(a::Interval, b::Interval)
function <(a::SetInterval, b::SetInterval)
isempty(a) && isempty(b) && return true
(isempty(a) || isempty(b)) && return false
islessprime(a.lo, b.lo) && islessprime(a.hi, b.hi)
Expand Down
4 changes: 2 additions & 2 deletions src/intervals/conversion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ else
function atomic(::Type{Interval{T}}, x::S) where {T<:AbstractFloat, S<:Real}
isinf(x) && return wideinterval(T(x))

Interval{T}( T(x, RoundDown), T(x, RoundUp) )
Interval( T(x, RoundDown), T(x, RoundUp) )
# the rounding up could be done as nextfloat of the rounded down one?
# use @round_up and @round_down here?
end
Expand All @@ -121,7 +121,7 @@ atomic(::Type{Interval{Irrational{T}}}, x::Irrational{S}) where {T, S} =
float(atomic(Interval{Float64}, x))

function atomic(::Type{Interval{T}}, x::Interval) where T<:AbstractFloat
Interval{T}( T(x.lo, RoundDown), T(x.hi, RoundUp) )
Interval( T(x.lo, RoundDown), T(x.hi, RoundUp) )
end

# Complex numbers:
Expand Down
42 changes: 42 additions & 0 deletions src/intervals/heuristics.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

struct UndecidableError <: Exception
msg
end

function isfinite(x::NumberInterval)
isbounded(x) && return true
throw(UndecidableError("may represent a finite number (or not)"))
end
isinf(x::NumberInterval) = !isfinite(x)

function iszero(x::NumberInterval)
if contains_zero(x)
if isthin(x)
return true
else
throw(UndecidableError("may represent zero (or not)"))
end
end
return false
end

#isequal(a::NumberInterval{T}, b::NumberInterval{S}) where {T, S} =
# SetInterval{T}(a) == SetInterval{S}(b)
isequal(a::NumberInterval, b::NumberInterval) =
SetInterval(a) == SetInterval(b)

function ==(a::NumberInterval, b::NumberInterval)
isthin(a) && isequal(a, b) && return true
isempty(a ∩ b) && return false
throw(UndecidableError("may represent equal numbers (or not)"))
end
!=(a::NumberInterval, b::NumberInterval) = !(a == b)

function <(a::NumberInterval, b::NumberInterval)
strictprecedes(a, b) && return true
precedes(b, a) && return false
throw(UndecidableError("cannot compare represented numbers"))
end
>(a::NumberInterval, b::NumberInterval) = b < a
<=(a::NumberInterval, b::NumberInterval) = !(b < a)
#>=(a::NumberInterval, b::NumberInterval) = b <= a
20 changes: 16 additions & 4 deletions src/intervals/intervals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ end

abstract type AbstractInterval{T} <: Real end

struct Interval{T<:Real} <: AbstractInterval{T}
abstract type Behavior end
struct SetLike <: Behavior end
struct NumberLike <: Behavior end
const DefaultBehavior = SetLike

struct Interval{T <: Real, B <: Behavior} <: AbstractInterval{T}
lo :: T
hi :: T

function Interval{T}(a::Real, b::Real) where T<:Real
function Interval{T, B}(a::Real, b::Real) where {T<:Real, B<: Behavior}

if validity_check

Expand All @@ -35,14 +40,20 @@ struct Interval{T<:Real} <: AbstractInterval{T}
end
end

const SetInterval{T} = Interval{T, SetLike}
const NumberInterval{T} = Interval{T, NumberLike}

SetInterval(x::NumberInterval{T}) where T = SetInterval{T}(x.lo, x.hi)
NumberInterval(x::SetInterval{T}) where T = NumberInterval{T}(x.lo, x.hi)

## Outer constructors

Interval(a::T, b::T) where T<:Real = Interval{T}(a, b)
Interval(a::T, b::T) where {T<:Real} = Interval{T, DefaultBehavior}(a, b)
#Interval(::Type{B}, a::T, b::T) where {B <: Behavior, T <: Real} =
# Interval{T, B}(a, b)
Interval(a::T) where T<:Real = Interval(a, a)
Interval(a::Tuple) = Interval(a...)
Interval(a::T, b::S) where {T<:Real, S<:Real} = Interval(promote(a,b)...)
Interval(a::T, b::S) where {T<:Real, S<:Real} = Interval(promote(a, b)...)

## Concrete constructors for Interval, to effectively deal only with Float64,
# BigFloat or Rational{Integer} intervals.
Expand Down Expand Up @@ -119,6 +130,7 @@ include("conversion.jl")
include("precision.jl")
include("set_operations.jl")
include("arithmetic.jl")
include("heuristics.jl")
include("functions.jl")
include("trigonometric.jl")
include("hyperbolic.jl")
Expand Down
2 changes: 1 addition & 1 deletion src/intervals/set_operations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ function setdiff(x::Interval, y::Interval)
intersection = x ∩ y

isempty(intersection) && return [x]
intersection == x && return typeof(x)[] # x is subset of y; setdiff is empty
isequal(intersection, x) && return typeof(x)[] # x is subset of y; setdiff is empty

x.lo == intersection.lo && return [Interval(intersection.hi, x.hi)]
x.hi == intersection.hi && return [Interval(x.lo, intersection.lo)]
Expand Down
15 changes: 8 additions & 7 deletions src/intervals/special.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"""`emptyinterval`s are represented as the interval [∞, -∞]; note
that this interval is an exception to the fact that the lower bound is
larger than the upper one."""
emptyinterval(::Type{T}) where T<:Real = Interval{T}(Inf, -Inf)
emptyinterval(x::Interval{T}) where T<:Real = emptyinterval(T)
emptyinterval(::Type{T}) where {T<:Real} =
Interval(convert(T, Inf), convert(T, -Inf))
emptyinterval(x::Interval{T}) where {T<:Real} = emptyinterval(T)
emptyinterval() = emptyinterval(precision(Interval)[1])
const ∅ = emptyinterval(Float64)

Expand All @@ -22,9 +23,9 @@ entireinterval(x::Interval{T}) where T<:Real = entireinterval(T)
entireinterval() = entireinterval(precision(Interval)[1])

isentire(x::Interval) = x.lo == -Inf && x.hi == Inf
isinf(x::Interval) = isentire(x)
isinf(x::SetInterval) = isentire(x)
isunbounded(x::Interval) = x.lo == -Inf || x.hi == Inf

isbounded(x::Interval) = !isunbounded(x)

# NaI: not-an-interval
"""`NaI` not-an-interval: [NaN, NaN]."""
Expand All @@ -34,8 +35,8 @@ nai() = nai(precision(Interval)[1])

isnai(x::Interval) = isnan(x.lo) || isnan(x.hi)

isfinite(x::Interval) = isfinite(x.lo) && isfinite(x.hi)
isnan(x::Interval) = isnai(x)
isfinite(x::SetInterval) = isfinite(x.lo) && isfinite(x.hi)
isnan(x::SetInterval) = isnai(x)

"""
isthin(x)
Expand Down Expand Up @@ -76,7 +77,7 @@ This occurs when the interval is empty, or when the upper bound equals the lower
"""
isatomic(x::Interval) = isempty(x) || (x.hi == x.lo) || (x.hi == nextfloat(x.lo))

iszero(x::Interval) = iszero(x.lo) && iszero(x.hi)
iszero(x::SetInterval) = iszero(x.lo) && iszero(x.hi)

contains_zero(X::Interval{T}) where {T} = zero(T) ∈ X

Expand Down