Less than approx equals? <≈

leaq(a,b) = (a <= b) || (a ≈ b)

leaq(a,b,c) = leaq(a,b) && leaq(b,c)

Is there a better way of doing this? The only alternative I can think of is to subtract some epsilon from the lesser part of the inequality based on machine precision? I feel like it should be in Base, but I’m sure there’s a good reason why it isn’t.

5 Likes

isapprox is mainly used for tests. What is approximate <= useful for? Without knowing the application, it’s hard to prescribe its behavior.

2 Likes

Sure so I recently was doing point of intersection of 2 line segments.

unit_test

in this case, we have a line that has the same Y value at both endpoints. So one might want to ensure that a given point of intersection is:
A.y <= PoI.y <= B.y

So say A.y = 1.5000000000001 and B.y = 1.4999999999999999 and PoI.y = 1.5
It will fail the check!
but, A.y <≈ PoI.y <≈ B.y
would work just fine.
Does that make sense? Sorry if this is still unclear.

1 Like

I saw https://github.com/lairez/ExactPredicates.jl awhile ago which seems like it might be of interest to you

3 Likes

Cool package! Unfortunately, it doesn’t have a lot of what I need. For example, the meet function doesn’t return the point of intersection. In this case, it was ~10 LOC to just write it myself.

Predominately interested in the OP though. This example is just for fun but I’ll be running into more of this as time progresses on this project.

1 Like

Less than approx equals?

LazySets.jl internally uses LazySets._leq; you can check the definition in src/Utils/comparisons.jl, and the approximate equality can be changed with the set_x functions where x is atol, rtol or ztol. For example:

# approximate membership test
julia> [1.5, 1.5] ∈ LineSegment([1.5, 1.50000000000001], [1.5, 2.0])
true

# change the default Float64 tolerance
julia> r = LazySets._rtol(Float64);

julia> LazySets.set_rtol(Float64, 1e-20)
1.0e-20

julia> [1.5, 1.5] ∈ LineSegment([1.5, 1.50000000000001], [1.5, 2.0])
false

julia> LazySets.set_rtol(Float64, r); # revert change

For the intersection of line segments,

using LazySets, Plots

α = 1.5000000000001
β = 1.4999999999999999
A = LineSegment([1.0, β], [2.0, β])
B = LineSegment([1.5, α], [1.5, 2])
plot(A, lab="A")
plot!(B, lab="B")

LazySets.set_rtol(Float64, 1e-12)
@show intersection(A, B)
LazySets.set_rtol(Float64, 1e-20)
@show intersection(A, B)

intersection(A, B) = Singleton{Float64,Array{Float64,1}}([1.5, 1.5])
intersection(A, B) = EmptySet{Float64}(2)

(Note: for these examples you’ll need to checkout the branch mforets/robust_in_linesegment until it’s been merged :wink: )

1 Like

Cool mforets. So I checked for that branch and don’t see it in there, also checked your fork and didn’t see it in there, might still be local? But, that’s okay I’m happy with my code except how I handled <≈. That being said, it’s probably the solution I’ll use at this point, otherwise, I’d have to deal with 100s of hrs of politics/PRs when I’m just messing around… For a hobby - that’s definitely not optimal on the fun scale. So no need to race to do stuff, I’m not making a package, flag-planting, or stepping on toes here…

I quickly looked at the code for _leq in LazySets but I didn’t really understand it. Is it overloading from Base? Seems a little risque compared to just defining a new operator with different semantics. It seems like you’ve run into this issue as well though, so at least I’m not alone. What are your thoughts about introducing a <≈ operator into Julia Base? Is it too specialized?

I don’t know, if it should actually be defined in Base, but I do think allowing <≈ and >≈ as operators might be useful.

1 Like

The operator makes perfect sense to me, but why not just define it with < instead of <=:

(a < b) || (a ≈ b)
1 Like

good point, that is more concise.

Oops, it’s already been merged, and the branch deleted. You can checkout the master branch or wait to the next patch release, probably tomorrow…

I quickly looked at the code for _leq in LazySets but I didn’t really understand it. Is it overloading from Base?

See this function, that I copy here for convenience:

function _leq(x::N, y::N;
              rtol::Real=_rtol(N),
              ztol::Real=_ztol(N),
              atol::Real=_atol(N)) where {N<:AbstractFloat}
    return x <= y || _isapprox(x, y, rtol=rtol, ztol=ztol, atol=atol)
end

It doesn’t overload functions from Base, it simply uses an internal _isapprox for approximate equality comparisons, with tolerances that can be set at once (globally) for all operations. Some problems require very small tolerances, for other problems you can work with bigger tolerances so it’s useful to be able to change everything with one command (like tests for disjointness, inclusion, membership, etc).

What are your thoughts about introducing a <≈ operator into Julia Base? Is it too specialized?

I think that’s not justified, the building blocks are already in Base, and the actual meaning of approximate <= depends a lot on the application (as suggested above).

1 Like

Cool, well I may use this package for future stuff. For now, think I’ll go with the lightweight thing, but thank you for the discussion and the philosophy.

1 Like

I wouldn’t do that. Keep it (a <= b) || (a ≈ b) as there’s no downsides to it when the values are the same. Changing to < only saves one byte in the source code, but not in the assembly, and will be slower when values are equal, then you need to call to long function (that you will not see in the assembly as not inlined):

julia> @code_native isapprox(1.0, 1.0)
3 Likes

True, but if you are using this means that you already expect the values to not be exactly equal. If there is any noise involved, the chance of two floating points evaluating false for < but true for <= already get very low. Will be consistently faster only for people doing integer arithmetic on floating points.

You don’t know who will be calling code that potentially uses <≈, or why they do it.

Will be no worse, and potentially better, in each case.

1 Like

I can think of checking approximate feasibility of a solution to a constrained problem, e.g. checking that Ax <= b up to some tolerance.

3 Likes

I would use something like this. But why not allowing additional kwargs?

leaq(a, b; kwargs...) = (a <= b) || isapprox(a, b; kwargs...)

Edit: added semicolon, thanks @mtsch

1 Like

You need to use ; when splatting kwargs.

leaq(a, b; kwargs...) = (a <= b) || isapprox(a, b; kwargs...)

What you wrote tres to splat kwargs as positional arguments.

3 Likes

It would be pretty awesome to be able to make functions from 2 or more symbols. I ran into this when I was trying to do approximate greater than and approximate lesser than functions. Something tells me the julia parser can’t handle this? Does anyone know differently?

Why not use and or and , which are all valid infix comparison operators in Julia?

I suppose you mean binary operators and not just “functions” — binary operators in Julia can’t be arbitrary combinations of symbols, but you have a huge list of Unicode operators to choose from — far more than in any other mainstream language — and can additionally suffix them in various ways (e.g. <ᵃ and <′). See also this issue on custom infix operators.

5 Likes