Skip to content

Commit

Permalink
Merge branch 'main' of github.com:sunpy/ndcube into remove_dimension
Browse files Browse the repository at this point in the history
  • Loading branch information
CyclingNinja committed Apr 23, 2024
2 parents 8467e17 + b1a30c8 commit 32b58c4
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 26 deletions.
1 change: 1 addition & 0 deletions changelog/678.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enable `~ndcube.NDCube` to be raised to a power.
6 changes: 3 additions & 3 deletions ndcube/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def skycoord_2d_lut(shape):
return SkyCoord(*data, unit=u.deg)


def data_nd(shape):
def data_nd(shape, dtype=float):
nelem = np.prod(shape)
return np.arange(nelem).reshape(shape)
return np.arange(nelem, dtype=dtype).reshape(shape)


def time_extra_coords(shape, axis, base):
Expand Down Expand Up @@ -330,7 +330,7 @@ def ndcube_4d_ln_l_t_lt(wcs_4d_lt_t_l_ln):
def ndcube_4d_ln_lt_l_t(wcs_4d_t_l_lt_ln):
shape = (5, 8, 10, 12)
wcs_4d_t_l_lt_ln.array_shape = shape
data_cube = data_nd(shape)
data_cube = data_nd(shape, dtype=int)
return NDCube(data_cube, wcs=wcs_4d_t_l_lt_ln)


Expand Down
29 changes: 26 additions & 3 deletions ndcube/ndcube.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,28 @@ def __rmul__(self, value):
def __truediv__(self, value):
return self.__mul__(1/value)

def __pow__(self, value):
new_data = self.data ** value
new_unit = self.unit if self.unit is None else self.unit ** value
new_uncertainty = self.uncertainty

if self.uncertainty is not None:
try:
new_uncertainty = new_uncertainty.propagate(np.power, self, self.data ** value, correlation=1)
except ValueError as e:
if "unsupported operation" in e.args[0]:
new_uncertainty = None
warnings.warn(f"{type(self.uncertainty)} does not support propagation of uncertainties for power. Setting uncertainties to None.",
UserWarning, stacklevel=2)
elif "does not support uncertainty propagation" in e.args[0]:
new_uncertainty = None
warnings.warn(f"{e.args[0]} Setting uncertainties to None.",
UserWarning, stacklevel=2)
else:
raise e

return self._new_instance_from_op(new_data, new_unit, new_uncertainty)

