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

Add absolute tolerance when comparing FITS #4729

Merged
merged 10 commits into from
Feb 17, 2017
Merged

Add absolute tolerance when comparing FITS #4729

merged 10 commits into from
Feb 17, 2017

Conversation

elehcim
Copy link
Contributor

@elehcim elehcim commented Mar 24, 2016

This changes a bit the interface: tolerance -> reltol
Added abstol keyword argument.

This is particularly useful when numbers to compare are very little. I'm exploiting the underlying numpy function numpy.allclose which can be used also with absolute tolerance.

@pllim pllim added io.fits Affects-dev PRs and issues that do not impact an existing Astropy release labels Mar 24, 2016
@pllim
Copy link
Member

pllim commented Mar 24, 2016

Need a change log. Also...

👍 on more controls on the accuracy. However, this changes the API. I think usually we issue a deprecation warning first for a while, and then really change it. So might need that and a way for backward compatibility here, at least temporarily. I think this needs some further discussions between core maintainers.

@pllim
Copy link
Member

pllim commented Mar 24, 2016

inserts magical @embray incantation

@embray
Copy link
Member

embray commented Mar 25, 2016

This should keep tolerance as an alias for the existing functionality, but I otherwise agree that where needed it's good to have separate absolute and relative tolerances passed through.

@embray embray added Affects-release and removed Affects-dev PRs and issues that do not impact an existing Astropy release labels Mar 25, 2016
@embray
Copy link
Member

embray commented Mar 25, 2016

"Affects-release" means "changes functionality in existing release"


abs(x1 - x2) > abstol + abs(x1)*reltol

are considered to be different
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silly nitpick but missing a period at the end here.

@elehcim
Copy link
Contributor Author

elehcim commented Mar 25, 2016

Thanks for the comments.
I'd propose something like the following:

def __init__(self, a, b, ignore_keywords=[], ignore_comments=[],
             ignore_fields=[], numdiffs=10, reltol=0.0, abstol=0.0,
             ignore_blanks=True, ignore_blank_cards=True, tolerance=None):

    [...]
    self.reltol = reltol
    self.abstol = abstol
    if tolerance is not None:
        warn("Named argument `tolerance` is deprecated and will be removed in future releases.
              Use `reltol` instead.")
        if reltol == 0.0:
            self.reltol = tolerance

Is it ok to use reltol in case FITSDiff(reltol=1e-2, tolerance=1e-3)?
Whats your standard way to warn the user of a deprecated syntax?
I'm pushing some more code, you can comment on that.

@elehcim
Copy link
Contributor Author

elehcim commented Mar 25, 2016

Ops, I forgot to update the other __init__ methods 😅. Before I proceed, please comment on the idea.

@pllim
Copy link
Member

pllim commented Mar 25, 2016

@embray, sorry. 😅

I thought "affect release" means we will need to backport or something. I still can't find a documentation on how to properly use the "affect" tags. At one point I had to dig through a bunch of issues for that ASCII flowchart you made buried in the comments. But that's about it.

Maybe for now, I'll just not touch those tags anymore...

@embray
Copy link
Member

embray commented Mar 29, 2016

On one hand I usually would push against having more than one way to do it, and I'd be okay with deprecating tolerance. On the other hand, I know there are enough tests at STScI using this that I don't want to be cropping up from beyond the grave 👻 to cause hair-pulling and headaches over that.

As a compromise I think we could deprecate the tolerance keyword argument to FITSDiff (since I think the API is used less frequently, but we might want to check on that--@pllim?) but keep the existing options in the command-line arguments (without a deprecation warning).


if tolerance is not None:
warnings.warn("Named argument `tolerance` is deprecated "
"and will be removed in future releases. Use `reltol` instead.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be formatted a bit difference. Grep the source for AstropyDeprecationWarning for some other examples.

@mcara
Copy link
Contributor

mcara commented Mar 29, 2016

I like the approach that is taking shape including deprecating tolerance parameter. Except, for a little bit more clarity, I would suggest the following change to the logic [the confusion may arise from the fact that in the current implementation tolerance is sometimes used and sometimes ignored]:

    if tolerance is not None:
        warnings.warn("Named argument `tolerance` is deprecated "
                      "and will be removed in future releases. Use `reltol` instead. "
                      "Ignoring `reltol` value...")

       self.reltol = tolerance # when tolerance is provided *always* ignore `reltol`
                               # during the transition/deprecation period

@pllim
Copy link
Member

pllim commented Apr 18, 2016

I know there are enough tests at STScI using this that...

@embray , no one from SSB raised any objection thus far. So it's on them if they run into problem in the future.

👍 to this change.

@astrofrog
Copy link
Member

Numpy uses the convention atol and rtol instead of abstol and reltol - does anyone have any thoughts on whether to follow this?

@mcara
Copy link
Contributor

mcara commented May 10, 2016

@astrofrog I am for following numpy

@astrofrog
Copy link
Member

astrofrog commented May 12, 2016

@elehcim - could you change abstol to atol and reltol to rtol? (if you can make this change by the end of tomorrow, we can include this in Astropy 1.2)

@elehcim
Copy link
Contributor Author

elehcim commented May 17, 2016

@astrofrog I've changed tolerance names and implemented some comments noted on the discussion above.

@embray
Copy link
Member

embray commented May 17, 2016

I actually liked @elehcim's original names over Numpy's--they're much clearer, and it's obvious enough how it maps onto the underlying Numpy function. But I'm not going to bikeshed any more than that. Decide amongst yourselves.

@pllim
Copy link
Member

pllim commented Dec 2, 2016

This needs a rebase. And if the coverage decrease still shows up after, we need to figure out why.

@elehcim
Copy link
Contributor Author

elehcim commented Dec 3, 2016

Rebased. Waiting for continuous integration checks.

@@ -352,7 +378,8 @@ def _report(self):
wrapper.fill(ignore_fields)))
self._writeln(u(' Maximum number of different data values to be '
'reported: {}').format(self.numdiffs))
self._writeln(u(' Data comparison level: {}').format(self.tolerance))
self._writeln(u(' Relative tolerance: {},'
' Absolute tolerance: {}}').format(self.rtol, self.atol))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a superfluous } here causing all kinds of testing failures

