IntervalArithmetic.jl icon indicating copy to clipboard operation
IntervalArithmetic.jl copied to clipboard

Inconsistency with `in(x, ::Interval)` and `iterate(::Interval)`

Open goretkin opened this issue 5 years ago • 4 comments

(originally from https://github.com/JuliaReach/ReachabilityAnalysis.jl/pull/373#issuecomment-733224612) From doing ?in

  in(item, collection) -> Bool
  ∈(item, collection) -> Bool
  ∋(collection, item) -> Bool

  Determine whether an item is in the given collection, in the sense that it is == to one of the values generated by iterating over the collection. Returns a Bool value,
  except if item is missing or collection contains missing but not item, in which case missing is returned (three-valued logic
  (https://en.wikipedia.org/wiki/Three-valued_logic), matching the behavior of any and ==).

  Some collections follow a slightly different definition. For example, Sets check whether the item isequal to one of the elements. Dicts look for key=>value pairs, and the
  key is compared using isequal. To test for the presence of a key in a dictionary, use haskey or k in keys(dict). For these collections, the result is always a Bool and
  never missing.

To help explain, I'll define

my_in(element, collection) = any(==(element),  collection)

which perhaps is more clear as

my_in(element, collection) = any( element == x for x in collection)

and "consistency", and following the documentation of in, means that one of two should be the case:

  1. my_in errors
  2. my_in returns the same answer as in

Interval of IntervalArithmetic.jl wants to both be set and a <:Number:

julia> supertypes(typeof(Interval(0,1)))
(Interval{Float64}, AbstractInterval{Float64}, Real, Number, Any)

and it's a fact of life (unfortunately? fortunately?) that <:Numbers are singleton collections of themselves: https://github.com/JuliaLang/julia/blob/6614645892f03915e4f10b051df9a228c980abc8/base/number.jl#L233 and so

julia> in(0.5, Interval(0, 1))
true

julia> my_in(0.5, Interval(0, 1))
false

One possibility is to just document this. I'd be curious to see if it wreaks any havoc to define new methods on Interval (iterate, length, first, last, maybe copy, ...) that error. This is safer and stricter, and ensures consistency with in.

goretkin avatar Nov 24 '20 20:11 goretkin

OK, fair point I guess. In some sense we're punning on the meaning of in. But clearly anybody would expect 0.5 ∈ (0..1) to be true. This is another instantiation of #2 I guess. See also https://github.com/gwater/NumberIntervals.jl

dpsanders avatar Nov 24 '20 21:11 dpsanders

That is the only reasonable definition of in, I agree, and furthermore it's consistent with a bunch of other set operations defined on Interval, like intersect (and to a lesser extent union and setdiff).

I don't propose another definition of in, just possibly another definition of iterate, etc. so that those methods error.

goretkin avatar Nov 24 '20 22:11 goretkin

Thanks for pointing me to https://github.com/gwater/NumberIntervals.jl . I suspected it would have the same inconsistency since NumberInterval <: Number (by way of <: AbstractFloat), and indeed:

julia> using NumberIntervals: NumberInterval

julia> in(0.5, NumberInterval(0.0, 1.0))
true

julia> my_in(element, collection) = any(==(element),  collection)
my_in (generic function with 1 method)

julia> my_in(0.5, NumberInterval(0.0, 1.0))
missing

goretkin avatar Nov 24 '20 23:11 goretkin

I was curious about other interval packages, and it seems like the three others I found are not <:Number and so do not run into this inconsistency.

script:

import Intervals
import IntervalSets
import ClosedIntervals
import NumberIntervals
import IntervalArithmetic

my_in(element, collection) = any(==(element),  collection)

function exception_wrap(f)
    function wrapped(args...)
        try
            return (Some(f(args...)), nothing)
        catch e
            return (nothing, e)
        end
    end
    return wrapped
end

function test_in(needle, haystack)
    _my_in = exception_wrap(my_in)
    mi = _my_in(needle, haystack)
    i = in(needle, haystack)
    return (i, mi)
end

intervals = Any[ # avoid https://github.com/JuliaIntervals/IntervalArithmetic.jl/issues/426
    Intervals.Interval(0, 1),
    IntervalSets.Interval(0, 1),
    ClosedIntervals.ClosedInterval(0, 1),
    NumberIntervals.NumberInterval(0, 1),
    IntervalArithmetic.Interval(0, 1),
]

println(stdout, "\n-----\n")
show(stdout, "text/plain", test_in.(Ref(0.5), intervals))
println(stdout, "\n-----\n")
show(stdout, "text/plain", test_in.(Ref(1.5), intervals))

goretkin avatar Nov 25 '20 03:11 goretkin