def to(self, new_unit, **kwargs):
"""Convert instance to another unit.
Expand Down Expand Up @@ -1144,10 +1166,11 @@ def my_propagate(uncertainty, data, mask, **kwargs):
if propagate_uncertainties:
if self.uncertainty is None:
warnings.warn("Uncertainties cannot be propagated as there are no uncertainties, "
"i.e. self.uncertainty is None.")
"i.e., the `uncertainty` keyword was never set on creation of this NDCube.")
elif isinstance(self.uncertainty, astropy.nddata.UnknownUncertainty):
warnings.warn("Uncertainty is an UnknownUncertainty which does not "
"support uncertainty propagation.")
warnings.warn("The uncertainty on this NDCube has no known way to propagate forward and so will be dropped. "
"To create an uncertainty that can propagate, please see "
"https://docs.astropy.org/en/stable/uncertainty/index.html")
elif (not operation_ignores_mask
and (self.mask is True or (self.mask is not None
and not isinstance(self.mask, bool)
Expand Down
35 changes: 31 additions & 4 deletions ndcube/tests/test_ndcube.py
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ def test_wcs_type_after_init(ndcube_3d_ln_lt_l, wcs_3d_l_lt_ln):
def test_rebin(ndcube_3d_l_ln_lt_ectime):
cube = ndcube_3d_l_ln_lt_ectime[:, 1:]
bin_shape = (10, 2, 1)
with pytest.warns(UserWarning, match="Uncertainty is an UnknownUncertainty which does not support uncertainty propagation."):
with pytest.warns(UserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"):
output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True)
output_sc, output_spec = output.axis_world_coords(wcs=output.wcs)
output_time, = output.axis_world_coords(wcs=output.extra_coords)
Expand All @@ -815,7 +815,7 @@ def test_rebin(ndcube_3d_l_ln_lt_ectime):
expected_time.format = "fits"

# Confirm output is as expected.
assert (output.shape.value == np.array([1, 2, 8])).all()
assert (output.shape == np.array([1, 2, 8])).all()
assert (output.data == expected_data).all()
assert (output.mask == expected_mask).all()
assert output.uncertainty == expected_uncertainty
Expand Down Expand Up @@ -845,7 +845,7 @@ def test_rebin_no_ec(ndcube_3d_l_ln_lt_ectime):
cube = ndcube_3d_l_ln_lt_ectime[:, 1:]
cube._extra_coords = ExtraCoords(cube)
bin_shape = (10, 2, 1)
with pytest.warns(UserWarning, match="Uncertainty is an UnknownUncertainty which does not support uncertainty propagation."):
with pytest.warns(UserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"):
output = cube.rebin(bin_shape, operation=np.mean, propagate_uncertainties=True)
assert output.extra_coords.is_empty

Expand Down Expand Up @@ -942,7 +942,7 @@ def test_rebin_no_propagate(ndcube_2d_ln_lt_mask_uncert):

cube._mask = False
cube._uncertainty = UnknownUncertainty(cube.data * 0.1)
with pytest.warns(UserWarning, match="Uncertainty is an UnknownUncertainty which does not support uncertainty propagation."):
with pytest.warns(UserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"):
output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True)
assert output.uncertainty is None

Expand Down Expand Up @@ -1124,6 +1124,33 @@ def test_cube_arithmetic_multiply_notimplementederror(ndcube_2d_ln_lt_units):
_ = ndcube_2d_ln_lt_units * ndcube_2d_ln_lt_units



@pytest.mark.parametrize('power', [2, -2, 10, 0.5])
def test_cube_arithmetic_power(ndcube_2d_ln_lt, power):
cube_quantity = u.Quantity(ndcube_2d_ln_lt.data, ndcube_2d_ln_lt.unit)
with np.errstate(divide='ignore'):
new_cube = ndcube_2d_ln_lt ** power
check_arithmetic_value_and_units(new_cube, cube_quantity**power)


@pytest.mark.parametrize('power', [2, -2, 10, 0.5])
def test_cube_arithmetic_power_unknown_uncertainty(ndcube_4d_unit_uncertainty, power):
cube_quantity = u.Quantity(ndcube_4d_unit_uncertainty.data, ndcube_4d_unit_uncertainty.unit)
with pytest.warns(UserWarning, match="UnknownUncertainty does not support uncertainty propagation with correlation. Setting uncertainties to None."):
with np.errstate(divide='ignore'):
new_cube = ndcube_4d_unit_uncertainty ** power
check_arithmetic_value_and_units(new_cube, cube_quantity**power)


@pytest.mark.parametrize('power', [2, -2, 10, 0.5])
def test_cube_arithmetic_power_std_uncertainty(ndcube_2d_ln_lt_uncert, power):
cube_quantity = u.Quantity(ndcube_2d_ln_lt_uncert.data, ndcube_2d_ln_lt_uncert.unit)
with pytest.warns(UserWarning, match=r"<class 'astropy.nddata.nduncertainty.StdDevUncertainty'> does not support propagation of uncertainties for power. Setting uncertainties to None."):
with np.errstate(divide='ignore'):
new_cube = ndcube_2d_ln_lt_uncert ** power
check_arithmetic_value_and_units(new_cube, cube_quantity**power)


@pytest.mark.parametrize('new_unit', [u.mJ, 'mJ'])
def test_to(ndcube_1d_l, new_unit):
cube = ndcube_1d_l
Expand Down
32 changes: 16 additions & 16 deletions ndcube/tests/test_ndcubesequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ def derive_sliced_cube_dims(orig_cube_dims, tuple_item):
def test_slice_sequence_axis(ndc, item):
# Calculate expected dimensions of cubes with sequence after slicing.
tuple_item = item if isinstance(item, tuple) else (item,)
expected_cube0_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]][0].shape, tuple_item)
expected_cube0_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]][0].dimensions, tuple_item)
# Assert output is as expected.
sliced_sequence = ndc[item]
assert isinstance(sliced_sequence, NDCubeSequence)
assert int(sliced_sequence.shape[0].value) == tuple_item[0].stop - tuple_item[0].start
assert (sliced_sequence[0].shape == expected_cube0_dims).all()
assert int(sliced_sequence.dimensions[0]) == tuple_item[0].stop - tuple_item[0].start
assert (sliced_sequence[0].dimensions == expected_cube0_dims).all()


@pytest.mark.parametrize("ndc, item",
Expand All @@ -56,7 +56,7 @@ def test_extract_ndcube(ndc, item):
tuple_item = item if isinstance(item, tuple) else (item,)
expected_cube_dims = derive_sliced_cube_dims(ndc.data[tuple_item[0]].shape, tuple_item)
assert isinstance(cube, NDCube)
assert (cube.shape == expected_cube_dims).all()
assert (cube.dimensions == expected_cube_dims).all()


@pytest.mark.parametrize("ndc, item, expected_common_axis",
Expand All @@ -72,7 +72,7 @@ def test_slice_common_axis(ndc, item, expected_common_axis):
assert sliced_sequence._common_axis == expected_common_axis


@pytest.mark.parametrize("ndc, item, expected_shape",
@pytest.mark.parametrize("ndc, item, expected_dimensions",
(
("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:7], (3 * u.pix,
2 * u.pix,
Expand All @@ -96,14 +96,14 @@ def test_slice_common_axis(ndc, item, expected_common_axis):
2 * u.pix))
),
indirect=("ndc",))
def test_index_as_cube(ndc, item, expected_shape):
def test_index_as_cube(ndc, item, expected_dimensions):
sliced_sequence = ndc.index_as_cube[item]
sliced_dims = sliced_sequence.shape
for dim, expected_dim in zip(sliced_dims, expected_shape):
for dim, expected_dim in zip(sliced_dims, expected_dimensions):
(dim == expected_dim).all()


@pytest.mark.parametrize("ndc, axis, expected_shape",
@pytest.mark.parametrize("ndc, axis, expected_dimensions",
(
("ndcubesequence_4c_ln_lt_l", 0, (8 * u.pix,
3 * u.pix,
Expand All @@ -113,9 +113,9 @@ def test_index_as_cube(ndc, item, expected_shape):
4 * u.pix))
),
indirect=("ndc",))
def test_explode_along_axis_common_axis_None(ndc, axis, expected_shape):
def test_explode_along_axis_common_axis_None(ndc, axis, expected_dimensions):
exploded_sequence = ndc.explode_along_axis(axis)
assert exploded_sequence.shape == expected_shape
assert exploded_sequence.shape == expected_dimensions
assert exploded_sequence._common_axis is None


Expand All @@ -133,26 +133,26 @@ def test_explode_along_axis_common_axis_changed(ndc):
assert exploded_sequence._common_axis == ndc._common_axis - 1


@pytest.mark.parametrize("ndc, expected_shape",
@pytest.mark.parametrize("ndc, expected_dimensions",
(
("ndcubesequence_4c_ln_lt_l_cax1", (4 * u.pix,
2. * u.pix,
3. * u.pix,
4. * u.pix)),
),
indirect=("ndc",))
def test_shape(ndc, expected_shape):
def test_dimensions(ndc, expected_dimensions):
unit_tester = unittest.TestCase()
unit_tester.assertEqual(ndc.shape, expected_shape)
unit_tester.assertEqual(ndc.dimensions, expected_dimensions)


@pytest.mark.parametrize("ndc, expected_shape",
@pytest.mark.parametrize("ndc, expected_dimensions",
(
("ndcubesequence_4c_ln_lt_l_cax1", [2., 12, 4] * u.pix),
),
indirect=("ndc",))
def test_cube_like_shape(ndc, expected_shape):
assert (ndc.cube_like_dimensions == expected_shape).all()
def test_cube_like_shape(ndc, expected_dimensions):
assert (ndc.cube_like_dimensions == expected_dimensions).all()


@pytest.mark.parametrize("ndc", (("ndcubesequence_4c_ln_lt_l",)), indirect=("ndc",))
Expand Down

0 comments on commit 32b58c4

Please sign in to comment.