@@ -262,6 +276,9 @@ def __init__(self, a, b, ignore_keywords=[], ignore_comments=[],
ignore_blank_cards : bool, optional
Ignore all cards that are blank, i.e. they only contain
whitespace (default: True).

tolerance : float, optional
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecation of tolerance should now follow the same implementation similar to #5171, utilizing a new decorator added in #5214.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's possible because the parameter was replaced by 2 new parameters. I haven't considered such a case. 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MSeifert04 , I disagree. tolerance is replacing rtol, and atol is a new feature.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But atol is added in between existing parameters so it will permanently change the function signature. Say you call the function with positional parameters only then the old ignore_blanks will now be interpreted as atol.

But you're right for the tolerance -> rtol change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. That is too bad.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not so "nice" but we could append "atol" at the end of the function signature. Then this is completly backwards-compatible. And of course using deprecated_renamed_argument for the tolerance -> rtol change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think deprecated parameters should be added to the docstring. Probably best to remove it completly, the homepage contains the documentation for older versions and with the directive in rtol about the renaming it should be fine, right?

@pllim
Copy link
Member

pllim commented Dec 5, 2016

Sphinx test failed on Travis CI; I don't see that failure on master. I cancelled the Mac build.

@elehcim elehcim mentioned this pull request Feb 12, 2017
3 tasks
@bsipocz
Copy link
Member

bsipocz commented Feb 12, 2017

@elehcim - I'm afraid you've added the changelog to the wrong section. This should go in to 2.0 and not 1.3.1 (as the latter is a bugfix release and that by definition doesn't contain enhancements or new deprecations).

@bsipocz bsipocz added this to the v2.0.0 milestone Feb 12, 2017
@elehcim
Copy link
Contributor Author

elehcim commented Feb 13, 2017

Ok, rebased and changed target version, thank you @bsipocz.
Reviewers, please have a look at it. @pllim, @MSeifert04

Copy link
Member

@pllim pllim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates. My review is attached.


.. math::

\\left| x_1 - x_2 \\right| > \\text{atol} + \\text{rtol} \\cdot \\left| x_1 \\right|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just following the formula from numpy.allclose is less confusing, since the keyword names are the same anyway.

are considered to be different.

atol : float, optional
The allowed absolute difference. See also rtol parameter.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Double backticks around rtol.

@@ -262,6 +274,9 @@ def __init__(self, a, b, ignore_keywords=[], ignore_comments=[],
ignore_blank_cards : bool, optional
Ignore all cards that are blank, i.e. they only contain
whitespace (default: True).

tolerance : float, optional
Alias of rtol. Deprecated, provided for backward compatibility.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Double backticks around rtol.

self.rtol = rtol
self.atol = atol

if tolerance is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a in-code comment that this can be removed for v3.1 and after? Is that numbering right, @mhvk? Sorry, I am still learning the proper deprecated code removal policy.

@@ -43,7 +43,7 @@
Example
-------

fitsdiff -k filename,filtnam1 -n 5 -d 1.e-6 test1.fits test2
fitsdiff -k filename,filtnam1 -n 5 -r 1.e-6 test1.fits test2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #5799.

@@ -252,7 +266,8 @@ def main():
opts.ignore_keywords = []
opts.ignore_comments = []
opts.ignore_fields = []
opts.tolerance = 0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, wait. Now I am confused. In the parser above, there is a dest="rtol" for -d option. But then, it is no handling for when both -d and -r are defined. And then below, somehow there is a tolerance=opts.tolerance option. I think all these need some cleaning up.

diff = HeaderDiff(ha, hb, rtol=1e-5, atol=1e-5)
assert diff.identical
diff = HeaderDiff(ha, hb, atol=1.1e-5)
assert diff.identical
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I would like to see such a test as well, where both rtol and tolerance are defined, but the latter overrides the former.

assert diff.identical
assert diff.diff_total == 0

def test_not_identical_within_rtol_and_atol(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name implies that you are testing for "not identical" but then the actual test does the opposite. This is very confusing. Same comment for the test that follows below.

@elehcim
Copy link
Contributor Author

elehcim commented Feb 15, 2017

Hello @pllim, I think I managed to implement all your comments, thank you very much.
I cannot see the link between the travis failures and this PR... do you have any idea?

@bsipocz
Copy link
Member

bsipocz commented Feb 15, 2017

I've restarted the failing builds.

@pllim
Copy link
Member

pllim commented Feb 15, 2017

Two of them still failed with URLError. I restarted them again.

@elehcim
Copy link
Contributor Author

elehcim commented Feb 15, 2017

🎉 travis did his job

Copy link
Member

@pllim pllim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you added a test to the fitsdiff command too, nice! Just a minor comment from me.

@MSeifert04 , do you want to look at this one last time?

@@ -247,12 +261,25 @@ def main():

opts, args = handle_options(argv)

if opts.tolerance is not None and opts.rtol is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the codes above, tolerance overwrites rtol with a warning, but here it is outright error. Why the difference in behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, maybe it's better to issue an AstropyDeprecationWarning and let tolerance overwrite rtol

from . import FitsTestCase
from ..hdu import PrimaryHDU
from ..scripts import fitsdiff
from ....tests.helper import pytest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to @astrofrog , you'll need to change this too for #5694 if this gets merged first.

Copy link
Contributor

@MSeifert04 MSeifert04 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I added a few additional comments and I'm not exactly sure the script is working as intended. Not because it seems wrong but just because I'm not familiar with scripts 😄

\\left| a - b \\right| > \\text{atol} + \\text{rtol} \\cdot \\left| b \\right|

are considered to be different.
The underlying function used for comparison is `numpy.allclose`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a .. versionchanged:: 2.0-directive here explaining the change of parameter name tolerance->rtol?

@@ -262,6 +276,9 @@ def __init__(self, a, b, ignore_keywords=[], ignore_comments=[],
ignore_blank_cards : bool, optional
Ignore all cards that are blank, i.e. they only contain
whitespace (default: True).

tolerance : float, optional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think deprecated parameters should be added to the docstring. Probably best to remove it completly, the homepage contains the documentation for older versions and with the directive in rtol about the renaming it should be fine, right?

self.atol = atol

if tolerance is not None: # This should be removed in the next astropy version
warnings.warn('"tolerance" was '
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a PEP-8-nitpick but that seems like odd-indentation to me. Feel free to keep it as is but I would prefer:

        warnings.warn(
            '"tolerance" was deprecated in version 2.0 and will be removed in '
            'a future version. Use argument "rtol" instead.',
            AstropyDeprecationWarning)

because the hanging indentation is the same for all strings and parameters here. Same for the other occurances of this warning.

"""

if isinstance(a, float) and isinstance(b, float):
if np.isnan(a) and np.isnan(b):
return False
return not np.allclose(a, b, tolerance, 0.0)
return not np.allclose(a, b, rtol, atol)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you pass them in by name here as well? rtol=rtol, atol=atol? That's what is done oion the other places as well.

… `fitsdiff`

* Add versionchanged directive
* Removed deprecated argument from docstring
* Fix indentation
@elehcim
Copy link
Contributor Author

elehcim commented Feb 16, 2017

Thank you @MSeifert04 for your review.
What are your doubts regarding the script?

@MSeifert04
Copy link
Contributor

What are your doubts regarding the script?

No specific doubts, it's just that these scripts are not covered by the test suite and I firmly believe that "untested code is broken code". 😄 (That doesn't mean that every case has to be tested but if there is no test at all then it may break down at any point).

Copy link
Contributor

@MSeifert04 MSeifert04 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@pllim
Copy link
Member

pllim commented Feb 17, 2017

So, we got two approvals, but CircleCI failed. I am not familiar with CircleCI (the log wouldn't load for me), can someone please confirm whether failure is related or not?

untested code is broken code

@MSeifert04 , that sound a little extreme as it implies 100% coverage to be a requirement. 😅

@astrofrog
Copy link
Member

I've restarted CircleCI.

Untested code is broken code - anytime I increase coverage I find bugs! It's just a question of whether they are critical bugs :)

@mcara
Copy link
Contributor

mcara commented Feb 17, 2017

anytime I increase coverage I find bugs!

That's hilarious!

@pllim
Copy link
Member

pllim commented Feb 17, 2017

The PR started back in March 2016. 😅 Time to merge! Thank you for your patience, @elehcim !

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

Successfully merging this pull request may close these issues.

8 participants