Implement __eq__ and __ne__ for coordinate classes#10154
Implement __eq__ and __ne__ for coordinate classes#10154taldcroft merged 14 commits intoastropy:masterfrom
Conversation
|
I do think we should have comparisons, but I wonder about just returning |
astropy/coordinates/baseframe.py
Outdated
| if self._data is None and value._data is None: | ||
| return True | ||
|
|
||
| if self.shape != value.shape: |
There was a problem hiding this comment.
Independently of my larger comment, I would allow different shapes (but catch the possible broadcast error and return False), i.e., sc[0] == sc should be possible.
astropy/coordinates/baseframe.py
Outdated
| if isinstance(eq, bool): | ||
| return not eq | ||
| else: | ||
| return ~eq |
There was a problem hiding this comment.
Wow, never appreciated that ~True equals -2!
There was a problem hiding this comment.
what? how? why? (I could see it it were -1, but -2?)
There was a problem hiding this comment.
As @jdavies-st explained it to me, ~ flips the bits and somehow True is treated as int when ~ is applied.
>>> np.binary_repr(1, width=8)
'00000001'
>>> np.binary_repr(-2, width=8)
'11111110'There was a problem hiding this comment.
Isn't __ne__ automagically defined if __eq__ is defined in python3? Does __eq__ as defined above ever return something other than True or False?
Btw, I'm really happy to see __eq__ for strict equality implemented for SkyCoord. Will make testing much cleaner. 🎉
There was a problem hiding this comment.
I'm not sure the automagic __ne__ would be numpy-aware, which is necessary since it can return a numpy array of bool type. Also, SkyCoord already has a __ne__ method that comes from somewhere but I honestly don't know where.
In [7]: sc.__ne__??
Signature: sc.__ne__(value, /)
Call signature: sc.__ne__(*args, **kwargs)
Type: method-wrapper
String form: <method-wrapper '__ne__' of SkyCoord object at 0x7f7f29204850>
Docstring: Return self!=value.
There was a problem hiding this comment.
I see. That makes sense.
In [13]: ~np.array([True, True, False])
Out[13]: array([False, False, True])
Thanks. I'd forgot that ~ acting on numpy boolean arrays is the same as np.logical_not. 👍
There was a problem hiding this comment.
Thanks for the reminder on np.logical_not. That works on both scalar and np.array values so I got rid of that ugly code.
Agreed. I'll make that change. I wasn't entirely happy with
I'm not sure how that helps in this case since we are stipulating that no transforms are allowed. So at best you can get a false positive where the representation values are extremely close but not equal, and after doing the separation math going down to a single float you wind up with exact equality. Also separations are just spatial and don't have visibility into velocities AFAIK.
Good idea. Along with the first point (raise, not False), this might point to factoring out all or most of the validation checks, maybe to private |
|
BTW, about |
|
Indeed, and people might want different definitions of closeness in distance than in angular coordinates. It is also a bit similar to |
| with pytest.raises(ValueError, match='unable to compare column c'): | ||
| t1.values_equal([1, 2]) | ||
|
|
||
| with pytest.raises(TypeError, match='comparison for column sk'): |
There was a problem hiding this comment.
This used to fail, no more!
|
Fingers crossed on CI tests (passes locally) but this should be mostly done and ready for review if anyone has the inclination. BTW I'm not planning to work on |
|
So now I have some higher-level questions. It turns out that this change caused real test failures in WCS. At some level I think that the failures are good because they revealed code which could be improved. But also point to ways that this PR as it stands might cause breakage in the real world. The comparisons in WCS are: astropy/astropy/visualization/wcsaxes/core.py Line 586 in d91f444 Boiling down the issue:
Other considerations:
|
b15724d to
0412d6c
Compare
|
Part of the issue is that python and numpy have different ideas about what Right now, in |
|
p.s. If we're not raising on unequal frames, I think in any case where we returning |
|
@mhvk - the current version stopped short of the slippery slope and only will allow the comparison of unequal frames if they both have no data. I do feel like In the current version one does still get a TypeError for And for BTW, I have been seeing the various numpy deprecations for what seems like years. When is that going to finally happen? |
|
@astrofrog - this PR required making some small changes in visualizations/wcsaxes. Git blame says this code is yours so it would be nice if you can review them. |
|
Humm, Travis seems to be unhappy, twice in a row I got the "oldest dependencies" job running over an hour and finally quitting. The corresponding tox test runs locally just fine. |
|
@taldcroft , the old deps job should be fixed once #10164 is merged. Just need approval... |
|
The CI failure workaround has been merged, so a rebase should fix travis here. |
0412d6c to
a89e382
Compare
eteq
left a comment
There was a problem hiding this comment.
Generally I'm happy with this. While I think I originally was opposed to equality like this, time has shown the value of this strict definition requiring exact equality of the data. Some caveats/concerns I have (all of which I think are not major deals/blockers, except possibly the last one which requires agreement).
I think we need to show the user how to do a "tolerance" comparison (I made an inline suggestion you can just accept as-is if you agree, @taldcroft) as early as possible. That was the original reason for disallowing this, to force them to encounter that, so I'm thinking we want to show it as prominently as practical.
Re: the discussion of False's vs the more numpy-like result of failing when comparisons are invalid: I think I like the behavior as it stands right now - I do see it as reasonably consistent with Time (which I think is what @mhvk was saying?) while still having some of the more practical comparisons like the FK4()==ICRS() case work is indeed a compromise but one I think that makes sense for the actual likely things people will do with this operator. So 👍 to that behavior as-is.
However, I noticed the following behavior, which seems rather counter-intuitive:
>>> sc1=coordinates.SkyCoord(ra=0*u.deg, dec=0*u.deg, distance=1*u.kpc)
>>> sc2=coordinates.SkyCoord(sc1.data.to_cartesian(), frame='icrs')
>>> sc1==sc2
True
>>> i1=coordinates.ICRS(ra=0*u.deg, dec=0*u.deg, distance=1*u.kpc)
>>> i2=coordinates.ICRS(i1.data.to_cartesian())
>>> i1==i2
TypeError: cannot compare: objects must have same class: SphericalRepresentation vs. CartesianRepresentation
This comes down to the fact that something in the SkyCoord initializer I think is coercing the representation, which is fine but it hides the confusing behavior in the representations. This behavior becomes even more obvious as a confusion in the following:
>>> i1.data+i2.data-i2.data == i1.data
True
>>> i2.data+i1.data-i2.data == i1.data
TypeError: cannot compare: objects must have same class: CartesianRepresentation vs. SphericalRepresentation
Basically, for arithmetic we make the assumption that representations can swap types (basically they all convert to cartesian and then back if need be), but we don't allow that behavior in equality.
So I'd vote for modifying the representation equality behavior to either do direct comparison if the representations are the same (i.e., the behavior in this PR), but if they are not the same, to convert both to cartesian and also try to do the equality test there. Then the above cases I think should work (modulo possible floating-point... but at least they won't raise errors)
adrn
left a comment
There was a problem hiding this comment.
I'm happy with this, apart from some minor documentation suggestions.
Do we have an issue to keep track of the idea of "close" coordinate matching? I do think it's something worth considering (despite API challenges related to the different units), so we may want to make an issue to keep track. We may even want to add a link to the issue into the documentation added here, suggesting that anyone wanting that functionality chime in to the discussion. Thanks @taldcroft for this - I do think this is an improvement over the existing behavior!
9b4bc49 to
e7cacd0
Compare
|
OK all thanks for the review and feedback, this got to a much better place. As @adrn has approved I will merge if this can get through CI before another changelog merge conflict. |
Co-authored-by: Adrian Price-Whelan <adrianmpw@gmail.com>
|
I think this broke a test... Please see #10297 |
Description
This pull request implements a strict equality operator for coordinate classes
SkyCoord,BaseFrame,BaseRepresentationOrDifferential, andBaseRepresentation.Strict equality means that if any of the attributes don't match then an exception is raised.
The logic is inspired by #9857, having realized that this PR went most of the way to defining when these coordinate objects can be compared meaningfully using the representation data.
This will help in testing and bring consistency with the general numpy array interface. In 4.0 one gets silly things like this:
Fixes #3182