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

isapprox should work for comparing open and closed intervals #129

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

dlfivefifty
Copy link
Member

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
Copy link

codecov bot commented Dec 2, 2022

Codecov Report

Base: 99.16% // Head: 99.16% // Decreases project coverage by -0.00% ⚠️

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%) ⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@hyrodium
Copy link
Collaborator

hyrodium commented Dec 2, 2022

As discussed here (#125 (comment)), I still think isapprox(ClosedInterval(0,1), OpenInterval(0,1)) should return false.

@hyrodium
Copy link
Collaborator

hyrodium commented Dec 2, 2022

We may have a type for the unconnected subset of $\mathbb{R}$ such as $[1,2] \cup (3,4]$ in the future (#106 (comment)).
Then, should $[1,2) \cup (2,4] \approx [1,4]$ return true? I think it also should be false.

@dlfivefifty
Copy link
Member Author

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.

@hyrodium
Copy link
Collaborator

hyrodium commented Dec 2, 2022

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.

@hyrodium
Copy link
Collaborator

hyrodium commented Dec 2, 2022

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 .)

@dlfivefifty
Copy link
Member Author

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.

@daanhb
Copy link
Contributor

daanhb commented Dec 2, 2022

The Base.isapprox function 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.

@hyrodium
Copy link
Collaborator

hyrodium commented Dec 3, 2022

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 isapprox is 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

@daanhb
Copy link
Contributor

daanhb commented Dec 3, 2022

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.

@daanhb
Copy link
Contributor

daanhb commented Dec 3, 2022

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.

@dlfivefifty
Copy link
Member Author

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

@daanhb
Copy link
Contributor

daanhb commented Dec 6, 2022

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.

@hyrodium
Copy link
Collaborator

hyrodium commented Dec 6, 2022

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: #39 (comment))

@dlfivefifty
Copy link
Member Author

Why a macro and not just a function? E.g. leftopen(0..1)

@hyrodium
Copy link
Collaborator

hyrodium commented Dec 7, 2022

Ah, it can be a function. I was just thinking of @SVector [1,2,3].

@dlfivefifty
Copy link
Member Author

@SVector needs to be a macro since it detects the number of arguments. Otherwise it would just be SVector([1,2,3])

@daanhb
Copy link
Contributor

daanhb commented Dec 7, 2022

To me leftopen(0..1) seems like a good option.

@aplavin
Copy link
Contributor

aplavin commented Dec 19, 2022

In #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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants