-
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 unbounded endpoints. Represented by nothing
#92
Conversation
I highly recommend you look at |
src/endpoint.jl
Outdated
a.endpoint < b.endpoint || (a.endpoint == b.endpoint && a.included && !b.included) | ||
if isnothing(a.endpoint) && isnothing(b.endpoint) | ||
return false | ||
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.
If a
is unbounded and b
is not then a < b
src/endpoint.jl
Outdated
@@ -7,10 +7,10 @@ const Beginning = Left | |||
const Ending = Right | |||
|
|||
struct Endpoint{T, D} | |||
endpoint::T | |||
endpoint::Union{T, Nothing} |
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.
For Endpoint
I could see not making the endpoint field a Union
. What I'm imagining is that you'd have Endpoint{Nothing, Left}
for an unbounded left side. The advantage this may have is you can use multiple dispatch to special case the unbounded endpoint comparisons
src/endpoint.jl
Outdated
if isnothing(a.endpoint) && isnothing(b.endpoint) | ||
return false | ||
end | ||
return a.endpoint < b.endpoint |
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.
return isnothing(a.endpoint) || isnothing(b.endpoint) || a.endpoint < b.endpoint
src/endpoint.jl
Outdated
return false | ||
else | ||
return a.endpoint < b.endpoint || (a.endpoint == b.endpoint && !(a.included && b.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.
return !isnothing(a.endpoint) && !isnothing(b.endpoint) && (...)
src/endpoint.jl
Outdated
Base.:(==)(a, b::Endpoint) = a == b.endpoint && b.included | ||
function Base.:(==)(a, b::Endpoint) | ||
a == b.endpoint && b.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.
Why the change to the long-function form?
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 I was going to add something in here, but then found the actual place I had to change. Will revert
src/endpoint.jl
Outdated
end | ||
end | ||
# If the right endpoint is unbounded, then it is always greater than any other value | ||
Base.isless(a, b::RightEndpoint) = isnothing(b.endpoint) ? true : a < b.endpoint |
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.
Base.isless(a, b::RightEndpoint) = isnothing(b.endpoint) ? true : a < b.endpoint | |
Base.isless(a, b::RightEndpoint) = isnothing(b.endpoint) || a < b.endpoint |
src/endpoint.jl
Outdated
# If the right endpoint is unbounded, then it is always greater than any other value | ||
Base.isless(a, b::RightEndpoint) = isnothing(b.endpoint) ? true : a < b.endpoint | ||
# If the left endpoint is unbounded, then it is always less than any other value | ||
Base.isless(a::LeftEndpoint, b) = isnothing(a.endpoint) ? true : a.endpoint < b |
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.
Base.isless(a::LeftEndpoint, b) = isnothing(a.endpoint) ? true : a.endpoint < b | |
Base.isless(a::LeftEndpoint, b) = isnothing(a.endpoint) || a.endpoint < b |
src/interval.jl
Outdated
if isnothing(f) || isnothing(l) | ||
# If only one endpoint is nothing, use the type of the other endpoint | ||
T = isnothing(f) ? typeof(l) : typeof(f) | ||
return Interval{T}(f, l, inc...) | ||
else | ||
return Interval(promote(f, l)..., inc...) | ||
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.
Use more dispatch:
Interval(f::Nothing, l::T, inc...) where T = Interval{T}(f, l, inc...)
Interval(f::T, l::Nothing, inc...) where T = Interval{T}(f, l, inc...)
src/interval.jl
Outdated
return false | ||
else | ||
return re < le | ||
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.
Logic shouldn't have to change here as endpoint comparisons should handle unbounded right?
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'll have to double check. At some point I found that some of the tests were failing here without modifications, but I'll go back to it and make sure
src/interval.jl
Outdated
end | ||
else | ||
return !(a ≫ b || a ≪ b) | ||
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 would expect there should be no changes here and this logic would be handled by less_than_disjoint
src/endpoint.jl
Outdated
@@ -80,26 +80,59 @@ function Base.isequal(a::RightEndpoint, b::LeftEndpoint) | |||
end | |||
|
|||
function Base.isless(a::LeftEndpoint, b::LeftEndpoint) | |||
a.endpoint < b.endpoint || (a.endpoint == b.endpoint && a.included && !b.included) | |||
if isnothing(a.endpoint) && isnothing(b.endpoint) |
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.
all of the isnothing(x)
s should x === nothing
for reasons i don't pretend to understand but which are referenced in https://docs.julialang.org/en/v1/manual/performance-tips/#Checking-for-equality-with-a-singleton-1 (xref JuliaLang/julia#35585)
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's true. We may want to have a isunbounded
abstraction as we may switch our internal representation
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 interesting. Will do. I had read somewhere to use isnothing(x)
but that makes 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.
Actually, I believe isnothing(x)
should be as performant as x === nothing
it's x == nothing
that can be slightly slower
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 like the abstraction idea. I'll throw that in as well
@@ -41,5 +41,6 @@ export AbstractInterval, | |||
less_than_disjoint, | |||
greater_than_disjoint, | |||
superset, | |||
.., ≪, ≫, ⊆, ⊇, ⊈, ⊉ | |||
.., ≪, ≫, ⊆, ⊇, ⊈, ⊉, | |||
Endpoint, Left, Right, LeftEndpoint, RightEndpoint |
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 added this for testing purposes. I will need to remove this later.
|
||
# Note: Are these two comparisons correct? | ||
Base.isless(a::Endpoint{Nothing}, b::Endpoint{T}) where T = true | ||
Base.isless(a::Endpoint{T}, b::Endpoint{Nothing}) where T = 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.
I can't decide if this makes sense. Especially since we don't define a negative unbounded, and positive unbounded.
|
||
function Base.isless(a::RightEndpoint, b) | ||
return !isunbounded(a.endpoint) && (isunbounded(b) || (a.endpoint < b || (a.endpoint == b && !a.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.
Similar comment here as the one right above.
@@ -8,10 +8,10 @@ using VisualRegressionTests | |||
|
|||
|
|||
@testset "Intervals" begin | |||
include("inclusivity.jl") | |||
include("endpoint.jl") | |||
#include("inclusivity.jl") |
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 need to remember to add these back in
Codecov Report
@@ Coverage Diff @@
## master #92 +/- ##
===========================================
- Coverage 96.49% 37.94% -58.56%
===========================================
Files 7 7
Lines 257 369 +112
===========================================
- Hits 248 140 -108
- Misses 9 229 +220
Continue to review full report at Codecov.
|
@@ -1,3 +1,6 @@ | |||
# isnothing doesn't exist in Julia 1.0 | |||
isunbounded(a) = a === nothing |
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.
calling this isunbounded
rather than isbounded
means the code has lots of double-negatives !isunbounded
...i wonder if flipping the condition would make the long chains of &&
and ||
a little easier to read
isunbounded(a) = a === nothing | |
isbounded(a) = a !== nothing |
This PR does a lot of work essentially use julia> Interval(-Inf, 100.0)
Interval{Float64}(-Inf, 100.0, Inclusivity(true, true))
julia> Interval(100.0, Inf)
Interval{Float64}(100.0, Inf, Inclusivity(true, true)) Of course, julia> Interval(π, Inf)
Interval{Float64}(3.141592653589793, Inf, Inclusivity(true, true))
julia> Interval(10//2, Inf)
Interval{Float64}(5.0, Inf, Inclusivity(true, true))
julia> Interval(10, Inf)
Interval{Float64}(5.0, Inf, Inclusivity(true, true)) And for date/time types, there is no promotion julia> using Dates
julia> Interval(now(), Inf)
ERROR: promotion of types DateTime and Float64 failed to change any arguments But it does make me wonder if what we want here is just an "InfDateTime"? |
We are also looking into such an approach. As intervals only require the provided bounds to be orderable we'd need a representation of infinity for each type wanting to support unbounded endpoints. We're also looking into https://github.com/cjdoris/Infinity.jl but we could take other approaches like you suggest. I will note however we do not want to promote to julia> Interval(1, Inf)
Interval{Float64}(1.0, Inf, Inclusivity(true, true)) The Infinity.jl package approach is more suitable: julia> Interval(1, ∞)
Interval{InfExtended{Int64}}(1, ∞, Inclusivity(true, true)) |
Yes, I'm thinking maybe at this point in this PR, I should maybe change my efforts to adding a Dates PR to Infinity.jl and just end up using that. There will still be a few changes needed for this package, but I think that would be much easier in the end. This is also assuming that the maintainer of Infinity.jl will be open to a Dates addition. |
Closes #89
Initial WIP for adding unbounded endpoints which will be represented by
nothing
There seems to be a lot of edge cases to take into account. There are still a lot of tests that need to be written, and more modifications to support unbounded intervals (such as intersection, union, etc.