-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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: test max(atol,rtol*...) rather than atol+rtol*... #22742
Conversation
The only argument I can think of for the old behavior is performance: |
Hmm, it seems I did the wrong benchmark comparisons by comparing to 0.6. In 0.7 master (just before this PR), I get 7.85ns for that benchmark, a big improvement over 0.6. Compared to that, this PR is a 20% slowdown, which is surprising. (Anyone know why I still tend to think it is worth it, but the performance rationale for the old behavior is stronger than I expected. |
No idea about the performance change, but I agree that it seems unlikely that |
Travis failure is "The job exceeded the maximum time limit for jobs, and has been terminated.", so presumably unrelated. |
Adding this to 1.0 to make sure it is decided by then. |
Rebased. |
I'm all for it. @jiahao, @andreasnoack? Trying to think of others who haven't already thumbed this up who might care about this issue... |
I think this is an improvement but it seems that this way of testing is basically broken when we allow both tolerances to be non-zero. E.g. julia> x, y = 1e8, 1e8 + 0.5;
julia> atol = 0.1;
julia> abs(x-y) <= max(atol, Base.rtoldefault(x, y)*max(abs(x), max(y)))
true
julia> abs(x-y) <= atol
false Maybe it would be a better API to have |
If having both be non-zero is already broken it would seem better to get the design right rather than change this and then also deprecate it. Although I suppose that changing this could pre-emptively find some cases where an API change would break overly loose tests anyway. |
Thoughts from a brief chat with Three designs seem reasonable depending on what is useful in practice: A) If simultaneously enforcing absolute and relative tolerances isn't useful in practice, then some form of B) If simultaneously enforcing absolute and relative tolerances is useful in practice, then the following design seems reasonable:
... i.e. C) If both simultaneously enforcing absolute and relative tolerances and flexibility in their combination are useful in practice, then the following design seems reasonable:
Chances are scheme (C) is unnecessarily complex. One way to mitigate the complexity would be to make |
You occasionally need both tolerances. Typically in cases where you are evaluating a function at lots of points and what you mostly want is a relative tolerance, but you don't want it to unexpectedly break at points where the result happens to be nearly zero. Note that this implies max, not min. The min approach seems inherently flawed to me. |
That is, I see Andreas' "flaw" as a feature. You want it to return true if either test is satisfied, not necessarily both, in any use case I'm aware of. |
Another use case of this sort comes up in convergence tests for quadrature, where you want to halt if the estimated integral isapprox equal to a lower-order estimate. You usually want a relative tolerance, but it can be useful to specify an absolute tolerance too just in case the integral is nearly zero, to make sure it terminates. You want it to terminate when either tolerance is satisfied. (This is typical of lots of iterative methods where you specify multiple convergence criteria.) |
That makes sense but I think it is a problem that |
So that suggests C with |
Yes, perhaps then scheme (C) is in order :). |
I'm skeptical of the need for a |
We could also add a |
Cheers! Sounds like scheme (B) with
? Best! |
Updated to the proposed semantics (default |
@@ -242,7 +243,10 @@ const ≈ = isapprox | |||
# default tolerance arguments | |||
rtoldefault(::Type{T}) where {T<:AbstractFloat} = sqrt(eps(T)) | |||
rtoldefault(::Type{<:Real}) = 0 | |||
rtoldefault(x::Union{T,Type{T}}, y::Union{S,Type{S}}) where {T<:Number,S<:Number} = max(rtoldefault(real(T)),rtoldefault(real(S))) | |||
function rtoldefault(x::Union{T,Type{T}}, y::Union{S,Type{S}}, atol::Real) where {T<:Number,S<:Number} |
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.
enough packages are calling the two-argument version (despite it not being exported) that it should probably be deprecated
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.
Okay, I just pushed a deprecation.
Looks like an unrelated Travis failure ( |
The
and in #22998
are a segfault in gf.c . Has it been noticed in any other PRs? |
Bump from triage. Needs a rebase then good to go. |
Rebased. |
Nice to discover why the tests I wrote earlier today suddenly fail. I was falling in the class of problems that @stevengj described, where I test a lot of non-zero numbers, but occasionally the result is zero so I need an |
In JuliaLang/julia#22742 isapprox changed from using `atol + rtol*max(abs(x), abs(y)))` to using `max(atol, rtol*max(abs(x), abs(y))))`
In JuliaLang/julia#22742 isapprox changed from using atol + rtol*max(abs(x), abs(y))) to using max(atol, rtol*max(abs(x), abs(y))))
Ever since the
isapprox
function was merged in #3408, we have been testing something likenorm(x-y) <= atol + rtol*max(norm(x), norm(y))
. This PR changes that tonorm(x-y) <= max(atol, rtol*max(norm(x), norm(y)))
.Rationale: by adding the two tolerances when
atol ≠ 0
, we were effectively increasing the tolerance for numbers withatol
close tortol*norm(x)
, which doesn't seem desirable (though it is not terrible). Python 3's newis_close
function introduced in PEP 485 uses themax
behavior, which seems a bit more sensible.As a practical matter, I seriously doubt this will break any real code, but technically it is a breaking change.
(As an aside, the PEP contains this remark that should be gratifying to Julians: In theory, it should work for any type that supports abs() , multiplication, comparisons, and subtraction. However, the implementation in the math module is written in C, and thus can not (easily) use python's duck typing.)