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

Simplified version of mod(x::Interval, y::Real) #525

Merged
merged 12 commits into from
Jul 3, 2023
1 change: 1 addition & 0 deletions src/IntervalArithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Base:
in, zero, one, eps, typemin, typemax, abs, abs2, real, min, max,
sqrt, exp, log, sin, cos, tan, cot, inv, cbrt, csc, hypot, sec,
exp2, exp10, log2, log10,
mod,
asin, acos, atan,
sinh, cosh, tanh, coth, csch, sech, asinh, acosh, atanh, sinpi, cospi,
union, intersect, isempty,
Expand Down
10 changes: 10 additions & 0 deletions src/intervals/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,13 @@ function nthroot(a::Interval{T}, n::Integer) where T
b = nthroot(bigequiv(a), n)
return convert(Interval{T}, b)
end

"""
Calculate `x mod y` where `x` is an interval and `y` is a positive divisor.
"""
function mod(x::Interval, y::Real)
@assert y > zero(y) "modulo is currently implemented only for a positive divisor."
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to extend this to have strictly negative y? I understand that having 0 ∈ y complicates things....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated for strictly negative divisor. Hope its correct.

Copy link
Member

Choose a reason for hiding this comment

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

The implementation seems to me correct if we have y::AbstractFloat (perhaps also including Rationals, but let me forget about them now), which corresponds to the tests.

However, if y::Interval (and we have Interval <: Real) then there this function throws errors: e.g., try mod(1..2, 1..2), which I think should return Interval(0.0, 2.0). In this case things are subtle, because y != zero(y) is true for [-1, 1], but that interval is not strictly positive or negative. Also, Interval(zero(y), y) causes the error mentioned above. I guess this is the reason that mod has two methods in #178.

My suggestion is either restrict y::AbstractFloat, or include a new method where y::Interval.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is my mistake. My intention was to constrain y NOT to be Interval. I've not realized 1..3 isa Real -> true. However, If I constraint it to y::AbstractFloat it does not accept integers for example because 1 isa AbstractFloat -> false, right?

Copy link
Member

Choose a reason for hiding this comment

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

you could do Union{AbstractFloat, Integer} to accept both integers and floats but no intervals (similar to add rationals and irrationals).

Can the method be generalized to the case of y interval?

Copy link
Member

Choose a reason for hiding this comment

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

actually forget about the union, a better suggestion would be to define

mod(x::Real, y::Interval) = throw(ArgumentError("mod not defined for second argument interval"))
mod(x::Interval, y::Interval) = throw(ArgumentError("mod not defined for second argument interval"))

Copy link
Member

Choose a reason for hiding this comment

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

mod can indeed be generalized for y::Interval. It's tricky with respect of having zero within the interval, which is part of the reason I was suggesting to have either a strictly positive or negative y. Actually, a motivating example would be to have y::Irrational, or actually any (mathematical) real number which is not exactly representable as a Float64. Note that #178 includes such an implementation, though it does not include the restriction of strictly positive/negative intervals. The subtleties related to zero at the end are related to the division: y appears in the denominator.

Copy link
Member

Choose a reason for hiding this comment

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

I unresolved this conversation, so it is easy to track the discussion...

Copy link
Member

Choose a reason for hiding this comment

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

fwiw in octave

>> mod(infsup(-3, 2), infsup(-1, 0.5))
ans = [-1, +0.5]

>> mod(infsup(-3, 2), infsup(0, 0))
ans = [Empty]

>> mod(infsup(-3, 2), infsup(0, 3))
ans = [0, 3]

>> mod(infsup(-3, 2), infsup(1, 3))
ans = [0, 3]

>> mod(infsup(-3, 2), infsup(-3, 2))
ans = [-3, +2]

division = x / y
fl = floor(division)
fl.lo < fl.hi ? Interval(zero(y), y) : y * (division - fl)
petvana marked this conversation as resolved.
Show resolved Hide resolved
end
27 changes: 27 additions & 0 deletions test/interval_tests/numeric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,30 @@ end
@test nthroot(Interval{BigFloat}(-81, -16), -4) == ∅
@test nthroot(Interval{BigFloat}(-81, -16), 1) == Interval{BigFloat}(-81, -16)
end

# approximation used for testing (not to rely on ≈ for intervals)
# ⪆(x, y) = (x ≈ y) && (x ⊇ y)
⪆(x::Interval, y::Interval) = x.lo ≈ y.lo && x.hi ≈ y.hi && x ⊇ y
petvana marked this conversation as resolved.
Show resolved Hide resolved

@testset "`mod`" begin
petvana marked this conversation as resolved.
Show resolved Hide resolved
r = 0.0625
x = r..(1+r)
@test mod(x, 1) == mod(x, 1.0) == 0..1
@test mod(x, 2) == mod(x, 2.0) ⪆ x
@test mod(x, 2.5) ⪆ x
@test mod(x, 0.5) == 0..0.5

x = (-1+r) .. -r
@test mod(x, 1) == mod(x, 1.0) ⪆ 1+x
@test mod(x, 2) == mod(x, 2.0) ⪆ 2+x
@test mod(x, 2.5) ⪆ 2.5+x
@test mod(x, 0.5) == 0..0.5

x = -r .. 1-r
@test mod(x, 1) == mod(x, 1.0) == 0..1
@test mod(x, 2) == mod(x, 2.0) == 0..2
@test mod(x, 2.5) == 0..2.5
@test mod(x, 0.5) == 0..0.5

@test_throws AssertionError mod(x, -1)
end