Skip to content

Commit

Permalink
Allow pointwise equality (#604)
Browse files Browse the repository at this point in the history
* Fix `isthininteger` for `Complex{<:Interval}`

* Allow pointwise equality

* Update tests

* Add docstring

* Remove superfluous methods

* Fix broken test
  • Loading branch information
OlivierHnt authored Dec 29, 2023
1 parent a105724 commit 7dd2ab6
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 15 deletions.
27 changes: 19 additions & 8 deletions src/intervals/interval_operations/boolean.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@ function isinterior(x::Interval, y::Interval)
return isinterior(bareinterval(x), bareinterval(y))
end

isinterior(x::AbstractVector, y::AbstractVector) = all(t -> isinterior(t[1], t[2]), zip(x, y))
function isinterior(x::AbstractVector, y::AbstractVector)
n = length(x)
m = length(y)
n == m || return throw(DimensionMismatch("dimensions must match: x has length $n, y has length $m"))
return all(t -> isinterior(t[1], t[2]), zip(x, y))
end

isinterior(x, y, z, w...) = isinterior(x, y) & isinterior(y, z, w...)
isinterior(x::Complex, y::Complex) =
Expand All @@ -86,7 +91,7 @@ isstrictsubset(x::BareInterval, y::BareInterval) = isinterior(x, y)

isstrictsubset(x::Interval, y::Interval) = isinterior(x, y)

isstrictsubset(x::AbstractVector, y::AbstractVector) = any(t -> isinterior(t[1], t[2]), zip(x, y))
isstrictsubset(x::AbstractVector, y::AbstractVector) = isinterior(x, y) & any(t -> isinterior(t[1], t[2]), zip(x, y))

isstrictsubset(x, y, z, w...) = isstrictsubset(x, y) & isstrictsubset(y, z, w...)
isstrictsubset(x::Complex, y::Complex) =
Expand Down Expand Up @@ -179,12 +184,13 @@ Test whether `x` is an element of `y`.
Implement the `isMember` function of the IEEE Standard 1788-2015 (Section 10.6.3).
"""
function in_interval(x::Real, y::BareInterval)
function in_interval(x::Number, y::BareInterval)
isinf(x) && return contains_infinity(y)
return inf(y) x sup(y)
end
in_interval(x::Complex, y::BareInterval) = in_interval(real(x), y) & iszero(imag(x))

function in_interval(x::Real, y::Interval)
function in_interval(x::Number, y::Interval)
isnai(y) && return false
return in_interval(x, bareinterval(y))
end
Expand All @@ -196,8 +202,8 @@ in_interval(::Interval, ::Interval) =
throw(ArgumentError("`in_interval` is purposely not supported for two interval arguments. See instead `issubset_interval`"))

in_interval(x::Complex, y::Complex) = in_interval(real(x), real(y)) & in_interval(imag(x), imag(y))
in_interval(x::Complex, y::Real) = in_interval(real(x), y) & isthinzero(imag(x))
in_interval(x::Real, y::Complex) = in_interval(x, real(y)) & in_interval(0, imag(y))
in_interval(x::Complex, y::Number) = in_interval(real(x), y) & iszero(imag(x))
in_interval(x::Number, y::Complex) = in_interval(x, real(y)) & in_interval(0, imag(y))

in_interval(x) = Base.Fix2(in_interval, x)

Expand Down Expand Up @@ -328,13 +334,18 @@ isthin(x::Complex) = isthin(real(x)) & isthin(imag(x))
Test whether `x` contains only `y`.
"""
isthin(x::BareInterval, y::Number) = inf(x) == sup(x) == y
isthin(x::BareInterval, y::Complex) = isthin(x, real(y)) & iszero(imag(y))

function isthin(x::Interval, y::Number)
isnai(x) && return false
return isthin(bareinterval(x), y)
end

isthin(x::Complex, y::Number) = isthin(real(x), y) & isthin(imag(x), y)
isthin(x::Complex, y::Complex) = isthin(real(x), real(y)) & isthin(imag(x), imag(y))
isthin(x::Complex, y::Number) = isthin(real(x), real(y)) & isthinzero(imag(x))
isthin(x::Number, y::Complex) = isthin(real(x), real(y)) & iszero(imag(y))

isthin(x::BareInterval, y::Interval) = throw(MethodError(isthin, (x, y)))

"""
isthinzero(x)
Expand Down Expand Up @@ -376,4 +387,4 @@ function isthininteger(x::Interval)
return isthininteger(bareinterval(x))
end

isthininteger(x::Complex) = isthininteger(real(x)) & isthininteger(imag(x))
isthininteger(x::Complex) = isthininteger(real(x)) & isthinzero(imag(x))
47 changes: 44 additions & 3 deletions src/intervals/real_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,6 @@ for T ∈ (:BareInterval, :Interval)
Base.isnan(::$T) =
throw(ArgumentError("`isnan` is purposely not supported for intervals. See instead `isnai`"))

Base.isinteger(::$T) =
throw(ArgumentError("`isinteger` is purposely not supported for intervals. See instead `isthininteger`"))

Base.intersect(::$T) =
throw(ArgumentError("`intersect` is purposely not supported for intervals. See instead `intersect_interval`"))

Expand All @@ -130,3 +127,47 @@ for T ∈ (:BareInterval, :Interval)
throw(ArgumentError("`setdiff!` is purposely not supported for intervals. See instead `interiordiff`"))
end
end

# allow pointwise equality

"""
==(::BareInterval, ::Number)
==(::Number, ::BareInterval)
==(::Interval, ::Number)
==(::Number, ::Interval)
Test whether an interval is the singleton of a given number. In other words, the
result is true if and only if the interval contains only that number.
!!! note
Comparison between intervals is purposely disallowed. Indeed, equality
between non-singleton intervals has distinct properties, notably ``x = y``
does not imply ``x - y = 0``. See instead [`isequal_interval`](@ref).
"""
Base.:(==)(x::BareInterval, y::Number) = inf(x) == sup(x) == y
Base.:(==)(x::Number, y::BareInterval) = y == x
function Base.:(==)(x::Interval, y::Number)
isnai(x) && return false
return bareinterval(x) == y
end
Base.:(==)(x::Number, y::Interval) = y == x
Base.:(==)(x::BareInterval, y::Interval) = throw(MethodError(==, (x, y)))
Base.:(==)(x::Interval, y::BareInterval) = throw(MethodError(==, (x, y)))

Base.iszero(x::BareInterval) = iszero(inf(x)) & iszero(sup(x))
function Base.iszero(x::Interval)
isnai(x) && return false
return iszero(bareinterval(x))
end

Base.isone(x::BareInterval) = isone(inf(x)) & isone(sup(x))
function Base.isone(x::Interval)
isnai(x) && return false
return isone(bareinterval(x))
end

Base.isinteger(x::BareInterval) = (inf(x) == sup(x)) & isinteger(inf(x))
function Base.isinteger(x::Interval)
isnai(x) && return false
return isinteger(bareinterval(x))
end
2 changes: 1 addition & 1 deletion test/interval_tests/complex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@test isequal_interval(a * a, interval(-1))
@test isequal_interval(a + a, interval(2)*interval(im))
@test isthin(a - a, 0)
@test_broken isthin(a / a, 1)
@test isthin(a / a, 1)

@test in_interval(3+2im, c)
@test isequal_interval(hull(a, b), complex(interval(0, 3), interval(1, 4)))
Expand Down
5 changes: 4 additions & 1 deletion test/interval_tests/consistency.jl
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,10 @@
@test_throws ArgumentError isempty(x)
@test_throws ArgumentError isfinite(x)
@test_throws ArgumentError isnan(x)
@test_throws ArgumentError isinteger(x)
@test isinteger(x)
@test x == 1
@test isone(x)
@test !iszero(x)
end

end
Expand Down
4 changes: 2 additions & 2 deletions test/interval_tests/forwarddiff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ end
(t) = ForwardDiff.derivative(ψ, t)
ddψ(t) = ForwardDiff.derivative(dψ, t)
dddψ(t) = ForwardDiff.derivative(ddψ, t)
@test ψ′(0) === (0) && !isguaranteed(ψ′(0))
@test ψ′(0) === (0) && !isguaranteed(ψ′(0))
@test_broken ψ′′(0) === ddψ(0) && !isguaranteed(ψ′′(0)) # rely on `Interval{T}(::Real)` being defined
@test_broken ψ′′′(0) === dddψ(0) && !isguaranteed(ψ′′′(0)) # rely on `Interval{T}(::Real)` being defined
t₀ = interval(0)
Expand All @@ -62,6 +62,6 @@ end

# g(x) = 2^x # not guaranteed

@test_broken f′(0) === df(0)
@test f′(0) === df(0)
end
end

0 comments on commit 7dd2ab6

Please sign in to comment.