isapprox should work for comparing open and closed intervals
Something like OpenInterval(0,1) should certainly be approximately ClosedInterval(0,1) as they are closer to each other than the following which returns true:
julia> nextfloat(0.)..prevfloat(1.) ≈ 0..1
true
Codecov Report
Base: 99.16% // Head: 99.16% // Decreases project coverage by -0.00% :warning:
Coverage data is based on head (
9951dde) compared to base (c88d162). Patch has no changes to coverable lines.
Additional details and impacted files
@@ Coverage Diff @@
## master #129 +/- ##
==========================================
- Coverage 99.16% 99.16% -0.01%
==========================================
Files 3 3
Lines 240 239 -1
==========================================
- Hits 238 237 -1
Misses 2 2
| Impacted Files | Coverage Δ | |
|---|---|---|
| src/IntervalSets.jl | 98.30% <ø> (-0.02%) |
:arrow_down: |
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.
:umbrella: View full report at Codecov.
:loudspeaker: Do you have feedback about the report comment? Let us know in this issue.
As discussed here (https://github.com/JuliaMath/IntervalSets.jl/pull/125#discussion_r1032855576), I still think isapprox(ClosedInterval(0,1), OpenInterval(0,1)) should return false.
We may have a type for the unconnected subset of $\mathbb{R}$ such as $[1,2] \cup (3,4]$ in the future (https://github.com/JuliaMath/IntervalSets.jl/issues/106#issuecomment-1263696451).
Then, should $[1,2) \cup (2,4] \approx [1,4]$ return true? I think it also should be false.
Hmm a more interesting example is
1..2 ∪ 3..3 ≈ 1..2
In this case the difference between the two sets has measure 0 but I agree that they are not approximately the same.
I think the right concept here is Hausdorff distance. We have
d(1..2 ∪ 3..3 , 1..2) == 3
d(OpenInterval(0,1), 0..1) == 0
d(1..2 ∪ 2..4 , 1..4) == 0
So mathematically if the Hausdorff distance d(A,B) is approximately 0 I do think A ≈ B should return true.
The Base.isapprox function tends to return strictly equality evaluations for exact numbers.
For example:
julia> a = 1.000000000000001
1.000000000000001
julia> a ≈ 1
true
julia> rationalize(a) ≈ 1
false
julia> rationalize(a) ≈ 1.0
true
Therefore, OpenInterval(0,1) ≈ ClosedInterval(0,1) (without floating point numbers) should return false, I guess.
So mathematically if the Hausdorff distance d(A,B) is approximately 0 I do think A ≈ B should return true.
It seems there are some ways to define isapprox.
- Hausdorff distance is approximately zero.
- Measure of symmetric difference is approximately zero.
- The closeness/openness of the endpoints are the same, and the positions of the endpoints are approximately equal.
- Ignore the closeness/openness of the endpoints, and the positions of the endpoints are approximately equal.
I prefer the third definition for isapprox, but the choice of the definition should be based on practical usage too.
(If we need more definitions, we can define them with other symbols such as ≃.)
Note 1 and 4 are the same thing.
You haven't given a usage reason not to just use Hausdorff distance, which is the most mathematically natural.
The
Base.isapproxfunction tends to return strictly equality evaluations for exact numbers. For example:
That is a funny example. So we have
julia> b = rationalize(1.000000000000001)
750599937895084//750599937895083
julia> isapprox(b, 1)
false
julia> isapprox(b, 1.0)
true
julia> isapprox(1, 1.0)
true
I'm not sure I like that outcome. Because then you get things like this:
julia> b ≈ 1.0 ≈ 1
true
Also, norm has no problem converting rationals to floats:
julia> norm(b-1)
1.3322676295501873e-15
It seems a fair point to me that a generic meaning of isapprox would involve a notion of a metric.
Note 1 and 4 are the same thing.
Yes, 1 and 4 are the same for intervals, but they are different for unconnected subsets of $\mathbb{R}$ such as $[1,2) \cup (2,4] \approx [1,4]$. It will be true with definition 1, but false with definition 4.
You haven't given a usage reason not to just use Hausdorff distance, which is the most mathematically natural.
To me, definition 3 is the most mathematically (or engineeringly?) natural for the following reasons:
Interval{L,R}(a,b)stores the following four information:- L: left endpoint is closed or not (discrete info)
- R: Right endpoint is closed or not (discrete info)
- a: The position of the left endpoint (continuous info)
- b: The position of the right endpoint (continuous info)
- The
isapproxis a function to check whether continuous information is close or not, so we need strict evaluation for the discrete information. - If we have
atol = rtol = 0.0, then≈should be equivalent to==. But the Hausdorff definition does not satisfy this property.
Currently, I don't have practical usage for isapprox.
I'm not sure I like that outcome. Because then you get things like this:
Note that ≈ is not a transitive relation. This property cannot be avoided because of its tolerance.
julia> a = 1.00000001
1.00000001
julia> b = 0.99999999
0.99999999
julia> a ≈ 1
true
julia> b ≈ 1
true
julia> a ≈ b
false
julia> a ≈ 1 ≈ b
true
Note that
≈is not a transitive relation. This property cannot be avoided because of its tolerance.
True. It's a little weirder than that because 1.0 == 1, so b is considered close to one point but not another one, even though they are considered equal. Since that difference is based on type, I guess that confirms your original point about strictness.
Back on topic, I can't think of an argument in either direction for more general domains right now, as DomainSets also represents open and closedness explicitly (which makes it "discrete info" that is conveniently available). But here is a related question. DomainSets defines an approximate in, approx_in. Currently:
julia> using DomainSets
julia> approx_in(1+1e-15, OpenInterval(0,1))
true
For an approximate in, it becomes much harder to argue that open and closedness should be taken into account.
On the other hand, also currently:
julia> 1+1e-15 ∈ Point(1)
false
So that is not consistent.
I would say a "fuzzy circle" (e.g. the set 1-ε < |x| < 1+ε) , which is needed as otherwise there are very few floats in 2D in the circle, should approximately be equal to a "circle". This would be consistent with using Hausdorff
It seems I was relying on approximate comparisons between open and closed intervals in BasisFunctions.jl. Easily fixed, but somewhat annoying, because it is harder to create open and closed intervals than typing 0..1.
because it is harder to create open and closed intervals than typing
0..1.
Would it be useful to have a macro like @leftopen 0..1? (x-ref: https://github.com/JuliaMath/IntervalSets.jl/issues/39#issuecomment-1098151346)
Why a macro and not just a function? E.g. leftopen(0..1)
Ah, it can be a function. I was just thinking of @SVector [1,2,3].
@SVector needs to be a macro since it detects the number of arguments. Otherwise it would just be SVector([1,2,3])
To me leftopen(0..1) seems like a good option.
In https://github.com/JuliaMath/IntervalSets.jl/pull/125 original version I made closed and open interval approximately equal. Didn't even consider that could be controversial, but turned out there are two totally reasonable but opposite PoVs in this regard.
Maybe, it's best just to keep this comparison undefined, as it is now? I would be extremely surprised by OpenInterval(1, 2) not being approximately equal to ClosedInterval(1, 2) (ie if isapprox returned false).
Helper functions that change closedness would help users both here and elsewhere.
Btw, a nice and general syntax for changing closedness (or anything else!) is
julia> using IntervalSets, Accessors
julia> i = 2..5;
julia> @set first(closedendpoints(i)) = false
2..5 (open–closed)
Even cleaner if functions like leftclosed(i)::Bool are introduced. Then the interface would be @set leftclosed(i) = true.