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
Ensure Distribution can be used in Latitude and Longitude #14421
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -311,6 +311,9 @@ def view(self, dtype=None, type=None): | |
|
||
# Override __getitem__ so that 'samples' is returned as the sample class. | ||
def __getitem__(self, item): | ||
if isinstance(item, Distribution): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thing it might be better to use duck-typing here - will make a follow-on PR with a proposal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. after looking more closely I realize this is a bigger topic that's not worth worrying about right now, because it would be more consistent to do this with all the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed. While usually a big fan of duck-typing, I think in this instance, |
||
# Required for in-place operations like dist[dist < 0] += 360. | ||
return self.distribution[item.distribution] | ||
result = super().__getitem__(item) | ||
if item == "samples": | ||
# Here, we need to avoid our own redefinition of view. | ||
|
@@ -320,6 +323,30 @@ def __getitem__(self, item): | |
else: | ||
return result | ||
|
||
def __setitem__(self, item, value): | ||
if isinstance(item, Distribution): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (same here as above) |
||
# Support operations like dist[dist < 0] = 0. | ||
self.distribution[item.distribution] = value | ||
else: | ||
super().__setitem__(item, value) | ||
|
||
# Override __eq__ and __ne__ to pass on directly to the ufunc since | ||
# otherwise comparisons with non-distributions do not work (but | ||
# deferring if other defines __array_ufunc__ = None -- see | ||
# numpy/core/src/common/binop_override.h for the logic; we assume we | ||
# will never deal with __array_priority__ any more). Note: there is no | ||
# problem for other comparisons, since for those, structured arrays are | ||
# not treated differently in numpy/core/src/multiarray/arrayobject.c. | ||
def __eq__(self, other): | ||
if getattr(other, "__array_ufunc__", False) is None: | ||
return NotImplemented | ||
return np.equal(self, other) | ||
|
||
def __ne__(self, other): | ||
if getattr(other, "__array_ufunc__", False) is None: | ||
return NotImplemented | ||
return np.not_equal(self, other) | ||
|
||
|
||
class _DistributionRepr: | ||
def __repr__(self): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
"""Test that Distribution works with classes other than ndarray and Quantity.""" | ||
|
||
import numpy as np | ||
import pytest | ||
from numpy.testing import assert_array_equal | ||
|
||
import astropy.units as u | ||
from astropy.coordinates import Angle, Latitude, Longitude | ||
from astropy.uncertainty import Distribution | ||
|
||
|
||
class TestAngles: | ||
@classmethod | ||
def setup_class(cls): | ||
cls.a = np.arange(27.0).reshape(3, 9) | ||
cls.d = Distribution(cls.a) | ||
cls.q = cls.a << u.deg | ||
cls.dq = Distribution(cls.q) | ||
|
||
@pytest.mark.parametrize("angle_cls", [Angle, Longitude, Latitude]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe should add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
def test_as_input_for_angle(self, angle_cls): | ||
da = angle_cls(self.dq) | ||
assert isinstance(da, angle_cls) | ||
assert isinstance(da, Distribution) | ||
assert_array_equal(da.distribution, angle_cls(self.q)) | ||
|
||
@pytest.mark.parametrize("angle_cls", [Angle, Longitude, Latitude]) | ||
def test_using_angle_as_input(self, angle_cls): | ||
a = angle_cls(self.q) | ||
da = Distribution(a) | ||
assert isinstance(da, angle_cls) | ||
assert isinstance(da, Distribution) | ||
|
||
# Parametrize the unit to check the various branches in Latitude._validate_angles | ||
@pytest.mark.parametrize("dtype", ["f8", "f4"]) | ||
@pytest.mark.parametrize( | ||
"value", [90 * u.deg, np.pi / 2 * u.radian, 90 * 60 * u.arcmin] | ||
) | ||
def test_at_limit_for_latitude(self, value, dtype): | ||
q = u.Quantity(value, dtype=dtype).reshape(1) | ||
qd = Distribution(q) | ||
ld = Latitude(qd) | ||
assert_array_equal(ld.distribution, Latitude(q)) | ||
|
||
# Parametrize the unit in case Longitude._wrap_at becomes unit-dependent. | ||
@pytest.mark.parametrize("dtype", ["f8", "f4"]) | ||
@pytest.mark.parametrize( | ||
"value", [360 * u.deg, 2 * np.pi * u.radian, 360 * 60 * u.arcmin] | ||
) | ||
def test_at_wrap_angle_for_longitude(self, value, dtype): | ||
q = u.Quantity(value, dtype=dtype).reshape(1) | ||
qd = Distribution(q) | ||
ld = Longitude(qd) | ||
assert_array_equal(ld.distribution, Longitude(q)) | ||
assert np.all(ld.distribution == 0) | ||
|
||
@pytest.mark.parametrize("angle_cls", [Longitude, Latitude]) | ||
def test_operation_gives_correct_subclass(self, angle_cls): | ||
# Lon and Lat always fall back to Angle | ||
da = angle_cls(self.dq) | ||
da2 = da + da | ||
assert isinstance(da, Angle) | ||
assert isinstance(da, Distribution) | ||
|
||
@pytest.mark.parametrize("angle_cls", [Longitude, Latitude]) | ||
def test_pdfstd_gives_correct_subclass(self, angle_cls): | ||
# Lon and Lat always fall back to Angle | ||
da = angle_cls(self.dq) | ||
std = da.pdf_std() | ||
assert isinstance(std, Angle) | ||
assert_array_equal(std, Angle(self.q.std(-1))) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Ensure that ``Distribution`` can be compared with ``==`` and ``!=`` | ||
with regular arrays or scalars, and that inplace operations like | ||
``dist[dist<0] *= -1`` work. |
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.
It looks like since this was written there might be a fix in a future numpy for this - might want to add a todo note aobut that
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.
xref numpy/numpy#23247