-
Notifications
You must be signed in to change notification settings - Fork 18
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 support for Infinity.jl #95
Conversation
test/comparisons.jl
Outdated
@show a | ||
@show b | ||
earlier = convert(A, Interval(a[1], a[2], a[3], a[4])) | ||
later = convert(B, Interval(b[1], b[2], b[3], b[4])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These converts seem to fail for AnchoredIntervals, and I currently can't seem to figure out how to fix it.
Seems to fail here https://github.com/invenia/Intervals.jl/blob/master/src/anchoredinterval.jl#L165 with the error
a = (-∞, 2, true, true)
b = (4, ∞, true, true)
non-overlapping: Error During Test at /Users/fchorney/Documents/repos/julia/Intervals.jl/test/comparisons.jl:20
Got exception outside of a @test
TypeError: in Type, in parameter, expected Int64, got InfExtended{Int64}
Stacktrace:
[1] convert(::Type{AnchoredInterval{Intervals.Direction{:Right}(),T} where T}, ::Interval{InfExtended{Int64}}) at /Users/fchorney/Documents/repos/julia/Intervals.jl/src/anchoredinterval.jl:167
[2] top-level scope at /Users/fchorney/Documents/repos/julia/Intervals.jl/test/comparisons.jl:29
[3] top-level scope at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.3/Test/src/Test.jl:1107
[4] top-level scope at /Users/fchorney/Documents/repos/julia/Intervals.jl/test/comparisons.jl:21
[5] top-level scope at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.3/Test/src/Test.jl:1180
[6] include at ./boot.jl:328 [inlined]
[7] include_relative(::Module, ::String) at ./loading.jl:1105
[8] include(::Module, ::String) at ./Base.jl:31
[9] include(::String) at ./client.jl:424
[10] top-level scope at /Users/fchorney/Documents/repos/julia/Intervals.jl/test/runtests.jl:15
[11] top-level scope at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.3/Test/src/Test.jl:1107
[12] top-level scope at /Users/fchorney/Documents/repos/julia/Intervals.jl/test/runtests.jl:15
[13] include at ./boot.jl:328 [inlined]
[14] include_relative(::Module, ::String) at ./loading.jl:1105
[15] include(::Module, ::String) at ./Base.jl:31
[16] include(::String) at ./client.jl:424
[17] top-level scope at none:6
[18] eval(::Module, ::Any) at ./boot.jl:330
[19] exec_options(::Base.JLOptions) at ./client.jl:263
[20] _start() at ./client.jl:460
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this call is occuring convert(AnchoredInterval{Ending}, Interval(4, ∞, true, true))
which doesn't make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m unsure why that doesn’t make any sense? Seems like it would work how it used to but now has infinity values in it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason it's non-sense is the point at which you're setting the one endpoint used in the AnchoredInterval
is ∞
. Then we're calculating the other endpoint by subtracting 4
from ∞
which is also ∞
. There doesn't seem to be a way to do this conversion without losing information which is why I'm thinking this conversion should error. We should detect this and report a nice error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote up another example here: #89 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah ok I see. Thanks for the explanation!
b8bc4e0
to
4e3ebe5
Compare
src/interval.jl
Outdated
end | ||
return Interval(f, l, inc) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if a user has explicitly set T
we shouldn't be messing with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm yeah I suppose this change is actually just a byproduct of trying to throw in unbounded values into the tests.
This test is specifically the issue (https://github.com/invenia/Intervals.jl/blob/fc/using-infinity/test/interval.jl#L46-L47):
@test Interval(b, a, Inclusivity(true, false)) == Interval{typeof(a)}(a, b, Inclusivity(false, true))
When you create the interval on the left side of ==
it will promote(b, a)
so if one value is ∞
and the other is a value, let's say 10
, then it converts both values to InfExtended
. Now on the right side of ==
, if a
is of type Infinite
(due to being a ∞
or -∞
) then it will try to convert(Infinite, 10)
for b
, which is an error. On the other side of the spectrum, if a
is 10
which would make it Int64
type, then trying to do convert(Int64, ∞)
for b
will also fail. I guess we can just let it be an error if somebody puts in the wrong type in the Interval constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the test should be updated to be using:
julia> promote_type(Infinite, Int)
InfExtended{Int64}
Then in the tests you can use Interval{promote_type(typeof(a), typeof(b))}(...)
@@ -161,7 +181,8 @@ end | |||
|
|||
##### ARITHMETIC ##### | |||
|
|||
Base.:+(a::T, b) where {T <: Interval} = T(first(a) + b, last(a) + b, inclusivity(a)) | |||
Base.:+(a::Interval, b) = Interval(first(a) + b, last(a) + b, inclusivity(a)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous version always kept the Interval
unit type the same. This could cause the Interval type to change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This becomes the same issue as most of the other issues that cropped up by adding Infinity. If a
is -∞ .. ∞
and b
is of type Int
then ∞ - b
where b
is 1
becomes an InfExtended{Int64}
type which will error out since T would have been Interval{Infinite}(...)
. So we end up trying to run convert(Infinite, InfExtended{Int64})
which is undefined. That being said, maybe that shouldn't be undefined. Converting an InfExtended{Int64})
to Infinite
could potentially work if we check the value to make sure it's not an actual extended value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That conversion makes sense if the InfExtended{Int64}
is infinite
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed by adding the conversion: cjdoris/Infinity.jl#7
src/interval.jl
Outdated
return Interval( | ||
promote(left.endpoint, right.endpoint)..., | ||
left.included, right.included | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems preferable to change the endpoint constructor (Interval{T}(left::LeftEndpoint{T}, right::RightEndpoint{T})
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem here is that you'll end up with situations such that
typeof(left) = Intervals.Endpoint{InfExtended{Int64},Intervals.Direction{:Left}()}
typeof(right) = Intervals.Endpoint{Int64,Intervals.Direction{:Right}()}
Thus you don't actually make it to that constructor you mention since the types are different. It ends up trying to call Interval(f, l, inc...) = Interval(promote(f, l)..., inc...)
which fails because there are no rules on how to promote two endpoints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to also define a constructor for two endpoints where the types are different? I seems like we typically don't want to change what the Interval Type is, but with Infinity it seems to be required in certain situations
function Interval(left::LeftEndpoint{S}, right::RightEndpoint{D}) where {S, D}
T = promote_type(S, D)
return Interval{T}(left.endpoint, right.endpoint, left.included, right.included)
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is one option. We may alternatively want to define our own promote_rule
test/comparisons.jl
Outdated
tests = [ | ||
((1, 2, true, true), (4, 5, true, true)), | ||
((-∞, 2, true, true), (4, ∞, true, true)), | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testsets have a for-loop syntax which would be better as you can output details in the name of the testset (https://docs.julialang.org/en/v1/stdlib/Test/#Test.@testset). I'd probably prefer a distinct testset named "non-overlapping unbounded"
test/comparisons.jl
Outdated
@show a | ||
@show b | ||
earlier = convert(A, Interval(a[1], a[2], a[3], a[4])) | ||
later = convert(B, Interval(b[1], b[2], b[3], b[4])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this call is occuring convert(AnchoredInterval{Ending}, Interval(4, ∞, true, true))
which doesn't make sense.
src/Intervals.jl
Outdated
@@ -1,6 +1,8 @@ | |||
module Intervals | |||
|
|||
using Dates | |||
using Infinity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed as you explicitly import methods below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don’t then you can’t use the infinity symbol correctly. I suppose I could just import it with the other items.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the context of the package I don't think we're referencing the infinity symbol
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm good point I guess it's just for the tests.
@test Interval(-∞, ∞) == Interval(-∞, ∞) | ||
@test Interval(-∞, ∞) != Interval(-∞, ∞, true, false) | ||
@test Interval(-∞, ∞) != Interval(-∞, ∞, false, true) | ||
@test Interval(-∞, ∞) != Interval(-∞, ∞, false, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we weren't allowing inclusive endpoints for unbounded endpoints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn’t sure if we still wanted to do that with proper infinity. As it potentially makes sense for infinity itself to be inclusive or not? It definitely makes the logic nicer to include inclusivity. I am willing to go back to mutating the inclusivity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As infinity is the first non-finite value we're supporting in intervals I think there is going to be some specialized logic around it. I think I'll write some of the logic down in the original issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After writing down specific rules I think we can leave this as it is: #89 (comment)
Codecov Report
@@ Coverage Diff @@
## master #95 +/- ##
==========================================
+ Coverage 96.49% 96.57% +0.07%
==========================================
Files 7 7
Lines 257 321 +64
==========================================
+ Hits 248 310 +62
- Misses 9 11 +2
Continue to review full report at Codecov.
|
src/interval.jl
Outdated
function isbounded(a) | ||
T = typeof(a) | ||
if T <: Infinite || T <: InfExtended | ||
return !isposinf(a) && !isneginf(a) | ||
else | ||
return true | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isfinite
works with Infinity
and InfExtended
types as well as the Inf
from Float64
. Additionally this works with TimeType
s:
function isbounded(a) | |
T = typeof(a) | |
if T <: Infinite || T <: InfExtended | |
return !isposinf(a) && !isneginf(a) | |
else | |
return true | |
end | |
end | |
isbounded(x) = isfinite(x) |
We were aware that However there is currently another problem: julia> convert(AnchoredInterval{Beginning}, Interval(4, ∞, true, true))
ERROR: TypeError: in Type, in parameter, expected Int64, got InfExtended{Int64}
Stacktrace:
[1] convert(::Type{AnchoredInterval{Intervals.Direction{:Left}(),T} where T}, ::Interval{InfExtended{Int64}}) at /Users/omus/.julia/dev/Intervals/src/anchoredinterval.jl:169
[2] top-level scope at REPL[4]:1 This problem is due to julia> AnchoredInterval{InfExtended{Int64}(∞),InfExtended{Int64}}(4,Inclusivity(true,true))
ERROR: TypeError: in Type, in parameter, expected Int64, got InfExtended{Int64}
Stacktrace:
[1] top-level scope at REPL[5]:1
julia> AnchoredInterval{∞,InfExtended{Int64}}(4,Inclusivity(true,true))
AnchoredInterval{∞,InfExtended{Int64}}(4, Inclusivity(true, true)) |
Solutions:
Workarounds:
|
Prevents it from being used in type parameters
Allows for use of types where zero cannot be represented. Note that using `signbit(x)` appears preferable to `sign(x) < 0` but unfortunately less types support `signbit` (e.g. Date)
Using #92 as a jump off point. This is a PR that utilizing Infinity.jl instead of using
nothing
as a sentinel value to represent unbounded ranges.There were still a few changes and fixes to be made, but I think this somewhat just works(tm).
If this is good, the next steps would be to add Date/Time support to Infinity.jl
Most of this PR is just adding tests